diff --git a/.gitignore b/.gitignore index 5686f43de38..791f7b1bb7f 100644 --- a/.gitignore +++ b/.gitignore @@ -150,7 +150,7 @@ venv/ ENV/ env.bak/ venv.bak/ -.code-workspace +*.code-workspace shell.nix # Spyder project settings diff --git a/BaseClasses.py b/BaseClasses.py index 88857f80321..092f330bcbb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,6 +1,6 @@ from __future__ import annotations -import copy +import collections import itertools import functools import logging @@ -63,7 +63,6 @@ class MultiWorld(): state: CollectionState plando_options: PlandoOptions - accessibility: Dict[int, Options.Accessibility] early_items: Dict[int, Dict[str, int]] local_early_items: Dict[int, Dict[str, int]] local_items: Dict[int, Options.LocalItems] @@ -288,6 +287,86 @@ def set_item_links(self): group["non_local_items"] = item_link["non_local_items"] group["link_replacement"] = replacement_prio[item_link["link_replacement"]] + def link_items(self) -> None: + """Called to link together items in the itempool related to the registered item link groups.""" + from worlds import AutoWorld + + for group_id, group in self.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 self.itempool: + if item.player in counters and item.name in shared_pool: + counters[item.player][item.name] += 1 + classifications[item.name] |= item.classification + + for player in players.copy(): + if all([counters[player][item] == 0 for item in shared_pool]): + players.remove(player) + del (counters[player]) + + if not players: + return None, None + + for item in shared_pool: + count = min(counters[player][item] for player in players) + if count: + for player in players: + counters[player][item] = count + else: + for player in players: + del (counters[player][item]) + return counters, classifications + + common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) + if not common_item_count: + continue + + new_itempool: List[Item] = [] + for item_name, item_count in next(iter(common_item_count.values())).items(): + for _ in range(item_count): + new_item = group["world"].create_item(item_name) + # mangle together all original classification bits + new_item.classification |= classifications[item_name] + new_itempool.append(new_item) + + region = Region("Menu", group_id, self, "ItemLink") + self.regions.append(region) + locations = region.locations + for item in self.itempool: + count = common_item_count.get(item.player, {}).get(item.name, 0) + if count: + loc = Location(group_id, f"Item Link: {item.name} -> {self.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_) + + locations.append(loc) + loc.place_locked_item(item) + common_item_count[item.player][item.name] -= 1 + else: + new_itempool.append(item) + + itemcount = len(self.itempool) + self.itempool = new_itempool + + while itemcount > len(self.itempool): + items_to_add = [] + for player in group["players"]: + if group["link_replacement"]: + item_player = group_id + else: + item_player = player + if group["replacement_items"][player]: + items_to_add.append(AutoWorld.call_single(self, "create_item", item_player, + group["replacement_items"][player])) + else: + items_to_add.append(AutoWorld.call_single(self, "create_filler", item_player)) + self.random.shuffle(items_to_add) + self.itempool.extend(items_to_add[:itemcount - len(self.itempool)]) + def secure(self): self.random = ThreadBarrierProxy(secrets.SystemRandom()) self.is_race = True @@ -523,26 +602,22 @@ def fulfills_accessibility(self, state: Optional[CollectionState] = None): players: Dict[str, Set[int]] = { "minimal": set(), "items": set(), - "locations": set() + "full": set() } - for player, access in self.accessibility.items(): - players[access.current_key].add(player) + for player, world in self.worlds.items(): + players[world.options.accessibility.current_key].add(player) beatable_fulfilled = False - def location_condition(location: Location): + def location_condition(location: Location) -> bool: """Determine if this location has to be accessible, location is already filtered by location_relevant""" - if location.player in players["locations"] or (location.item and location.item.player not in - players["minimal"]): - return True - return False + return location.player in players["full"] or \ + (location.item and location.item.player not in players["minimal"]) - def location_relevant(location: Location): + def location_relevant(location: Location) -> bool: """Determine if this location is relevant to sweep.""" - if location.progress_type != LocationProgressType.EXCLUDED \ - and (location.player in players["locations"] or location.advancement): - return True - return False + return location.progress_type != LocationProgressType.EXCLUDED \ + and (location.player in players["full"] or location.advancement) def all_done() -> bool: """Check if all access rules are fulfilled""" @@ -643,14 +718,14 @@ def update_reachable_regions(self, player: int): def copy(self) -> CollectionState: ret = CollectionState(self.multiworld) - ret.prog_items = copy.deepcopy(self.prog_items) - ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in - self.reachable_regions} - ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in - self.blocked_connections} - ret.events = copy.copy(self.events) - ret.path = copy.copy(self.path) - ret.locations_checked = copy.copy(self.locations_checked) + ret.prog_items = {player: counter.copy() for player, counter in self.prog_items.items()} + ret.reachable_regions = {player: region_set.copy() for player, region_set in + self.reachable_regions.items()} + ret.blocked_connections = {player: entrance_set.copy() for player, entrance_set in + self.blocked_connections.items()} + ret.events = self.events.copy() + ret.path = self.path.copy() + ret.locations_checked = self.locations_checked.copy() for function in self.additional_copy_functions: ret = function(self, ret) return ret @@ -680,13 +755,13 @@ def can_reach_entrance(self, spot: str, player: int) -> bool: 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: + def sweep_for_events(self, locations: Optional[Iterable[Location]] = None) -> None: if locations is None: locations = self.multiworld.get_filled_locations() reachable_events = True # since the loop has a good chance to run more than once, only filter the events once - locations = {location for location in locations if location.advancement and location not in self.events and - not key_only or getattr(location.item, "locked_dungeon_item", False)} + locations = {location for location in locations if location.advancement and location not in self.events} + while reachable_events: reachable_events = {location for location in locations if location.can_reach(self)} locations -= reachable_events @@ -1052,9 +1127,9 @@ def can_fill(self, state: CollectionState, item: Item, check_access=True) -> boo and (not check_access or self.can_reach(state)))) def can_reach(self, state: CollectionState) -> bool: - # self.access_rule computes faster on average, so placing it first for faster abort + # Region.can_reach is just a cache lookup, so placing it first for faster abort on average assert self.parent_region, "Can't reach location without region" - return self.access_rule(state) and self.parent_region.can_reach(state) + return self.parent_region.can_reach(state) and self.access_rule(state) def place_locked_item(self, item: Item): if self.item: @@ -1291,8 +1366,6 @@ def create_playthrough(self, create_paths: bool = True) -> None: state = CollectionState(multiworld) collection_spheres = [] while required_locations: - state.sweep_for_events(key_only=True) - sphere = set(filter(state.can_reach, required_locations)) for location in sphere: diff --git a/CommonClient.py b/CommonClient.py index f8d1fcb7a22..09937e4b9ab 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -61,6 +61,7 @@ def _cmd_connect(self, address: str = "") -> bool: if address: self.ctx.server_address = None self.ctx.username = None + self.ctx.password = None elif not self.ctx.server_address: self.output("Please specify an address.") return False @@ -514,6 +515,7 @@ def update_permissions(self, permissions: typing.Dict[str, int]): async def shutdown(self): self.server_address = "" self.username = None + self.password = None self.cancel_autoreconnect() if self.server and not self.server.socket.closed: await self.server.socket.close() diff --git a/Fill.py b/Fill.py index 4967ff07360..5185bbb60ee 100644 --- a/Fill.py +++ b/Fill.py @@ -646,7 +646,6 @@ def balance_multiworld_progression(multiworld: MultiWorld) -> None: def get_sphere_locations(sphere_state: CollectionState, locations: typing.Set[Location]) -> typing.Set[Location]: - sphere_state.sweep_for_events(key_only=True, locations=locations) return {loc for loc in locations if sphere_state.can_reach(loc)} def item_percentage(player: int, num: int) -> float: diff --git a/Main.py b/Main.py index de6b467f93d..ce054dcd393 100644 --- a/Main.py +++ b/Main.py @@ -124,14 +124,19 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No 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 + world_excluded_locations = set() for location_name in multiworld.worlds[player].options.priority_locations.value: try: 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 multiworld.worlds[player].location_name_to_id: - raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e - else: + except KeyError: + continue + + if location.progress_type != LocationProgressType.EXCLUDED: location.progress_type = LocationProgressType.PRIORITY + else: + logger.warning(f"Unable to prioritize location \"{location_name}\" in player {player}'s world because the world excluded it.") + world_excluded_locations.add(location_name) + multiworld.worlds[player].options.priority_locations.value -= world_excluded_locations # Set local and non-local item rules. if multiworld.players > 1: @@ -179,82 +184,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No 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 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 multiworld.itempool: - if item.player in counters and item.name in shared_pool: - counters[item.player][item.name] += 1 - classifications[item.name] |= item.classification - - for player in players.copy(): - if all([counters[player][item] == 0 for item in shared_pool]): - players.remove(player) - del (counters[player]) - - if not players: - return None, None - - for item in shared_pool: - count = min(counters[player][item] for player in players) - if count: - for player in players: - counters[player][item] = count - else: - for player in players: - del (counters[player][item]) - return counters, classifications - - common_item_count, classifications = find_common_pool(group["players"], group["item_pool"]) - if not common_item_count: - continue - - new_itempool: List[Item] = [] - for item_name, item_count in next(iter(common_item_count.values())).items(): - for _ in range(item_count): - new_item = group["world"].create_item(item_name) - # mangle together all original classification bits - new_item.classification |= classifications[item_name] - new_itempool.append(new_item) - - region = Region("Menu", group_id, multiworld, "ItemLink") - multiworld.regions.append(region) - locations = region.locations - 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} -> {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_) - - locations.append(loc) - loc.place_locked_item(item) - common_item_count[item.player][item.name] -= 1 - else: - new_itempool.append(item) - - itemcount = len(multiworld.itempool) - multiworld.itempool = new_itempool - - while itemcount > len(multiworld.itempool): - items_to_add = [] - for player in group["players"]: - if group["link_replacement"]: - item_player = group_id - else: - item_player = player - if group["replacement_items"][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(multiworld, "create_filler", item_player)) - multiworld.random.shuffle(items_to_add) - multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)]) + multiworld.link_items() if any(multiworld.item_links.values()): multiworld._all_state = None diff --git a/Options.py b/Options.py index b5fb25ea34a..d040828509d 100644 --- a/Options.py +++ b/Options.py @@ -786,17 +786,22 @@ class VerifyKeys(metaclass=FreezeValidKeys): verify_location_name: bool = False value: typing.Any - @classmethod - def verify_keys(cls, data: typing.Iterable[str]) -> None: - if cls.valid_keys: - data = set(data) - dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data) - extra = dataset - cls._valid_keys + def verify_keys(self) -> None: + if self.valid_keys: + data = set(self.value) + dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data) + extra = dataset - self._valid_keys if extra: - raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. " - f"Allowed keys: {cls._valid_keys}.") + raise OptionError( + f"Found unexpected key {', '.join(extra)} in {getattr(self, 'display_name', self)}. " + f"Allowed keys: {self._valid_keys}." + ) def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None: + try: + self.verify_keys() + except OptionError as validation_error: + raise OptionError(f"Player {player_name} has invalid option keys:\n{validation_error}") if self.convert_name_groups and self.verify_item_name: new_value = type(self.value)() # empty container of whatever value is for item_name in self.value: @@ -833,7 +838,6 @@ def __init__(self, value: typing.Dict[str, typing.Any]): @classmethod def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict: if type(data) == dict: - cls.verify_keys(data) return cls(data) else: raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") @@ -879,7 +883,6 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): if is_iterable_except_str(data): - cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -905,7 +908,6 @@ def from_text(cls, text: str): @classmethod def from_any(cls, data: typing.Any): if is_iterable_except_str(data): - cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -948,6 +950,19 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P self.value = [] logging.warning(f"The plando texts module is turned off, " f"so text for {player_name} will be ignored.") + else: + super().verify(world, player_name, plando_options) + + def verify_keys(self) -> None: + if self.valid_keys: + data = set(text.at for text in self) + dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data) + extra = dataset - self._valid_keys + if extra: + raise OptionError( + f"Invalid \"at\" placement {', '.join(extra)} in {getattr(self, 'display_name', self)}. " + f"Allowed placements: {self._valid_keys}." + ) @classmethod def from_any(cls, data: PlandoTextsFromAnyType) -> Self: @@ -971,7 +986,6 @@ def from_any(cls, data: PlandoTextsFromAnyType) -> Self: texts.append(text) else: raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}") - cls.verify_keys([text.at for text in texts]) return cls(texts) else: raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}") @@ -1144,18 +1158,35 @@ def __len__(self) -> int: class Accessibility(Choice): - """Set rules for reachability of your items/locations. + """ + Set rules for reachability of your items/locations. + + **Full:** ensure everything can be reached and acquired. - - **Locations:** ensure everything can be reached and acquired. - - **Items:** ensure all logically relevant items can be acquired. - - **Minimal:** ensure what is needed to reach your goal can be acquired. + **Minimal:** ensure what is needed to reach your goal can be acquired. """ display_name = "Accessibility" rich_text_doc = True - option_locations = 0 - option_items = 1 + option_full = 0 option_minimal = 2 alias_none = 2 + alias_locations = 0 + alias_items = 0 + default = 0 + + +class ItemsAccessibility(Accessibility): + """ + Set rules for reachability of your items/locations. + + **Full:** ensure everything can be reached and acquired. + + **Minimal:** ensure what is needed to reach your goal can be acquired. + + **Items:** ensure all logically relevant items can be acquired. Some items, such as keys, may be self-locking, and + some locations may be inaccessible. + """ + option_items = 1 default = 1 diff --git a/README.md b/README.md index cebd4f7e752..5b66e3db878 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Currently, the following games are supported: * Aquaria * Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 * A Hat in Time +* Old School Runescape For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 33339daa198..15b7bd61cee 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -231,6 +231,13 @@ def generate_yaml(game: str): del options[key] + # Detect keys which end with -range, indicating a NamedRange with a possible custom value + elif key_parts[-1].endswith("-range"): + if options[key_parts[-1][:-6]] == "custom": + options[key_parts[-1][:-6]] = val + + del options[key] + # Detect random-* keys and set their options accordingly for key, val in options.copy().items(): if key.startswith("random-"): diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index 415739b861a..30a4fc78dff 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -54,7 +54,7 @@ {% macro NamedRange(option_name, option) %} {{ OptionTitle(option_name, option) }}
- {% for key, val in option.special_range_names.items() %} {% if option.default == val %} @@ -64,17 +64,17 @@ {% endfor %} -
+
- + {{ option.default | default(option.range_start) if option.default != "random" else option.range_start }} {{ RandomizeButton(option_name, option) }} diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html index aeb6e864a54..73de5d56eb2 100644 --- a/WebHostLib/templates/playerOptions/playerOptions.html +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -11,7 +11,7 @@ diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index 8e567afc35f..75b5fb0202d 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -79,7 +79,7 @@ def __init__(self, room: Room): # Normal lookup tables as well. self.item_name_to_id[game] = game_package["item_name_to_id"] - self.location_name_to_id[game] = game_package["item_name_to_id"] + self.location_name_to_id[game] = game_package["location_name_to_id"] def get_seed_name(self) -> str: """Retrieves the seed name.""" diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 3b40d7e77a7..4f012c306be 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -1,8 +1,8 @@ # Archipelago World Code Owners / Maintainers Document # -# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder. For any pull -# requests that modify these worlds, a code owner must approve the PR in addition to a core maintainer. This is not to -# be used for files/folders outside the /worlds folder, those will always need sign off from a core maintainer. +# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder as well as +# certain documentation. For any pull requests that modify these worlds/docs, a code owner must approve the PR in +# addition to a core maintainer. All other files and folders are owned and maintained by core maintainers directly. # # All usernames must be GitHub usernames (and are case sensitive). @@ -115,6 +115,9 @@ # Ocarina of Time /worlds/oot/ @espeon65536 +# Old School Runescape +/worlds/osrs @digiholic + # Overcooked! 2 /worlds/overcooked2/ @toasterparty @@ -226,3 +229,11 @@ # Ori and the Blind Forest # /worlds_disabled/oribf/ + +################### +## Documentation ## +################### + +# Apworld Dev Faq +/docs/apworld_dev_faq.md @qwint @ScipioWright + diff --git a/docs/apworld_dev_faq.md b/docs/apworld_dev_faq.md new file mode 100644 index 00000000000..8d9429afa32 --- /dev/null +++ b/docs/apworld_dev_faq.md @@ -0,0 +1,45 @@ +# APWorld Dev FAQ + +This document is meant as a reference tool to show solutions to common problems when developing an apworld. +It is not intended to answer every question about Archipelago and it assumes you have read the other docs, +including [Contributing](contributing.md), [Adding Games](), and [World API](). + +--- + +### My game has a restrictive start that leads to fill errors + +Hint to the Generator that an item needs to be in sphere one with local_early_items. Here, `1` represents the number of "Sword" items to attempt to place in sphere one. +```py +early_item_name = "Sword" +self.multiworld.local_early_items[self.player][early_item_name] = 1 +``` + +Some alternative ways to try to fix this problem are: +* Add more locations to sphere one of your world, potentially only when there would be a restrictive start +* Pre-place items yourself, such as during `create_items` +* Put items into the player's starting inventory using `push_precollected` +* Raise an exception, such as an `OptionError` during `generate_early`, to disallow options that would lead to a restrictive start + +--- + +### I have multiple settings that change the item/location pool counts and need to balance them out + +In an ideal situation your system for producing locations and items wouldn't leave any opportunity for them to be unbalanced. But in real, complex situations, that might be unfeasible. + +If that's the case, you can create extra filler based on the difference between your unfilled locations and your itempool by comparing [get_unfilled_locations](https://github.com/ArchipelagoMW/Archipelago/blob/main/BaseClasses.py#:~:text=get_unfilled_locations) to your list of items to submit + +Note: to use self.create_filler(), self.get_filler_item_name() should be defined to only return valid filler item names +```py +total_locations = len(self.multiworld.get_unfilled_locations(self.player)) +item_pool = self.create_non_filler_items() + +for _ in range(total_locations - len(item_pool)): + item_pool.append(self.create_filler()) + +self.multiworld.itempool += item_pool +``` + +A faster alternative to the `for` loop would be to use a [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions): +```py +item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))] +``` diff --git a/kvui.py b/kvui.py index 500203a8818..f83590a819d 100644 --- a/kvui.py +++ b/kvui.py @@ -595,8 +595,9 @@ def command_button_action(self, button): "!help for server commands.") def connect_button_action(self, button): + self.ctx.username = None + self.ctx.password = None if self.ctx.server: - self.ctx.username = None async_start(self.ctx.disconnect()) else: async_start(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", ""))) @@ -836,6 +837,10 @@ def _handle_color(self, node: JSONMessagePart): return self._handle_text(node) def _handle_text(self, node: JSONMessagePart): + # All other text goes through _handle_color, and we don't want to escape markup twice, + # or mess up text that already has intentional markup applied to it + if node.get("type", "text") == "text": + node["text"] = escape_markup(node["text"]) for ref in node.get("refs", []): node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]" self.ref_count += 1 diff --git a/settings.py b/settings.py index 7ab618c344d..79277052145 100644 --- a/settings.py +++ b/settings.py @@ -3,6 +3,7 @@ This is different from player options. """ +import os import os.path import shutil import sys @@ -11,7 +12,6 @@ from enum import IntEnum from threading import Lock from typing import cast, Any, BinaryIO, ClassVar, Dict, Iterator, List, Optional, TextIO, Tuple, Union, TypeVar -import os __all__ = [ "get_settings", "fmt_doc", "no_gui", @@ -798,6 +798,7 @@ def autosave() -> None: atexit.register(autosave) def save(self, location: Optional[str] = None) -> None: # as above + from Utils import parse_yaml location = location or self._filename assert location, "No file specified" temp_location = location + ".tmp" # not using tempfile to test expected file access @@ -807,10 +808,18 @@ def save(self, location: Optional[str] = None) -> None: # as above # can't use utf-8-sig because it breaks backward compat: pyyaml on Windows with bytes does not strip the BOM with open(temp_location, "w", encoding="utf-8") as f: self.dump(f) - # replace old with new - if os.path.exists(location): + f.flush() + if hasattr(os, "fsync"): + os.fsync(f.fileno()) + # validate new file is valid yaml + with open(temp_location, encoding="utf-8") as f: + parse_yaml(f.read()) + # replace old with new, try atomic operation first + try: + os.rename(temp_location, location) + except (OSError, FileExistsError): os.unlink(location) - os.rename(temp_location, location) + os.rename(temp_location, location) self._filename = location def dump(self, f: TextIO, level: int = 0) -> None: @@ -832,7 +841,6 @@ def get_settings() -> Settings: with _lock: # make sure we only have one instance res = getattr(get_settings, "_cache", None) if not res: - import os from Utils import user_path, local_path filenames = ("options.yaml", "host.yaml") locations: List[str] = [] diff --git a/setup.py b/setup.py index cb4d1a7511b..0c9ee2c2930 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ "Adventure", "ArchipIDLE", "Archipelago", - "ChecksFinder", "Clique", "Final Fantasy", "Lufia II Ancient Cave", diff --git a/test/general/test_fill.py b/test/general/test_fill.py index 485007ff0d5..db24b706918 100644 --- a/test/general/test_fill.py +++ b/test/general/test_fill.py @@ -174,8 +174,8 @@ def test_minimal_mixed_fill(self): player1 = generate_player_data(multiworld, 1, 3, 3) player2 = generate_player_data(multiworld, 2, 3, 3) - multiworld.accessibility[player1.id].value = multiworld.accessibility[player1.id].option_minimal - multiworld.accessibility[player2.id].value = multiworld.accessibility[player2.id].option_locations + multiworld.worlds[player1.id].options.accessibility.value = Accessibility.option_minimal + multiworld.worlds[player2.id].options.accessibility.value = Accessibility.option_full multiworld.completion_condition[player1.id] = lambda state: True multiworld.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id) diff --git a/test/general/test_host_yaml.py b/test/general/test_host_yaml.py index 7174befca42..3edbd34a51c 100644 --- a/test/general/test_host_yaml.py +++ b/test/general/test_host_yaml.py @@ -1,11 +1,12 @@ import os +import os.path import unittest from io import StringIO -from tempfile import TemporaryFile +from tempfile import TemporaryDirectory, TemporaryFile from typing import Any, Dict, List, cast import Utils -from settings import Settings, Group +from settings import Group, Settings, ServerOptions class TestIDs(unittest.TestCase): @@ -80,3 +81,27 @@ class AGroup(Group): self.assertEqual(value_spaces[2], value_spaces[0]) # start of sub-list self.assertGreater(value_spaces[3], value_spaces[0], f"{value_lines[3]} should have more indentation than {value_lines[0]} in {lines}") + + +class TestSettingsSave(unittest.TestCase): + def test_save(self) -> None: + """Test that saving and updating works""" + with TemporaryDirectory() as d: + filename = os.path.join(d, "host.yaml") + new_release_mode = ServerOptions.ReleaseMode("enabled") + # create default host.yaml + settings = Settings(None) + settings.save(filename) + self.assertTrue(os.path.exists(filename), + "Default settings could not be saved") + self.assertNotEqual(settings.server_options.release_mode, new_release_mode, + "Unexpected default release mode") + # update host.yaml + settings.server_options.release_mode = new_release_mode + settings.save(filename) + self.assertFalse(os.path.exists(filename + ".tmp"), + "Temp file was not removed during save") + # read back host.yaml + settings = Settings(filename) + self.assertEqual(settings.server_options.release_mode, new_release_mode, + "Settings were not overwritten") diff --git a/test/general/test_options.py b/test/general/test_options.py index 6cf642029e6..2229b7ea7e6 100644 --- a/test/general/test_options.py +++ b/test/general/test_options.py @@ -1,6 +1,6 @@ import unittest -from BaseClasses import PlandoOptions +from BaseClasses import MultiWorld, PlandoOptions from Options import ItemLinks from worlds.AutoWorld import AutoWorldRegister @@ -47,3 +47,15 @@ def test_item_links_name_groups(self): self.assertIn("Bow", link.value[0]["item_pool"]) # TODO test that the group created using these options has the items + + def test_item_links_resolve(self): + """Test item link option resolves correctly.""" + item_link_group = [{ + "name": "ItemLinkTest", + "item_pool": ["Everything"], + "link_replacement": False, + "replacement_item": None, + }] + item_links = {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)} + for link in item_links.values(): + self.assertEqual(link.value[0], item_link_group[0]) diff --git a/test/multiworld/test_multiworlds.py b/test/multiworld/test_multiworlds.py index 677f0de8293..5289cac6c35 100644 --- a/test/multiworld/test_multiworlds.py +++ b/test/multiworld/test_multiworlds.py @@ -69,7 +69,7 @@ def test_two_player_single_game_fills(self) -> None: for world in AutoWorldRegister.world_types.values(): self.multiworld = setup_multiworld([world, world], ()) for world in self.multiworld.worlds.values(): - world.options.accessibility.value = Accessibility.option_locations + world.options.accessibility.value = Accessibility.option_full self.assertSteps(gen_steps) with self.subTest("filling multiworld", seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) diff --git a/worlds/__init__.py b/worlds/__init__.py index bb2fe866d02..c277ac9ca1d 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -73,7 +73,12 @@ def load(self) -> bool: else: # TODO: remove with 3.8 support mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0]) - mod.__package__ = f"worlds.{mod.__package__}" + if mod.__package__ is not None: + mod.__package__ = f"worlds.{mod.__package__}" + else: + # load_module does not populate package, we'll have to assume mod.__name__ is correct here + # probably safe to remove with 3.8 support + mod.__package__ = f"worlds.{mod.__name__}" mod.__name__ = f"worlds.{mod.__name__}" sys.modules[mod.__name__] = mod with warnings.catch_warnings(): diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py index 3ef83fe81e6..54c6e6b5d39 100644 --- a/worlds/ahit/Items.py +++ b/worlds/ahit/Items.py @@ -39,7 +39,7 @@ def create_itempool(world: "HatInTimeWorld") -> List[Item]: continue else: if name == "Scooter Badge": - if world.options.CTRLogic is CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE: + if world.options.CTRLogic == CTRLogic.option_scooter or get_difficulty(world) >= Difficulty.MODERATE: item_type = ItemClassification.progression elif name == "No Bonk Badge" and world.is_dw(): item_type = ItemClassification.progression diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py index c6aeaa35779..8cb3782bdec 100644 --- a/worlds/ahit/Regions.py +++ b/worlds/ahit/Regions.py @@ -659,6 +659,10 @@ def is_valid_act_combo(world: "HatInTimeWorld", entrance_act: Region, if exit_act.name not in chapter_finales: return False + exit_chapter: str = act_chapters.get(exit_act.name) + # make sure that certain time rift combinations never happen + always_block: bool = exit_chapter != "Mafia Town" and exit_chapter != "Subcon Forest" + if not ignore_certain_rules or always_block: if entrance_act.name in rift_access_regions and exit_act.name in rift_access_regions[entrance_act.name]: return False @@ -684,9 +688,12 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool: if act.name not in guaranteed_first_acts: return False + if world.options.ActRandomizer == ActRandomizer.option_light and "Time Rift" in act.name: + return False + # If there's only a single level in the starting chapter, only allow Mafia Town or Subcon Forest levels start_chapter = world.options.StartingChapter - if start_chapter is ChapterIndex.ALPINE or start_chapter is ChapterIndex.SUBCON: + if start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON: if "Time Rift" in act.name: return False @@ -723,7 +730,8 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool: elif act.name == "Contractual Obligations" and world.options.ShuffleSubconPaintings: return False - if world.options.ShuffleSubconPaintings and act_chapters.get(act.name, "") == "Subcon Forest": + if world.options.ShuffleSubconPaintings and "Time Rift" not in act.name \ + and act_chapters.get(act.name, "") == "Subcon Forest": # Only allow Subcon levels if painting skips are allowed if diff < Difficulty.MODERATE or world.options.NoPaintingSkips: return False diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py index b0513c43328..b716b793a79 100644 --- a/worlds/ahit/Rules.py +++ b/worlds/ahit/Rules.py @@ -1,7 +1,6 @@ from worlds.AutoWorld import CollectionState from worlds.generic.Rules import add_rule, set_rule -from .Locations import location_table, zipline_unlocks, is_location_valid, contract_locations, \ - shop_locations, event_locs +from .Locations import location_table, zipline_unlocks, is_location_valid, shop_locations, event_locs from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty, HitType from BaseClasses import Location, Entrance, Region from typing import TYPE_CHECKING, List, Callable, Union, Dict @@ -148,14 +147,14 @@ def set_rules(world: "HatInTimeWorld"): if world.is_dlc1(): chapter_list.append(ChapterIndex.CRUISE) - if world.is_dlc2() and final_chapter is not ChapterIndex.METRO: + if world.is_dlc2() and final_chapter != ChapterIndex.METRO: chapter_list.append(ChapterIndex.METRO) chapter_list.remove(starting_chapter) world.random.shuffle(chapter_list) # Make sure Alpine is unlocked before any DLC chapters are, as the Alpine door needs to be open to access them - if starting_chapter is not ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()): + if starting_chapter != ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()): index1 = 69 index2 = 69 pos: int @@ -165,7 +164,7 @@ def set_rules(world: "HatInTimeWorld"): if world.is_dlc1(): index1 = chapter_list.index(ChapterIndex.CRUISE) - if world.is_dlc2() and final_chapter is not ChapterIndex.METRO: + if world.is_dlc2() and final_chapter != ChapterIndex.METRO: index2 = chapter_list.index(ChapterIndex.METRO) lowest_index = min(index1, index2) @@ -242,9 +241,6 @@ def set_rules(world: "HatInTimeWorld"): if not is_location_valid(world, key): continue - if key in contract_locations.keys(): - continue - loc = world.multiworld.get_location(key, world.player) for hat in data.required_hats: @@ -256,7 +252,7 @@ def set_rules(world: "HatInTimeWorld"): if data.paintings > 0 and world.options.ShuffleSubconPaintings: add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings)) - if data.hit_type is not HitType.none and world.options.UmbrellaLogic: + if data.hit_type != HitType.none and world.options.UmbrellaLogic: if data.hit_type == HitType.umbrella: add_rule(loc, lambda state: state.has("Umbrella", world.player)) @@ -518,7 +514,7 @@ def set_hard_rules(world: "HatInTimeWorld"): lambda state: can_use_hat(state, world, HatType.ICE)) # Hard: clear Rush Hour with Brewing Hat only - if world.options.NoTicketSkips is not NoTicketSkips.option_true: + if world.options.NoTicketSkips != NoTicketSkips.option_true: set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: can_use_hat(state, world, HatType.BREWING)) else: diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py index 6996811c6a2..14cf13ec346 100644 --- a/worlds/ahit/__init__.py +++ b/worlds/ahit/__init__.py @@ -1,15 +1,16 @@ from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \ - calculate_yarn_costs + calculate_yarn_costs, alps_hooks from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \ get_total_locations -from .Rules import set_rules +from .Rules import set_rules, has_paintings from .Options import AHITOptions, slot_data_options, adjust_options, RandomizeHatOrder, EndGoal, create_option_groups -from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item +from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item, Difficulty from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses from worlds.AutoWorld import World, WebWorld, CollectionState +from worlds.generic.Rules import add_rule from typing import List, Dict, TextIO from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type from Utils import local_path @@ -86,19 +87,27 @@ def generate_early(self): if self.is_dw_only(): return - # If our starting chapter is 4 and act rando isn't on, force hookshot into inventory - # If starting chapter is 3 and painting shuffle is enabled, and act rando isn't, give one free painting unlock - start_chapter: ChapterIndex = ChapterIndex(self.options.StartingChapter) - - if start_chapter == ChapterIndex.ALPINE or start_chapter == ChapterIndex.SUBCON: - if not self.options.ActRandomizer: - if start_chapter == ChapterIndex.ALPINE: - self.multiworld.push_precollected(self.create_item("Hookshot Badge")) - if self.options.UmbrellaLogic: - self.multiworld.push_precollected(self.create_item("Umbrella")) - - if start_chapter == ChapterIndex.SUBCON and self.options.ShuffleSubconPaintings: + # Take care of some extremely restrictive starts in other chapters with act shuffle off + if not self.options.ActRandomizer: + start_chapter = self.options.StartingChapter + if start_chapter == ChapterIndex.ALPINE: + self.multiworld.push_precollected(self.create_item("Hookshot Badge")) + if self.options.UmbrellaLogic: + self.multiworld.push_precollected(self.create_item("Umbrella")) + + if self.options.ShuffleAlpineZiplines: + ziplines = list(alps_hooks.keys()) + ziplines.remove("Zipline Unlock - The Twilight Bell Path") # not enough checks from this one + self.multiworld.push_precollected(self.create_item(self.random.choice(ziplines))) + elif start_chapter == ChapterIndex.SUBCON: + if self.options.ShuffleSubconPaintings: self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock")) + elif start_chapter == ChapterIndex.BIRDS: + if self.options.UmbrellaLogic: + if self.options.LogicDifficulty < Difficulty.EXPERT: + self.multiworld.push_precollected(self.create_item("Umbrella")) + elif self.options.LogicDifficulty < Difficulty.MODERATE: + self.multiworld.push_precollected(self.create_item("Umbrella")) def create_regions(self): # noinspection PyClassVar @@ -119,7 +128,10 @@ def create_regions(self): # place vanilla contract locations if contract shuffle is off if not self.options.ShuffleActContracts: for name in contract_locations.keys(): - self.multiworld.get_location(name, self.player).place_locked_item(create_item(self, name)) + loc = self.get_location(name) + loc.place_locked_item(create_item(self, name)) + if self.options.ShuffleSubconPaintings and loc.name != "Snatcher's Contract - The Subcon Well": + add_rule(loc, lambda state: has_paintings(state, self, 1)) def create_items(self): if self.has_yarn(): @@ -318,7 +330,7 @@ def collect(self, state: "CollectionState", item: "Item") -> bool: def remove(self, state: "CollectionState", item: "Item") -> bool: old_count: int = state.count(item.name, self.player) - change = super().collect(state, item) + change = super().remove(state, item) if change and old_count == 1: if "Stamp" in item.name: if "2 Stamp" in item.name: diff --git a/worlds/ahit/docs/setup_en.md b/worlds/ahit/docs/setup_en.md index 509869fc256..23b34907071 100644 --- a/worlds/ahit/docs/setup_en.md +++ b/worlds/ahit/docs/setup_en.md @@ -12,41 +12,29 @@ ## Instructions -1. Have Steam running. Open the Steam console with this link: [steam://open/console](steam://open/console) -This may not work for some browsers. If that's the case, and you're on Windows, open the Run dialog using Win+R, -paste the link into the box, and hit Enter. +1. **BACK UP YOUR SAVE FILES IN YOUR MAIN INSTALL IF YOU CARE ABOUT THEM!!!** + Go to `steamapps/common/HatinTime/HatinTimeGame/SaveData/` and copy everything inside that folder over to a safe place. + **This is important! Changing the game version CAN and WILL break your existing save files!!!** -2. In the Steam console, enter the following command: -`download_depot 253230 253232 7770543545116491859`. ***Wait for the console to say the download is finished!*** -This can take a while to finish (30+ minutes) depending on your connection speed, so please be patient. Additionally, -**try to prevent your connection from being interrupted or slowed while Steam is downloading the depot,** -or else the download may potentially become corrupted (see first FAQ issue below). +2. In your Steam library, right-click on **A Hat in Time** in the list of games and click on **Properties**. -3. Once the download finishes, go to `steamapps/content/app_253230` in Steam's program folder. +3. Click the **Betas** tab. In the **Beta Participation** dropdown, select `tcplink`. + While it downloads, you can subscribe to the [Archipelago workshop mod.]((https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601)) -4. There should be a folder named `depot_253232`. Rename it to HatinTime_AP and move it to your `steamapps/common` folder. +4. Once the game finishes downloading, start it up. + In Game Settings, make sure **Enable Developer Console** is checked. -5. In the HatinTime_AP folder, navigate to `Binaries/Win64` and create a new file: `steam_appid.txt`. -In this new text file, input the number **253230** on the first line. - - -6. Create a shortcut of `HatinTimeGame.exe` from that folder and move it to wherever you'd like. -You will use this shortcut to open the Archipelago-compatible version of A Hat in Time. - - -7. Start up the game using your new shortcut. To confirm if you are on the correct version, -go to Settings -> Game Settings. If you don't see an option labelled ***Live Game Events*** you should be running -the correct version of the game. In Game Settings, make sure ***Enable Developer Console*** is checked. +5. You should now be good to go. See below for more details on how to use the mod and connect to an Archipelago game. ## Connecting to the Archipelago server -To connect to the multiworld server, simply run the **ArchipelagoAHITClient** -(or run it from the Launcher if you have the apworld installed) and connect it to the Archipelago server. +To connect to the multiworld server, simply run the **Archipelago AHIT Client** from the Launcher +and connect it to the Archipelago server. The game will connect to the client automatically when you create a new save file. @@ -61,33 +49,8 @@ make sure ***Enable Developer Console*** is checked in Game Settings and press t ## FAQ/Common Issues -### I followed the setup, but I receive an odd error message upon starting the game or creating a save file! -If you receive an error message such as -**"Failed to find default engine .ini to retrieve My Documents subdirectory to use. Force quitting."** or -**"Failed to load map "hub_spaceship"** after booting up the game or creating a save file respectively, then the depot -download was likely corrupted. The only way to fix this is to start the entire download all over again. -Unfortunately, this appears to be an underlying issue with Steam's depot downloader. The only way to really prevent this -from happening is to ensure that your connection is not interrupted or slowed while downloading. - -### The game keeps crashing on startup after the splash screen! -This issue is unfortunately very hard to fix, and the underlying cause is not known. If it does happen however, -try the following: - -- Close Steam **entirely**. -- Open the downpatched version of the game (with Steam closed) and allow it to load to the titlescreen. -- Close the game, and then open Steam again. -- After launching the game, the issue should hopefully disappear. If not, repeat the above steps until it does. - -### I followed the setup, but "Live Game Events" still shows up in the options menu! -The most common cause of this is the `steam_appid.txt` file. If you're on Windows 10, file extensions are hidden by -default (thanks Microsoft). You likely made the mistake of still naming the file `steam_appid.txt`, which, since file -extensions are hidden, would result in the file being named `steam_appid.txt.txt`, which is incorrect. -To show file extensions in Windows 10, open any folder, click the View tab at the top, and check -"File name extensions". Then you can correct the name of the file. If the name of the file is correct, -and you're still running into the issue, re-read the setup guide again in case you missed a step. -If you still can't get it to work, ask for help in the Discord thread. - -### The game is running on the older version, but it's not connecting when starting a new save! + +### The game is not connecting when starting a new save! For unknown reasons, the mod will randomly disable itself in the mod menu. To fix this, go to the Mods menu (rocket icon) in-game, and re-enable the mod. diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index ee3ebc587ce..20dd18038a1 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -1,8 +1,8 @@ import typing from BaseClasses import MultiWorld -from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, \ - StartInventoryPool, PlandoBosses, PlandoConnections, PlandoTexts, FreeText, Removed +from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, Option, \ + PlandoBosses, PlandoConnections, PlandoTexts, Removed, StartInventoryPool, Toggle from .EntranceShuffle import default_connections, default_dungeon_connections, \ inverted_default_connections, inverted_default_dungeon_connections from .Text import TextTable @@ -743,6 +743,7 @@ class ALttPPlandoTexts(PlandoTexts): alttp_options: typing.Dict[str, type(Option)] = { + "accessibility": ItemsAccessibility, "plando_connections": ALttPPlandoConnections, "plando_texts": ALttPPlandoTexts, "start_inventory_from_pool": StartInventoryPool, diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 67684a6f3ce..f596749ae66 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -2,6 +2,7 @@ import logging from typing import Iterator, Set +from Options import ItemsAccessibility from BaseClasses import Entrance, MultiWorld from worlds.generic.Rules import (add_item_rule, add_rule, forbid_item, item_name_in_location_names, location_item_name, set_rule, allow_self_locking_items) @@ -39,7 +40,7 @@ def set_rules(world): else: # Set access rules according to max glitches for multiworld progression. # Set accessibility to none, and shuffle assuming the no logic players can always win - world.accessibility[player] = world.accessibility[player].from_text("minimal") + world.accessibility[player].value = ItemsAccessibility.option_minimal world.progression_balancing[player].value = 0 else: @@ -377,7 +378,7 @@ def global_rules(multiworld: MultiWorld, player: int): or state.has("Cane of Somaria", player))) set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) @@ -393,7 +394,7 @@ def global_rules(multiworld: MultiWorld, player: int): if state.has('Hookshot', player) else state._lttp_has_key('Small Key (Swamp Palace)', player, 4)) set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') set_rule(multiworld.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 multiworld.small_key_shuffle[player] and multiworld.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: @@ -423,7 +424,7 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.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(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) @@ -522,12 +523,12 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.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 multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': set_always_allow(multiworld.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)) set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))) - if multiworld.accessibility[player] != 'locations': + if multiworld.accessibility[player] != 'full': set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', 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)) set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6)) @@ -1200,7 +1201,7 @@ def tr_big_key_chest_keys_needed(state): # 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': + if world.accessibility[player] == 'full': 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', @@ -1214,7 +1215,7 @@ def tr_big_key_chest_keys_needed(state): location.place_locked_item(item) toss_junk_item(world, player) - if world.accessibility[player] != 'locations': + if world.accessibility[player] != 'full': set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player))) diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py index 769dcc19985..328e28da934 100644 --- a/worlds/alttp/SubClasses.py +++ b/worlds/alttp/SubClasses.py @@ -76,10 +76,6 @@ def dungeon_item(self) -> Optional[str]: if self.type in {"SmallKey", "BigKey", "Map", "Compass"}: return self.type - @property - def locked_dungeon_item(self): - return self.location.locked and self.dungeon_item - class LTTPRegionType(IntEnum): LightWorld = 1 diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py index 0a2aa7a1865..a0a654991b4 100644 --- a/worlds/alttp/test/inverted/TestInverted.py +++ b/worlds/alttp/test/inverted/TestInverted.py @@ -1,11 +1,11 @@ -from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool +from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index a8fa5c808c3..bf25c5c9a16 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -6,7 +6,7 @@ from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index bbdf0f79244..1de22b95e59 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -6,7 +6,7 @@ from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops -from test.TestBase import TestBase +from test.bases import TestBase from worlds.alttp.test import LTTPTestBase diff --git a/worlds/aquaria/Items.py b/worlds/aquaria/Items.py index 34557d95d00..f822d675e6e 100644 --- a/worlds/aquaria/Items.py +++ b/worlds/aquaria/Items.py @@ -99,7 +99,7 @@ def __init__(self, id: int, count: int, type: ItemType, group: ItemGroup): "Mutant Costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume "Baby Nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus "Baby Piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha - "Arnassi Armor": ItemData(698023, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_seahorse_costume + "Arnassi Armor": ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume "Seed Bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag "King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull "Song Plant Spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed diff --git a/worlds/aquaria/Locations.py b/worlds/aquaria/Locations.py index 2eb9d1e9a29..f6e098103fd 100644 --- a/worlds/aquaria/Locations.py +++ b/worlds/aquaria/Locations.py @@ -45,7 +45,7 @@ class AquariaLocations: "Home Water, bulb below the grouper fish": 698058, "Home Water, bulb in the path below Nautilus Prime": 698059, "Home Water, bulb in the little room above the grouper fish": 698060, - "Home Water, bulb in the end of the left path from the Verse Cave": 698061, + "Home Water, bulb in the end of the path close to the Verse Cave": 698061, "Home Water, bulb in the top left path": 698062, "Home Water, bulb in the bottom left room": 698063, "Home Water, bulb close to Naija's Home": 698064, @@ -67,7 +67,7 @@ class AquariaLocations: locations_song_cave = { "Song Cave, Erulian spirit": 698206, - "Song Cave, bulb in the top left part": 698071, + "Song Cave, bulb in the top right part": 698071, "Song Cave, bulb in the big anemone room": 698072, "Song Cave, bulb in the path to the singing statues": 698073, "Song Cave, bulb under the rock in the path to the singing statues": 698074, @@ -152,6 +152,9 @@ class AquariaLocations: locations_arnassi_path = { "Arnassi Ruins, Arnassi Statue": 698164, + } + + locations_arnassi_cave_transturtle = { "Arnassi Ruins, Transturtle": 698217, } @@ -269,9 +272,12 @@ class AquariaLocations: } locations_forest_bl = { + "Kelp Forest bottom left area, Transturtle": 698212, + } + + locations_forest_bl_sc = { "Kelp Forest bottom left area, bulb close to the spirit crystals": 698054, "Kelp Forest bottom left area, Walker Baby": 698186, - "Kelp Forest bottom left area, Transturtle": 698212, } locations_forest_br = { @@ -370,7 +376,7 @@ class AquariaLocations: locations_sun_temple_r = { "Sun Temple, first bulb of the temple": 698091, - "Sun Temple, bulb on the left part": 698092, + "Sun Temple, bulb on the right part": 698092, "Sun Temple, bulb in the hidden room of the right part": 698093, "Sun Temple, Sun Key": 698182, } @@ -402,6 +408,9 @@ class AquariaLocations: "Abyss right area, bulb in the middle path": 698110, "Abyss right area, bulb behind the rock in the middle path": 698111, "Abyss right area, bulb in the left green room": 698112, + } + + locations_abyss_r_transturtle = { "Abyss right area, Transturtle": 698214, } @@ -499,6 +508,7 @@ class AquariaLocations: **AquariaLocations.locations_skeleton_path_sc, **AquariaLocations.locations_arnassi, **AquariaLocations.locations_arnassi_path, + **AquariaLocations.locations_arnassi_cave_transturtle, **AquariaLocations.locations_arnassi_crab_boss, **AquariaLocations.locations_sun_temple_l, **AquariaLocations.locations_sun_temple_r, @@ -509,6 +519,7 @@ class AquariaLocations: **AquariaLocations.locations_abyss_l, **AquariaLocations.locations_abyss_lb, **AquariaLocations.locations_abyss_r, + **AquariaLocations.locations_abyss_r_transturtle, **AquariaLocations.locations_energy_temple_1, **AquariaLocations.locations_energy_temple_2, **AquariaLocations.locations_energy_temple_3, @@ -530,6 +541,7 @@ class AquariaLocations: **AquariaLocations.locations_forest_tr, **AquariaLocations.locations_forest_tr_fp, **AquariaLocations.locations_forest_bl, + **AquariaLocations.locations_forest_bl_sc, **AquariaLocations.locations_forest_br, **AquariaLocations.locations_forest_boss, **AquariaLocations.locations_forest_boss_entrance, diff --git a/worlds/aquaria/Regions.py b/worlds/aquaria/Regions.py index 93c02d4e676..3ec1fb880e1 100755 --- a/worlds/aquaria/Regions.py +++ b/worlds/aquaria/Regions.py @@ -14,97 +14,112 @@ # Every condition to connect regions -def _has_hot_soup(state:CollectionState, player: int) -> bool: +def _has_hot_soup(state: CollectionState, player: int) -> bool: """`player` in `state` has the hotsoup item""" - return state.has("Hot soup", player) + return state.has_any({"Hot soup", "Hot soup x 2"}, player) -def _has_tongue_cleared(state:CollectionState, player: int) -> bool: +def _has_tongue_cleared(state: CollectionState, player: int) -> bool: """`player` in `state` has the Body tongue cleared item""" return state.has("Body tongue cleared", player) -def _has_sun_crystal(state:CollectionState, player: int) -> bool: +def _has_sun_crystal(state: CollectionState, player: int) -> bool: """`player` in `state` has the Sun crystal item""" return state.has("Has sun crystal", player) and _has_bind_song(state, player) -def _has_li(state:CollectionState, player: int) -> bool: +def _has_li(state: CollectionState, player: int) -> bool: """`player` in `state` has Li in its team""" return state.has("Li and Li song", player) -def _has_damaging_item(state:CollectionState, player: int) -> bool: +def _has_damaging_item(state: CollectionState, player: int) -> bool: """`player` in `state` has the shield song item""" - return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus", - "Baby Piranha", "Baby Blaster"}, player) + return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus", + "Baby Piranha", "Baby Blaster"}, player) -def _has_shield_song(state:CollectionState, player: int) -> bool: +def _has_energy_attack_item(state: CollectionState, player: int) -> bool: + """`player` in `state` has items that can do a lot of damage (enough to beat bosses)""" + return _has_energy_form(state, player) or _has_dual_form(state, player) + + +def _has_shield_song(state: CollectionState, player: int) -> bool: """`player` in `state` has the shield song item""" return state.has("Shield song", player) -def _has_bind_song(state:CollectionState, player: int) -> bool: +def _has_bind_song(state: CollectionState, player: int) -> bool: """`player` in `state` has the bind song item""" return state.has("Bind song", player) -def _has_energy_form(state:CollectionState, player: int) -> bool: +def _has_energy_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the energy form item""" return state.has("Energy form", player) -def _has_beast_form(state:CollectionState, player: int) -> bool: +def _has_beast_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the beast form item""" return state.has("Beast form", player) -def _has_nature_form(state:CollectionState, player: int) -> bool: +def _has_beast_and_soup_form(state: CollectionState, player: int) -> bool: + """`player` in `state` has the beast form item""" + return _has_beast_form(state, player) and _has_hot_soup(state, player) + + +def _has_beast_form_or_arnassi_armor(state: CollectionState, player: int) -> bool: + """`player` in `state` has the beast form item""" + return _has_beast_form(state, player) or state.has("Arnassi Armor", player) + + +def _has_nature_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the nature form item""" return state.has("Nature form", player) -def _has_sun_form(state:CollectionState, player: int) -> bool: +def _has_sun_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the sun form item""" return state.has("Sun form", player) -def _has_light(state:CollectionState, player: int) -> bool: +def _has_light(state: CollectionState, player: int) -> bool: """`player` in `state` has the light item""" return state.has("Baby Dumbo", player) or _has_sun_form(state, player) -def _has_dual_form(state:CollectionState, player: int) -> bool: +def _has_dual_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the dual form item""" return _has_li(state, player) and state.has("Dual form", player) -def _has_fish_form(state:CollectionState, player: int) -> bool: +def _has_fish_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the fish form item""" return state.has("Fish form", player) -def _has_spirit_form(state:CollectionState, player: int) -> bool: +def _has_spirit_form(state: CollectionState, player: int) -> bool: """`player` in `state` has the spirit form item""" return state.has("Spirit form", player) -def _has_big_bosses(state:CollectionState, player: int) -> bool: +def _has_big_bosses(state: CollectionState, player: int) -> bool: """`player` in `state` has beated every big bosses""" return state.has_all({"Fallen God beated", "Mithalan God beated", "Drunian God beated", - "Sun God beated", "The Golem beated"}, player) + "Sun God beated", "The Golem beated"}, player) -def _has_mini_bosses(state:CollectionState, player: int) -> bool: +def _has_mini_bosses(state: CollectionState, player: int) -> bool: """`player` in `state` has beated every big bosses""" return state.has_all({"Nautilus Prime beated", "Blaster Peg Prime beated", "Mergog beated", - "Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated", - "Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player) + "Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated", + "Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player) -def _has_secrets(state:CollectionState, player: int) -> bool: - return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"},player) +def _has_secrets(state: CollectionState, player: int) -> bool: + return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"}, player) class AquariaRegions: @@ -134,6 +149,7 @@ class AquariaRegions: skeleton_path: Region skeleton_path_sc: Region arnassi: Region + arnassi_cave_transturtle: Region arnassi_path: Region arnassi_crab_boss: Region simon: Region @@ -152,6 +168,7 @@ class AquariaRegions: forest_tr: Region forest_tr_fp: Region forest_bl: Region + forest_bl_sc: Region forest_br: Region forest_boss: Region forest_boss_entrance: Region @@ -179,6 +196,7 @@ class AquariaRegions: abyss_l: Region abyss_lb: Region abyss_r: Region + abyss_r_transturtle: Region ice_cave: Region bubble_cave: Region bubble_cave_boss: Region @@ -213,7 +231,7 @@ class AquariaRegions: """ def __add_region(self, hint: str, - locations: Optional[Dict[str, Optional[int]]]) -> Region: + locations: Optional[Dict[str, int]]) -> Region: """ Create a new Region, add it to the `world` regions and return it. Be aware that this function have a side effect on ``world`.`regions` @@ -236,7 +254,7 @@ def __create_home_water_area(self) -> None: self.home_water_nautilus = self.__add_region("Home Water, Nautilus nest", AquariaLocations.locations_home_water_nautilus) self.home_water_transturtle = self.__add_region("Home Water, turtle room", - AquariaLocations.locations_home_water_transturtle) + AquariaLocations.locations_home_water_transturtle) self.naija_home = self.__add_region("Naija's Home", AquariaLocations.locations_naija_home) self.song_cave = self.__add_region("Song Cave", AquariaLocations.locations_song_cave) @@ -280,6 +298,8 @@ def __create_openwater(self) -> None: self.arnassi = self.__add_region("Arnassi Ruins", AquariaLocations.locations_arnassi) self.arnassi_path = self.__add_region("Arnassi Ruins, back entrance path", AquariaLocations.locations_arnassi_path) + self.arnassi_cave_transturtle = self.__add_region("Arnassi Ruins, transturtle area", + AquariaLocations.locations_arnassi_cave_transturtle) self.arnassi_crab_boss = self.__add_region("Arnassi Ruins, Crabbius Maximus lair", AquariaLocations.locations_arnassi_crab_boss) @@ -302,9 +322,9 @@ def __create_mithalas(self) -> None: AquariaLocations.locations_cathedral_r) self.cathedral_underground = self.__add_region("Mithalas Cathedral underground", AquariaLocations.locations_cathedral_underground) - self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room", + self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room", None) + self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room", AquariaLocations.locations_cathedral_boss) - self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room", None) def __create_forest(self) -> None: """ @@ -320,6 +340,8 @@ def __create_forest(self) -> None: AquariaLocations.locations_forest_tr_fp) self.forest_bl = self.__add_region("Kelp Forest bottom left area", AquariaLocations.locations_forest_bl) + self.forest_bl_sc = self.__add_region("Kelp Forest bottom left area, spirit crystals", + AquariaLocations.locations_forest_bl_sc) self.forest_br = self.__add_region("Kelp Forest bottom right area", AquariaLocations.locations_forest_br) self.forest_sprite_cave = self.__add_region("Kelp Forest spirit cave", @@ -375,9 +397,9 @@ def __create_sun_temple(self) -> None: self.sun_temple_r = self.__add_region("Sun Temple right area", AquariaLocations.locations_sun_temple_r) self.sun_temple_boss_path = self.__add_region("Sun Temple before boss area", - AquariaLocations.locations_sun_temple_boss_path) + AquariaLocations.locations_sun_temple_boss_path) self.sun_temple_boss = self.__add_region("Sun Temple boss area", - AquariaLocations.locations_sun_temple_boss) + AquariaLocations.locations_sun_temple_boss) def __create_abyss(self) -> None: """ @@ -388,6 +410,8 @@ def __create_abyss(self) -> None: AquariaLocations.locations_abyss_l) self.abyss_lb = self.__add_region("Abyss left bottom area", AquariaLocations.locations_abyss_lb) self.abyss_r = self.__add_region("Abyss right area", AquariaLocations.locations_abyss_r) + self.abyss_r_transturtle = self.__add_region("Abyss right area, transturtle", + AquariaLocations.locations_abyss_r_transturtle) self.ice_cave = self.__add_region("Ice Cave", AquariaLocations.locations_ice_cave) self.bubble_cave = self.__add_region("Bubble Cave", AquariaLocations.locations_bubble_cave) self.bubble_cave_boss = self.__add_region("Bubble Cave boss area", AquariaLocations.locations_bubble_cave_boss) @@ -407,7 +431,7 @@ def __create_sunken_city(self) -> None: self.sunken_city_r = self.__add_region("Sunken City right area", AquariaLocations.locations_sunken_city_r) self.sunken_city_boss = self.__add_region("Sunken City boss area", - AquariaLocations.locations_sunken_city_boss) + AquariaLocations.locations_sunken_city_boss) def __create_body(self) -> None: """ @@ -427,7 +451,7 @@ def __create_body(self) -> None: self.final_boss_tube = self.__add_region("The Body, final boss area turtle room", AquariaLocations.locations_final_boss_tube) self.final_boss = self.__add_region("The Body, final boss", - AquariaLocations.locations_final_boss) + AquariaLocations.locations_final_boss) self.final_boss_end = self.__add_region("The Body, final boss area", None) def __connect_one_way_regions(self, source_name: str, destination_name: str, @@ -455,8 +479,8 @@ def __connect_home_water_regions(self) -> None: """ Connect entrances of the different regions around `home_water` """ - self.__connect_regions("Menu", "Verse Cave right area", - self.menu, self.verse_cave_r) + self.__connect_one_way_regions("Menu", "Verse Cave right area", + self.menu, self.verse_cave_r) self.__connect_regions("Verse Cave left area", "Verse Cave right area", self.verse_cave_l, self.verse_cave_r) self.__connect_regions("Verse Cave", "Home Water", self.verse_cave_l, self.home_water) @@ -464,7 +488,8 @@ def __connect_home_water_regions(self) -> None: self.__connect_regions("Home Water", "Song Cave", self.home_water, self.song_cave) self.__connect_regions("Home Water", "Home Water, nautilus nest", self.home_water, self.home_water_nautilus, - lambda state: _has_energy_form(state, self.player) and _has_bind_song(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player) and + _has_bind_song(state, self.player)) self.__connect_regions("Home Water", "Home Water transturtle room", self.home_water, self.home_water_transturtle) self.__connect_regions("Home Water", "Energy Temple first area", @@ -472,7 +497,7 @@ def __connect_home_water_regions(self) -> None: lambda state: _has_bind_song(state, self.player)) self.__connect_regions("Home Water", "Energy Temple_altar", self.home_water, self.energy_temple_altar, - lambda state: _has_energy_form(state, self.player) and + lambda state: _has_energy_attack_item(state, self.player) and _has_bind_song(state, self.player)) self.__connect_regions("Energy Temple first area", "Energy Temple second area", self.energy_temple_1, self.energy_temple_2, @@ -482,28 +507,28 @@ def __connect_home_water_regions(self) -> None: lambda state: _has_fish_form(state, self.player)) self.__connect_regions("Energy Temple idol room", "Energy Temple boss area", self.energy_temple_idol, self.energy_temple_boss, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player) and + _has_fish_form(state, self.player)) self.__connect_one_way_regions("Energy Temple first area", "Energy Temple boss area", self.energy_temple_1, self.energy_temple_boss, lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_one_way_regions("Energy Temple boss area", "Energy Temple first area", self.energy_temple_boss, self.energy_temple_1, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) self.__connect_regions("Energy Temple second area", "Energy Temple third area", self.energy_temple_2, self.energy_temple_3, - lambda state: _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + lambda state: _has_energy_form(state, self.player)) self.__connect_regions("Energy Temple boss area", "Energy Temple blaster room", self.energy_temple_boss, self.energy_temple_blaster_room, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_regions("Energy Temple first area", "Energy Temple blaster room", self.energy_temple_1, self.energy_temple_blaster_room, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player) and + _has_energy_attack_item(state, self.player) and _has_beast_form(state, self.player)) self.__connect_regions("Home Water", "Open Water top left area", self.home_water, self.openwater_tl) @@ -520,7 +545,7 @@ def __connect_open_water_regions(self) -> None: self.openwater_tl, self.forest_br) self.__connect_regions("Open Water top right area", "Open Water top right area, turtle room", self.openwater_tr, self.openwater_tr_turtle, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) self.__connect_regions("Open Water top right area", "Open Water bottom right area", self.openwater_tr, self.openwater_br) self.__connect_regions("Open Water top right area", "Mithalas City", @@ -529,10 +554,9 @@ def __connect_open_water_regions(self) -> None: self.openwater_tr, self.veil_bl) self.__connect_one_way_regions("Open Water top right area", "Veil bottom right", self.openwater_tr, self.veil_br, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) self.__connect_one_way_regions("Veil bottom right", "Open Water top right area", - self.veil_br, self.openwater_tr, - lambda state: _has_beast_form(state, self.player)) + self.veil_br, self.openwater_tr) self.__connect_regions("Open Water bottom left area", "Open Water bottom right area", self.openwater_bl, self.openwater_br) self.__connect_regions("Open Water bottom left area", "Skeleton path", @@ -551,10 +575,14 @@ def __connect_open_water_regions(self) -> None: self.arnassi, self.openwater_br) self.__connect_regions("Arnassi", "Arnassi path", self.arnassi, self.arnassi_path) + self.__connect_regions("Arnassi ruins, transturtle area", "Arnassi path", + self.arnassi_cave_transturtle, self.arnassi_path, + lambda state: _has_fish_form(state, self.player)) self.__connect_one_way_regions("Arnassi path", "Arnassi crab boss area", self.arnassi_path, self.arnassi_crab_boss, - lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player) and + (_has_energy_attack_item(state, self.player) or + _has_nature_form(state, self.player))) self.__connect_one_way_regions("Arnassi crab boss area", "Arnassi path", self.arnassi_crab_boss, self.arnassi_path) @@ -564,61 +592,62 @@ def __connect_mithalas_regions(self) -> None: """ self.__connect_one_way_regions("Mithalas City", "Mithalas City top path", self.mithalas_city, self.mithalas_city_top_path, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) self.__connect_one_way_regions("Mithalas City_top_path", "Mithalas City", self.mithalas_city_top_path, self.mithalas_city) self.__connect_regions("Mithalas City", "Mithalas City home with fishpass", self.mithalas_city, self.mithalas_city_fishpass, lambda state: _has_fish_form(state, self.player)) self.__connect_regions("Mithalas City", "Mithalas castle", - self.mithalas_city, self.cathedral_l, - lambda state: _has_fish_form(state, self.player)) + self.mithalas_city, self.cathedral_l) self.__connect_one_way_regions("Mithalas City top path", "Mithalas castle, flower tube", self.mithalas_city_top_path, self.cathedral_l_tube, lambda state: _has_nature_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_one_way_regions("Mithalas castle, flower tube area", "Mithalas City top path", self.cathedral_l_tube, self.mithalas_city_top_path, - lambda state: _has_beast_form(state, self.player) and - _has_nature_form(state, self.player)) + lambda state: _has_nature_form(state, self.player)) self.__connect_one_way_regions("Mithalas castle flower tube area", "Mithalas castle, spirit crystals", - self.cathedral_l_tube, self.cathedral_l_sc, - lambda state: _has_spirit_form(state, self.player)) + self.cathedral_l_tube, self.cathedral_l_sc, + lambda state: _has_spirit_form(state, self.player)) self.__connect_one_way_regions("Mithalas castle_flower tube area", "Mithalas castle", - self.cathedral_l_tube, self.cathedral_l, - lambda state: _has_spirit_form(state, self.player)) + self.cathedral_l_tube, self.cathedral_l, + lambda state: _has_spirit_form(state, self.player)) self.__connect_regions("Mithalas castle", "Mithalas castle, spirit crystals", self.cathedral_l, self.cathedral_l_sc, lambda state: _has_spirit_form(state, self.player)) - self.__connect_regions("Mithalas castle", "Cathedral boss left area", - self.cathedral_l, self.cathedral_boss_l, - lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player) and - _has_bind_song(state, self.player)) + self.__connect_one_way_regions("Mithalas castle", "Cathedral boss right area", + self.cathedral_l, self.cathedral_boss_r, + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Cathedral boss left area", "Mithalas castle", + self.cathedral_boss_l, self.cathedral_l, + lambda state: _has_beast_form(state, self.player)) self.__connect_regions("Mithalas castle", "Mithalas Cathedral underground", self.cathedral_l, self.cathedral_underground, - lambda state: _has_beast_form(state, self.player) and - _has_bind_song(state, self.player)) - self.__connect_regions("Mithalas castle", "Mithalas Cathedral", - self.cathedral_l, self.cathedral_r, - lambda state: _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) - self.__connect_regions("Mithalas Cathedral", "Mithalas Cathedral underground", - self.cathedral_r, self.cathedral_underground, - lambda state: _has_energy_form(state, self.player)) - self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss left area", - self.cathedral_underground, self.cathedral_boss_r, - lambda state: _has_energy_form(state, self.player) and - _has_bind_song(state, self.player)) - self.__connect_one_way_regions("Cathedral boss left area", "Mithalas Cathedral underground", + lambda state: _has_beast_form(state, self.player)) + self.__connect_one_way_regions("Mithalas castle", "Mithalas Cathedral", + self.cathedral_l, self.cathedral_r, + lambda state: _has_bind_song(state, self.player) and + _has_energy_attack_item(state, self.player)) + self.__connect_one_way_regions("Mithalas Cathedral", "Mithalas Cathedral underground", + self.cathedral_r, self.cathedral_underground) + self.__connect_one_way_regions("Mithalas Cathedral underground", "Mithalas Cathedral", + self.cathedral_underground, self.cathedral_r, + lambda state: _has_beast_form(state, self.player) and + _has_energy_attack_item(state, self.player)) + self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss right area", + self.cathedral_underground, self.cathedral_boss_r) + self.__connect_one_way_regions("Cathedral boss right area", "Mithalas Cathedral underground", self.cathedral_boss_r, self.cathedral_underground, lambda state: _has_beast_form(state, self.player)) - self.__connect_regions("Cathedral boss right area", "Cathedral boss left area", + self.__connect_one_way_regions("Cathedral boss right area", "Cathedral boss left area", self.cathedral_boss_r, self.cathedral_boss_l, lambda state: _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) + self.__connect_one_way_regions("Cathedral boss left area", "Cathedral boss right area", + self.cathedral_boss_l, self.cathedral_boss_r) def __connect_forest_regions(self) -> None: """ @@ -628,6 +657,12 @@ def __connect_forest_regions(self) -> None: self.forest_br, self.veil_bl) self.__connect_regions("Forest bottom right", "Forest bottom left area", self.forest_br, self.forest_bl) + self.__connect_one_way_regions("Forest bottom left area", "Forest bottom left area, spirit crystals", + self.forest_bl, self.forest_bl_sc, + lambda state: _has_energy_attack_item(state, self.player) or + _has_fish_form(state, self.player)) + self.__connect_one_way_regions("Forest bottom left area, spirit crystals", "Forest bottom left area", + self.forest_bl_sc, self.forest_bl) self.__connect_regions("Forest bottom right", "Forest top right area", self.forest_br, self.forest_tr) self.__connect_regions("Forest bottom left area", "Forest fish cave", @@ -641,7 +676,7 @@ def __connect_forest_regions(self) -> None: self.forest_tl, self.forest_tl_fp, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player) and + _has_energy_attack_item(state, self.player) and _has_fish_form(state, self.player)) self.__connect_regions("Forest top left area", "Forest top right area", self.forest_tl, self.forest_tr) @@ -649,7 +684,7 @@ def __connect_forest_regions(self) -> None: self.forest_tl, self.forest_boss_entrance) self.__connect_regions("Forest boss area", "Forest boss entrance", self.forest_boss, self.forest_boss_entrance, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) self.__connect_regions("Forest top right area", "Forest top right area fish pass", self.forest_tr, self.forest_tr_fp, lambda state: _has_fish_form(state, self.player)) @@ -663,7 +698,7 @@ def __connect_forest_regions(self) -> None: self.__connect_regions("Fermog cave", "Fermog boss", self.mermog_cave, self.mermog_boss, lambda state: _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) def __connect_veil_regions(self) -> None: """ @@ -681,8 +716,7 @@ def __connect_veil_regions(self) -> None: self.veil_b_sc, self.veil_br, lambda state: _has_spirit_form(state, self.player)) self.__connect_regions("Veil bottom right", "Veil top left area", - self.veil_br, self.veil_tl, - lambda state: _has_beast_form(state, self.player)) + self.veil_br, self.veil_tl) self.__connect_regions("Veil top left area", "Veil_top left area, fish pass", self.veil_tl, self.veil_tl_fp, lambda state: _has_fish_form(state, self.player)) @@ -691,20 +725,25 @@ def __connect_veil_regions(self) -> None: self.__connect_regions("Veil top left area", "Turtle cave", self.veil_tl, self.turtle_cave) self.__connect_regions("Turtle cave", "Turtle cave Bubble Cliff", - self.turtle_cave, self.turtle_cave_bubble, - lambda state: _has_beast_form(state, self.player)) + self.turtle_cave, self.turtle_cave_bubble) self.__connect_regions("Veil right of sun temple", "Sun Temple right area", self.veil_tr_r, self.sun_temple_r) - self.__connect_regions("Sun Temple right area", "Sun Temple left area", - self.sun_temple_r, self.sun_temple_l, - lambda state: _has_bind_song(state, self.player)) + self.__connect_one_way_regions("Sun Temple right area", "Sun Temple left area", + self.sun_temple_r, self.sun_temple_l, + lambda state: _has_bind_song(state, self.player) or + _has_light(state, self.player)) + self.__connect_one_way_regions("Sun Temple left area", "Sun Temple right area", + self.sun_temple_l, self.sun_temple_r, + lambda state: _has_light(state, self.player)) self.__connect_regions("Sun Temple left area", "Veil left of sun temple", self.sun_temple_l, self.veil_tr_l) self.__connect_regions("Sun Temple left area", "Sun Temple before boss area", - self.sun_temple_l, self.sun_temple_boss_path) + self.sun_temple_l, self.sun_temple_boss_path, + lambda state: _has_light(state, self.player) or + _has_sun_crystal(state, self.player)) self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area", self.sun_temple_boss_path, self.sun_temple_boss, - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) self.__connect_one_way_regions("Sun Temple boss area", "Veil left of sun temple", self.sun_temple_boss, self.veil_tr_l) self.__connect_regions("Veil left of sun temple", "Octo cave top path", @@ -712,7 +751,7 @@ def __connect_veil_regions(self) -> None: lambda state: _has_fish_form(state, self.player) and _has_sun_form(state, self.player) and _has_beast_form(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_regions("Veil left of sun temple", "Octo cave bottom path", self.veil_tr_l, self.octo_cave_b, lambda state: _has_fish_form(state, self.player)) @@ -728,16 +767,22 @@ def __connect_abyss_regions(self) -> None: self.abyss_lb, self.sunken_city_r, lambda state: _has_li(state, self.player)) self.__connect_one_way_regions("Abyss left bottom area", "Body center area", - self.abyss_lb, self.body_c, - lambda state: _has_tongue_cleared(state, self.player)) + self.abyss_lb, self.body_c, + lambda state: _has_tongue_cleared(state, self.player)) self.__connect_one_way_regions("Body center area", "Abyss left bottom area", - self.body_c, self.abyss_lb) + self.body_c, self.abyss_lb) self.__connect_regions("Abyss left area", "King jellyfish cave", self.abyss_l, self.king_jellyfish_cave, - lambda state: _has_energy_form(state, self.player) and - _has_beast_form(state, self.player)) + lambda state: (_has_energy_form(state, self.player) and + _has_beast_form(state, self.player)) or + _has_dual_form(state, self.player)) self.__connect_regions("Abyss left area", "Abyss right area", self.abyss_l, self.abyss_r) + self.__connect_one_way_regions("Abyss right area", "Abyss right area, transturtle", + self.abyss_r, self.abyss_r_transturtle) + self.__connect_one_way_regions("Abyss right area, transturtle", "Abyss right area", + self.abyss_r_transturtle, self.abyss_r, + lambda state: _has_light(state, self.player)) self.__connect_regions("Abyss right area", "Inside the whale", self.abyss_r, self.whale, lambda state: _has_spirit_form(state, self.player) and @@ -747,13 +792,14 @@ def __connect_abyss_regions(self) -> None: lambda state: _has_spirit_form(state, self.player) and _has_sun_form(state, self.player) and _has_bind_song(state, self.player) and - _has_energy_form(state, self.player)) + _has_energy_attack_item(state, self.player)) self.__connect_regions("Abyss right area", "Ice Cave", self.abyss_r, self.ice_cave, lambda state: _has_spirit_form(state, self.player)) - self.__connect_regions("Abyss right area", "Bubble Cave", + self.__connect_regions("Ice cave", "Bubble Cave", self.ice_cave, self.bubble_cave, - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form(state, self.player) or + _has_hot_soup(state, self.player)) self.__connect_regions("Bubble Cave boss area", "Bubble Cave", self.bubble_cave, self.bubble_cave_boss, lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player) @@ -772,7 +818,7 @@ def __connect_sunken_city_regions(self) -> None: self.sunken_city_l, self.sunken_city_boss, lambda state: _has_beast_form(state, self.player) and _has_sun_form(state, self.player) and - _has_energy_form(state, self.player) and + _has_energy_attack_item(state, self.player) and _has_bind_song(state, self.player)) def __connect_body_regions(self) -> None: @@ -780,11 +826,13 @@ def __connect_body_regions(self) -> None: Connect entrances of the different regions around The Body """ self.__connect_regions("Body center area", "Body left area", - self.body_c, self.body_l) + self.body_c, self.body_l, + lambda state: _has_energy_form(state, self.player)) self.__connect_regions("Body center area", "Body right area top path", self.body_c, self.body_rt) self.__connect_regions("Body center area", "Body right area bottom path", - self.body_c, self.body_rb) + self.body_c, self.body_rb, + lambda state: _has_energy_form(state, self.player)) self.__connect_regions("Body center area", "Body bottom area", self.body_c, self.body_b, lambda state: _has_dual_form(state, self.player)) @@ -803,22 +851,12 @@ def __connect_body_regions(self) -> None: self.__connect_one_way_regions("final boss third form area", "final boss end", self.final_boss, self.final_boss_end) - def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region, region_target: Region, - rule=None) -> None: + def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region, + region_target: Region) -> None: """Connect a single transturtle to another one""" if item_source != item_target: - if rule is None: - self.__connect_one_way_regions(item_source, item_target, region_source, region_target, - lambda state: state.has(item_target, self.player)) - else: - self.__connect_one_way_regions(item_source, item_target, region_source, region_target, rule) - - def __connect_arnassi_path_transturtle(self, item_source: str, item_target: str, region_source: Region, - region_target: Region) -> None: - """Connect the Arnassi Ruins transturtle to another one""" - self.__connect_one_way_regions(item_source, item_target, region_source, region_target, - lambda state: state.has(item_target, self.player) and - _has_fish_form(state, self.player)) + self.__connect_one_way_regions(item_source, item_target, region_source, region_target, + lambda state: state.has(item_target, self.player)) def _connect_transturtle_to_other(self, item: str, region: Region) -> None: """Connect a single transturtle to all others""" @@ -827,24 +865,10 @@ def _connect_transturtle_to_other(self, item: str, region: Region) -> None: self.__connect_transturtle(item, "Transturtle Open Water top right", region, self.openwater_tr_turtle) self.__connect_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl) self.__connect_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle) - self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r) + self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r_transturtle) self.__connect_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube) self.__connect_transturtle(item, "Transturtle Simon Says", region, self.simon) - self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_path, - lambda state: state.has("Transturtle Arnassi Ruins", self.player) and - _has_fish_form(state, self.player)) - - def _connect_arnassi_path_transturtle_to_other(self, item: str, region: Region) -> None: - """Connect the Arnassi Ruins transturtle to all others""" - self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top left", region, self.veil_tl) - self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l) - self.__connect_arnassi_path_transturtle(item, "Transturtle Open Water top right", region, - self.openwater_tr_turtle) - self.__connect_arnassi_path_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl) - self.__connect_arnassi_path_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle) - self.__connect_arnassi_path_transturtle(item, "Transturtle Abyss right", region, self.abyss_r) - self.__connect_arnassi_path_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube) - self.__connect_arnassi_path_transturtle(item, "Transturtle Simon Says", region, self.simon) + self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_cave_transturtle) def __connect_transturtles(self) -> None: """Connect every transturtle with others""" @@ -853,10 +877,10 @@ def __connect_transturtles(self) -> None: self._connect_transturtle_to_other("Transturtle Open Water top right", self.openwater_tr_turtle) self._connect_transturtle_to_other("Transturtle Forest bottom left", self.forest_bl) self._connect_transturtle_to_other("Transturtle Home Water", self.home_water_transturtle) - self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r) + self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r_transturtle) self._connect_transturtle_to_other("Transturtle Final Boss", self.final_boss_tube) self._connect_transturtle_to_other("Transturtle Simon Says", self.simon) - self._connect_arnassi_path_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_path) + self._connect_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_cave_transturtle) def connect_regions(self) -> None: """ @@ -893,7 +917,7 @@ def __add_event_big_bosses(self) -> None: self.__add_event_location(self.energy_temple_boss, "Beating Fallen God", "Fallen God beated") - self.__add_event_location(self.cathedral_boss_r, + self.__add_event_location(self.cathedral_boss_l, "Beating Mithalan God", "Mithalan God beated") self.__add_event_location(self.forest_boss, @@ -970,8 +994,9 @@ def __adjusting_urns_rules(self) -> None: """Since Urns need to be broken, add a damaging item to rules""" add_rule(self.multiworld.get_location("Open Water top right area, first urn in the Mithalas exit", self.player), lambda state: _has_damaging_item(state, self.player)) - add_rule(self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player), - lambda state: _has_damaging_item(state, self.player)) + add_rule( + self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player), + lambda state: _has_damaging_item(state, self.player)) add_rule(self.multiworld.get_location("Open Water top right area, third urn in the Mithalas exit", self.player), lambda state: _has_damaging_item(state, self.player)) add_rule(self.multiworld.get_location("Mithalas City, first urn in one of the homes", self.player), @@ -1019,66 +1044,46 @@ def __adjusting_soup_rules(self) -> None: Modify rules for location that need soup """ add_rule(self.multiworld.get_location("Turtle cave, Urchin Costume", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) - add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) - add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + lambda state: _has_hot_soup(state, self.player)) add_rule(self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", self.player), - lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player)) + lambda state: _has_beast_and_soup_form(state, self.player)) def __adjusting_under_rock_location(self) -> None: """ Modify rules implying bind song needed for bulb under rocks """ add_rule(self.multiworld.get_location("Home Water, bulb under the rock in the left path from the Verse Cave", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Verse Cave left area, bulb under the rock at the end of the path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Naija's Home, bulb under the rock at the right of the main path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Song Cave, bulb under the rock in the path to the singing statues", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Song Cave, bulb under the rock close to the song door", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Energy Temple second area, bulb under the rock", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the left path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Kelp Forest top right area, bulb under the rock in the right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Abyss right area, bulb in the middle path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path", - self.player), lambda state: _has_bind_song(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) def __adjusting_light_in_dark_place_rules(self) -> None: add_rule(self.multiworld.get_location("Kelp Forest top right area, Black Pearl", self.player), lambda state: _has_light(state, self.player)) add_rule(self.multiworld.get_location("Kelp Forest bottom right area, Odd Container", self.player), lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Veil top left to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Open Water top right to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Veil top right to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Forest bottom left to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Home Water to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Final Boss to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Simon Says to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) - add_rule(self.multiworld.get_entrance("Transturtle Arnassi Ruins to Transturtle Abyss right", self.player), - lambda state: _has_light(state, self.player)) add_rule(self.multiworld.get_entrance("Body center area to Abyss left bottom area", self.player), lambda state: _has_light(state, self.player)) add_rule(self.multiworld.get_entrance("Veil left of sun temple to Octo cave top path", self.player), @@ -1097,12 +1102,14 @@ def __adjusting_light_in_dark_place_rules(self) -> None: def __adjusting_manual_rules(self) -> None: add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player), lambda state: _has_beast_form(state, self.player)) - add_rule(self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player), - lambda state: _has_fish_form(state, self.player)) + add_rule( + self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player), + lambda state: _has_fish_form(state, self.player)) add_rule(self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", self.player), lambda state: _has_spirit_form(state, self.player)) - add_rule(self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player), - lambda state: _has_bind_song(state, self.player)) + add_rule( + self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player), + lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Turtle cave, Turtle Egg", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Abyss left area, bulb in the bottom fish pass", self.player), @@ -1114,103 +1121,119 @@ def __adjusting_manual_rules(self) -> None: add_rule(self.multiworld.get_location("Verse Cave right area, Big Seed", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Arnassi Ruins, Song Plant Spore", self.player), - lambda state: _has_beast_form(state, self.player)) + lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) add_rule(self.multiworld.get_location("Energy Temple first area, bulb in the bottom room blocked by a rock", - self.player), lambda state: _has_energy_form(state, self.player)) + self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Home Water, bulb in the bottom left room", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Home Water, bulb in the path below Nautilus Prime", self.player), lambda state: _has_bind_song(state, self.player)) add_rule(self.multiworld.get_location("Naija's Home, bulb after the energy door", self.player), - lambda state: _has_energy_form(state, self.player)) + lambda state: _has_energy_attack_item(state, self.player)) add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", self.player), lambda state: _has_spirit_form(state, self.player) and _has_sun_form(state, self.player)) add_rule(self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", self.player), - lambda state: _has_fish_form(state, self.player) and - _has_spirit_form(state, self.player)) + lambda state: _has_fish_form(state, self.player) or + _has_beast_and_soup_form(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, urn inside a home fish pass", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location("Mithalas City, urn in the Castle flower tube entrance", self.player), + lambda state: _has_damaging_item(state, self.player)) + add_rule(self.multiworld.get_location( + "The Veil top right area, bulb in the middle of the wall jump cliff", self.player + ), lambda state: _has_beast_form_or_arnassi_armor(state, self.player)) + add_rule(self.multiworld.get_location("Kelp Forest top left area, Jelly Egg", self.player), + lambda state: _has_beast_form(state, self.player)) + add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player), + lambda state: state.has("Sun God beated", self.player)) + add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player), + lambda state: state.has("Sun God beated", self.player)) + add_rule(self.multiworld.get_location("The Body center area, breaking Li's cage", self.player), + lambda state: _has_tongue_cleared(state, self.player)) def __no_progression_hard_or_hidden_location(self) -> None: self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Mithalas boss area, beating Mithalan God", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Kelp Forest boss area, beating Drunian God", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Temple boss area, beating Sun God", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sunken City, bulb on top of the boss area", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Home Water, Nautilus Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Energy Temple blaster room, Blaster Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Mithalas City Castle, beating the Priests", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Mermog cave, Piranha Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Octopus Cave, Dumbo Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("King Jellyfish Cave, bulb in the right path from King Jelly", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("King Jellyfish Cave, Jellyfish Costume", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Final Boss area, bulb in the boss third form room", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Worm path, first cliff bulb", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Worm path, second cliff bulb", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Bubble Cave, bulb in the left cave wall", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Bubble Cave, bulb in the right cave wall (behind the ice crystal)", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Bubble Cave, Verse Egg", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Kelp Forest bottom left area, bulb close to the spirit crystals", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Temple, Sun Key", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("The Body bottom area, Mutant Costume", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Sun Temple, bulb in the hidden room of the right part", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", - self.player).item_rule =\ + self.player).item_rule = \ lambda item: item.classification != ItemClassification.progression def adjusting_rules(self, options: AquariaOptions) -> None: """ Modify rules for single location or optional rules """ + self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player) self.__adjusting_urns_rules() self.__adjusting_crates_rules() self.__adjusting_soup_rules() @@ -1234,7 +1257,7 @@ def adjusting_rules(self, options: AquariaOptions) -> None: lambda state: _has_bind_song(state, self.player)) if options.unconfine_home_water.value in [0, 2]: add_rule(self.multiworld.get_entrance("Home Water to Open Water top left area", self.player), - lambda state: _has_bind_song(state, self.player) and _has_energy_form(state, self.player)) + lambda state: _has_bind_song(state, self.player) and _has_energy_attack_item(state, self.player)) if options.early_energy_form: self.multiworld.early_items[self.player]["Energy form"] = 1 @@ -1274,6 +1297,7 @@ def __add_open_water_regions_to_world(self) -> None: self.multiworld.regions.append(self.arnassi) self.multiworld.regions.append(self.arnassi_path) self.multiworld.regions.append(self.arnassi_crab_boss) + self.multiworld.regions.append(self.arnassi_cave_transturtle) self.multiworld.regions.append(self.simon) def __add_mithalas_regions_to_world(self) -> None: @@ -1300,6 +1324,7 @@ def __add_forest_regions_to_world(self) -> None: self.multiworld.regions.append(self.forest_tr) self.multiworld.regions.append(self.forest_tr_fp) self.multiworld.regions.append(self.forest_bl) + self.multiworld.regions.append(self.forest_bl_sc) self.multiworld.regions.append(self.forest_br) self.multiworld.regions.append(self.forest_boss) self.multiworld.regions.append(self.forest_boss_entrance) @@ -1337,6 +1362,7 @@ def __add_abyss_regions_to_world(self) -> None: self.multiworld.regions.append(self.abyss_l) self.multiworld.regions.append(self.abyss_lb) self.multiworld.regions.append(self.abyss_r) + self.multiworld.regions.append(self.abyss_r_transturtle) self.multiworld.regions.append(self.ice_cave) self.multiworld.regions.append(self.bubble_cave) self.multiworld.regions.append(self.bubble_cave_boss) diff --git a/worlds/aquaria/test/__init__.py b/worlds/aquaria/test/__init__.py index 029db691b66..8c4f64c3452 100644 --- a/worlds/aquaria/test/__init__.py +++ b/worlds/aquaria/test/__init__.py @@ -141,7 +141,7 @@ "Sun Temple, bulb at the top of the high dark room", "Sun Temple, Golden Gear", "Sun Temple, first bulb of the temple", - "Sun Temple, bulb on the left part", + "Sun Temple, bulb on the right part", "Sun Temple, bulb in the hidden room of the right part", "Sun Temple, Sun Key", "Sun Worm path, first path bulb", diff --git a/worlds/aquaria/test/test_beast_form_access.py b/worlds/aquaria/test/test_beast_form_access.py index 0efc3e7388f..c09586269d3 100644 --- a/worlds/aquaria/test/test_beast_form_access.py +++ b/worlds/aquaria/test/test_beast_form_access.py @@ -13,36 +13,16 @@ class BeastFormAccessTest(AquariaTestBase): def test_beast_form_location(self) -> None: """Test locations that require beast form""" locations = [ - "Mithalas City Castle, beating the Priests", - "Arnassi Ruins, Crab Armor", - "Arnassi Ruins, Song Plant Spore", - "Mithalas City, first bulb at the end of the top path", - "Mithalas City, second bulb at the end of the top path", - "Mithalas City, bulb in the top path", - "Mithalas City, Mithalas Pot", - "Mithalas City, urn in the Castle flower tube entrance", "Mermog cave, Piranha Egg", + "Kelp Forest top left area, Jelly Egg", "Mithalas Cathedral, Mithalan Dress", - "Turtle cave, bulb in Bubble Cliff", - "Turtle cave, Urchin Costume", - "Sun Worm path, first cliff bulb", - "Sun Worm path, second cliff bulb", "The Veil top right area, bulb at the top of the waterfall", - "Bubble Cave, bulb in the left cave wall", - "Bubble Cave, bulb in the right cave wall (behind the ice crystal)", - "Bubble Cave, Verse Egg", "Sunken City, bulb on top of the boss area", "Octopus Cave, Dumbo Egg", "Beating the Golem", "Beating Mergog", - "Beating Crabbius Maximus", "Beating Octopus Prime", - "Beating Mantis Shrimp Prime", - "King Jellyfish Cave, Jellyfish Costume", - "King Jellyfish Cave, bulb in the right path from King Jelly", - "Beating King Jellyfish God Prime", - "Beating Mithalan priests", - "Sunken City cleared" + "Sunken City cleared", ] items = [["Beast form"]] self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py b/worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py new file mode 100644 index 00000000000..fa4c6923400 --- /dev/null +++ b/worlds/aquaria/test/test_beast_form_or_arnassi_armor_access.py @@ -0,0 +1,39 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the beast form or arnassi armor +""" + +from . import AquariaTestBase + + +class BeastForArnassiArmormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the beast form or arnassi armor""" + + def test_beast_form_arnassi_armor_location(self) -> None: + """Test locations that require beast form or arnassi armor""" + locations = [ + "Mithalas City Castle, beating the Priests", + "Arnassi Ruins, Crab Armor", + "Arnassi Ruins, Song Plant Spore", + "Mithalas City, first bulb at the end of the top path", + "Mithalas City, second bulb at the end of the top path", + "Mithalas City, bulb in the top path", + "Mithalas City, Mithalas Pot", + "Mithalas City, urn in the Castle flower tube entrance", + "Mermog cave, Piranha Egg", + "Mithalas Cathedral, Mithalan Dress", + "Kelp Forest top left area, Jelly Egg", + "The Veil top right area, bulb in the middle of the wall jump cliff", + "The Veil top right area, bulb at the top of the waterfall", + "Sunken City, bulb on top of the boss area", + "Octopus Cave, Dumbo Egg", + "Beating the Golem", + "Beating Mergog", + "Beating Crabbius Maximus", + "Beating Octopus Prime", + "Beating Mithalan priests", + "Sunken City cleared" + ] + items = [["Beast form", "Arnassi Armor"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_energy_form_access.py b/worlds/aquaria/test/test_energy_form_access.py index 82d8e89a006..b443166823b 100644 --- a/worlds/aquaria/test/test_energy_form_access.py +++ b/worlds/aquaria/test/test_energy_form_access.py @@ -17,55 +17,16 @@ class EnergyFormAccessTest(AquariaTestBase): def test_energy_form_location(self) -> None: """Test locations that require Energy form""" locations = [ - "Home Water, Nautilus Egg", - "Naija's Home, bulb after the energy door", - "Energy Temple first area, bulb in the bottom room blocked by a rock", "Energy Temple second area, bulb under the rock", - "Energy Temple bottom entrance, Krotite Armor", "Energy Temple third area, bulb in the bottom path", - "Energy Temple boss area, Fallen God Tooth", - "Energy Temple blaster room, Blaster Egg", - "Mithalas City Castle, beating the Priests", - "Mithalas Cathedral, first urn in the top right room", - "Mithalas Cathedral, second urn in the top right room", - "Mithalas Cathedral, third urn in the top right room", - "Mithalas Cathedral, urn in the flesh room with fleas", - "Mithalas Cathedral, first urn in the bottom right path", - "Mithalas Cathedral, second urn in the bottom right path", - "Mithalas Cathedral, urn behind the flesh vein", - "Mithalas Cathedral, urn in the top left eyes boss room", - "Mithalas Cathedral, first urn in the path behind the flesh vein", - "Mithalas Cathedral, second urn in the path behind the flesh vein", - "Mithalas Cathedral, third urn in the path behind the flesh vein", - "Mithalas Cathedral, fourth urn in the top right room", - "Mithalas Cathedral, Mithalan Dress", - "Mithalas Cathedral, urn below the left entrance", - "Mithalas boss area, beating Mithalan God", - "Kelp Forest top left area, bulb close to the Verse Egg", - "Kelp Forest top left area, Verse Egg", - "Kelp Forest boss area, beating Drunian God", - "Mermog cave, Piranha Egg", - "Octopus Cave, Dumbo Egg", - "Sun Temple boss area, beating Sun God", - "Arnassi Ruins, Crab Armor", - "King Jellyfish Cave, bulb in the right path from King Jelly", - "King Jellyfish Cave, Jellyfish Costume", - "Sunken City, bulb on top of the boss area", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", "Final Boss area, bulb in the boss third form room", - "Beating Fallen God", - "Beating Mithalan God", - "Beating Drunian God", - "Beating Sun God", - "Beating the Golem", - "Beating Nautilus Prime", - "Beating Blaster Peg Prime", - "Beating Mergog", - "Beating Mithalan priests", - "Beating Octopus Prime", - "Beating Crabbius Maximus", - "Beating King Jellyfish God Prime", - "First secret", - "Sunken City cleared", "Objective complete", ] items = [["Energy form"]] diff --git a/worlds/aquaria/test/test_energy_form_or_dual_form_access.py b/worlds/aquaria/test/test_energy_form_or_dual_form_access.py new file mode 100644 index 00000000000..8a765bc4e4e --- /dev/null +++ b/worlds/aquaria/test/test_energy_form_or_dual_form_access.py @@ -0,0 +1,92 @@ +""" +Author: Louis M +Date: Thu, 18 Apr 2024 18:45:56 +0000 +Description: Unit test used to test accessibility of locations with and without the energy form and dual form (and Li) +""" + +from . import AquariaTestBase + + +class EnergyFormDualFormAccessTest(AquariaTestBase): + """Unit test used to test accessibility of locations with and without the energy form and dual form (and Li)""" + options = { + "early_energy_form": False, + } + + def test_energy_form_or_dual_form_location(self) -> None: + """Test locations that require Energy form or dual form""" + locations = [ + "Naija's Home, bulb after the energy door", + "Home Water, Nautilus Egg", + "Energy Temple second area, bulb under the rock", + "Energy Temple bottom entrance, Krotite Armor", + "Energy Temple third area, bulb in the bottom path", + "Energy Temple blaster room, Blaster Egg", + "Energy Temple boss area, Fallen God Tooth", + "Mithalas City Castle, beating the Priests", + "Mithalas boss area, beating Mithalan God", + "Mithalas Cathedral, first urn in the top right room", + "Mithalas Cathedral, second urn in the top right room", + "Mithalas Cathedral, third urn in the top right room", + "Mithalas Cathedral, urn in the flesh room with fleas", + "Mithalas Cathedral, first urn in the bottom right path", + "Mithalas Cathedral, second urn in the bottom right path", + "Mithalas Cathedral, urn behind the flesh vein", + "Mithalas Cathedral, urn in the top left eyes boss room", + "Mithalas Cathedral, first urn in the path behind the flesh vein", + "Mithalas Cathedral, second urn in the path behind the flesh vein", + "Mithalas Cathedral, third urn in the path behind the flesh vein", + "Mithalas Cathedral, fourth urn in the top right room", + "Mithalas Cathedral, Mithalan Dress", + "Mithalas Cathedral, urn below the left entrance", + "Kelp Forest top left area, bulb close to the Verse Egg", + "Kelp Forest top left area, Verse Egg", + "Kelp Forest boss area, beating Drunian God", + "Mermog cave, Piranha Egg", + "Octopus Cave, Dumbo Egg", + "Sun Temple boss area, beating Sun God", + "King Jellyfish Cave, bulb in the right path from King Jelly", + "King Jellyfish Cave, Jellyfish Costume", + "Sunken City right area, crate close to the save crystal", + "Sunken City right area, crate in the left bottom room", + "Sunken City left area, crate in the little pipe room", + "Sunken City left area, crate close to the save crystal", + "Sunken City left area, crate before the bedroom", + "Sunken City left area, Girl Costume", + "Sunken City, bulb on top of the boss area", + "The Body center area, breaking Li's cage", + "The Body center area, bulb on the main path blocking tube", + "The Body left area, first bulb in the top face room", + "The Body left area, second bulb in the top face room", + "The Body left area, bulb below the water stream", + "The Body left area, bulb in the top path to the top face room", + "The Body left area, bulb in the bottom face room", + "The Body right area, bulb in the top face room", + "The Body right area, bulb in the top path to the bottom face room", + "The Body right area, bulb in the bottom face room", + "The Body bottom area, bulb in the Jelly Zap room", + "The Body bottom area, bulb in the nautilus room", + "The Body bottom area, Mutant Costume", + "Final Boss area, bulb in the boss third form room", + "Final Boss area, first bulb in the turtle room", + "Final Boss area, second bulb in the turtle room", + "Final Boss area, third bulb in the turtle room", + "Final Boss area, Transturtle", + "Beating Fallen God", + "Beating Blaster Peg Prime", + "Beating Mithalan God", + "Beating Drunian God", + "Beating Sun God", + "Beating the Golem", + "Beating Nautilus Prime", + "Beating Mergog", + "Beating Mithalan priests", + "Beating Octopus Prime", + "Beating King Jellyfish God Prime", + "Beating the Golem", + "Sunken City cleared", + "First secret", + "Objective complete" + ] + items = [["Energy form", "Dual form", "Li and Li song", "Body tongue cleared"]] + self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_fish_form_access.py b/worlds/aquaria/test/test_fish_form_access.py index c98a53e9243..40b15a87cd3 100644 --- a/worlds/aquaria/test/test_fish_form_access.py +++ b/worlds/aquaria/test/test_fish_form_access.py @@ -17,6 +17,7 @@ def test_fish_form_location(self) -> None: """Test locations that require fish form""" locations = [ "The Veil top left area, bulb inside the fish pass", + "Energy Temple first area, Energy Idol", "Mithalas City, Doll", "Mithalas City, urn inside a home fish pass", "Kelp Forest top right area, bulb in the top fish pass", @@ -30,8 +31,7 @@ def test_fish_form_location(self) -> None: "Octopus Cave, Dumbo Egg", "Octopus Cave, bulb in the path below the Octopus Cave path", "Beating Octopus Prime", - "Abyss left area, bulb in the bottom fish pass", - "Arnassi Ruins, Arnassi Armor" + "Abyss left area, bulb in the bottom fish pass" ] items = [["Fish form"]] self.assertAccessDependency(locations, items) diff --git a/worlds/aquaria/test/test_light_access.py b/worlds/aquaria/test/test_light_access.py index b5d7cf99fea..29d37d790b2 100644 --- a/worlds/aquaria/test/test_light_access.py +++ b/worlds/aquaria/test/test_light_access.py @@ -39,7 +39,6 @@ def test_light_location(self) -> None: "Abyss right area, bulb in the middle path", "Abyss right area, bulb behind the rock in the middle path", "Abyss right area, bulb in the left green room", - "Abyss right area, Transturtle", "Ice Cave, bulb in the room to the right", "Ice Cave, first bulb in the top exit room", "Ice Cave, second bulb in the top exit room", diff --git a/worlds/aquaria/test/test_spirit_form_access.py b/worlds/aquaria/test/test_spirit_form_access.py index 3bcbd7d72e0..7e31de9905e 100644 --- a/worlds/aquaria/test/test_spirit_form_access.py +++ b/worlds/aquaria/test/test_spirit_form_access.py @@ -30,7 +30,6 @@ def test_spirit_form_location(self) -> None: "Sunken City left area, Girl Costume", "Beating Mantis Shrimp Prime", "First secret", - "Arnassi Ruins, Arnassi Armor", ] items = [["Spirit form"]] self.assertAccessDependency(locations, items) diff --git a/worlds/bomb_rush_cyberfunk/Locations.py b/worlds/bomb_rush_cyberfunk/Locations.py index 863e2ad020c..7ea95901906 100644 --- a/worlds/bomb_rush_cyberfunk/Locations.py +++ b/worlds/bomb_rush_cyberfunk/Locations.py @@ -762,7 +762,7 @@ class EventDict(TypedDict): 'game_id': "graf385"}, {'name': "Tagged 389 Graffiti Spots", 'stage': Stages.Misc, - 'game_id': "graf379"}, + 'game_id': "graf389"}, ] diff --git a/worlds/bomb_rush_cyberfunk/Rules.py b/worlds/bomb_rush_cyberfunk/Rules.py index f59a4285709..8f283ee613b 100644 --- a/worlds/bomb_rush_cyberfunk/Rules.py +++ b/worlds/bomb_rush_cyberfunk/Rules.py @@ -1006,6 +1006,8 @@ def rules(brcworld): lambda state: mataan_challenge2(state, player, limit, glitched)) set_rule(multiworld.get_location("Mataan: Score challenge reward", player), lambda state: mataan_challenge3(state, player)) + set_rule(multiworld.get_location("Mataan: Coil joins the crew", player), + lambda state: mataan_deepest(state, player, limit, glitched)) if photos: set_rule(multiworld.get_location("Mataan: Trash Polo", player), lambda state: camera(state, player)) diff --git a/worlds/checksfinder/Items.py b/worlds/checksfinder/Items.py index 2e86267396f..5f9be79598a 100644 --- a/worlds/checksfinder/Items.py +++ b/worlds/checksfinder/Items.py @@ -3,8 +3,8 @@ class ItemData(typing.NamedTuple): - code: typing.Optional[int] - progression: bool + code: int + progression: bool = True class ChecksFinderItem(Item): @@ -12,16 +12,9 @@ class ChecksFinderItem(Item): item_table = { - "Map Width": ItemData(80000, True), - "Map Height": ItemData(80001, True), - "Map Bombs": ItemData(80002, True), + "Map Width": ItemData(80000), + "Map Height": ItemData(80001), + "Map Bombs": ItemData(80002), } -required_items = { -} - -item_frequencies = { - -} - -lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items()} diff --git a/worlds/checksfinder/Locations.py b/worlds/checksfinder/Locations.py index 59a96c83ea8..aefdc383810 100644 --- a/worlds/checksfinder/Locations.py +++ b/worlds/checksfinder/Locations.py @@ -3,46 +3,14 @@ class AdvData(typing.NamedTuple): - id: typing.Optional[int] - region: str + id: int + region: str = "Board" -class ChecksFinderAdvancement(Location): +class ChecksFinderLocation(Location): game: str = "ChecksFinder" -advancement_table = { - "Tile 1": AdvData(81000, 'Board'), - "Tile 2": AdvData(81001, 'Board'), - "Tile 3": AdvData(81002, 'Board'), - "Tile 4": AdvData(81003, 'Board'), - "Tile 5": AdvData(81004, 'Board'), - "Tile 6": AdvData(81005, 'Board'), - "Tile 7": AdvData(81006, 'Board'), - "Tile 8": AdvData(81007, 'Board'), - "Tile 9": AdvData(81008, 'Board'), - "Tile 10": AdvData(81009, 'Board'), - "Tile 11": AdvData(81010, 'Board'), - "Tile 12": AdvData(81011, 'Board'), - "Tile 13": AdvData(81012, 'Board'), - "Tile 14": AdvData(81013, 'Board'), - "Tile 15": AdvData(81014, 'Board'), - "Tile 16": AdvData(81015, 'Board'), - "Tile 17": AdvData(81016, 'Board'), - "Tile 18": AdvData(81017, 'Board'), - "Tile 19": AdvData(81018, 'Board'), - "Tile 20": AdvData(81019, 'Board'), - "Tile 21": AdvData(81020, 'Board'), - "Tile 22": AdvData(81021, 'Board'), - "Tile 23": AdvData(81022, 'Board'), - "Tile 24": AdvData(81023, 'Board'), - "Tile 25": AdvData(81024, 'Board'), -} - -exclusion_table = { -} - -events_table = { -} - -lookup_id_to_name: typing.Dict[int, str] = {data.id: item_name for item_name, data in advancement_table.items() if data.id} \ No newline at end of file +base_id = 81000 +advancement_table = {f"Tile {i+1}": AdvData(base_id+i) for i in range(25)} +lookup_id_to_name: typing.Dict[int, str] = {data.id: item_name for item_name, data in advancement_table.items()} diff --git a/worlds/checksfinder/Options.py b/worlds/checksfinder/Options.py deleted file mode 100644 index a670109362f..00000000000 --- a/worlds/checksfinder/Options.py +++ /dev/null @@ -1,6 +0,0 @@ -import typing -from Options import Option - - -checksfinder_options: typing.Dict[str, type(Option)] = { -} diff --git a/worlds/checksfinder/Rules.py b/worlds/checksfinder/Rules.py index 38d7d77ad39..8e8809be5c1 100644 --- a/worlds/checksfinder/Rules.py +++ b/worlds/checksfinder/Rules.py @@ -1,44 +1,24 @@ -from ..generic.Rules import set_rule -from BaseClasses import MultiWorld, CollectionState +from worlds.generic.Rules import set_rule +from BaseClasses import MultiWorld -def _has_total(state: CollectionState, player: int, total: int): - return (state.count('Map Width', player) + state.count('Map Height', player) + - state.count('Map Bombs', player)) >= total +items = ["Map Width", "Map Height", "Map Bombs"] # Sets rules on entrances and advancements that are always applied -def set_rules(world: MultiWorld, player: int): - set_rule(world.get_location("Tile 6", player), lambda state: _has_total(state, player, 1)) - set_rule(world.get_location("Tile 7", player), lambda state: _has_total(state, player, 2)) - set_rule(world.get_location("Tile 8", player), lambda state: _has_total(state, player, 3)) - set_rule(world.get_location("Tile 9", player), lambda state: _has_total(state, player, 4)) - set_rule(world.get_location("Tile 10", player), lambda state: _has_total(state, player, 5)) - set_rule(world.get_location("Tile 11", player), lambda state: _has_total(state, player, 6)) - set_rule(world.get_location("Tile 12", player), lambda state: _has_total(state, player, 7)) - set_rule(world.get_location("Tile 13", player), lambda state: _has_total(state, player, 8)) - set_rule(world.get_location("Tile 14", player), lambda state: _has_total(state, player, 9)) - set_rule(world.get_location("Tile 15", player), lambda state: _has_total(state, player, 10)) - set_rule(world.get_location("Tile 16", player), lambda state: _has_total(state, player, 11)) - set_rule(world.get_location("Tile 17", player), lambda state: _has_total(state, player, 12)) - set_rule(world.get_location("Tile 18", player), lambda state: _has_total(state, player, 13)) - set_rule(world.get_location("Tile 19", player), lambda state: _has_total(state, player, 14)) - set_rule(world.get_location("Tile 20", player), lambda state: _has_total(state, player, 15)) - set_rule(world.get_location("Tile 21", player), lambda state: _has_total(state, player, 16)) - set_rule(world.get_location("Tile 22", player), lambda state: _has_total(state, player, 17)) - set_rule(world.get_location("Tile 23", player), lambda state: _has_total(state, player, 18)) - set_rule(world.get_location("Tile 24", player), lambda state: _has_total(state, player, 19)) - set_rule(world.get_location("Tile 25", player), lambda state: _has_total(state, player, 20)) +def set_rules(multiworld: MultiWorld, player: int): + for i in range(20): + set_rule(multiworld.get_location(f"Tile {i+6}", player), lambda state, i=i: state.has_from_list(items, player, i+1)) # Sets rules on completion condition -def set_completion_rules(world: MultiWorld, player: int): - - width_req = 10-5 - height_req = 10-5 - bomb_req = 20-5 - completion_requirements = lambda state: \ - state.has("Map Width", player, width_req) and \ - state.has("Map Height", player, height_req) and \ - state.has("Map Bombs", player, bomb_req) - world.completion_condition[player] = lambda state: completion_requirements(state) +def set_completion_rules(multiworld: MultiWorld, player: int): + width_req = 5 # 10 - 5 + height_req = 5 # 10 - 5 + bomb_req = 15 # 20 - 5 + multiworld.completion_condition[player] = lambda state: state.has_all_counts( + { + "Map Width": width_req, + "Map Height": height_req, + "Map Bombs": bomb_req, + }, player) diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py index c8b9587f850..e064a1c4194 100644 --- a/worlds/checksfinder/__init__.py +++ b/worlds/checksfinder/__init__.py @@ -1,9 +1,9 @@ -from BaseClasses import Region, Entrance, Item, Tutorial, ItemClassification -from .Items import ChecksFinderItem, item_table, required_items -from .Locations import ChecksFinderAdvancement, advancement_table, exclusion_table -from .Options import checksfinder_options +from BaseClasses import Region, Entrance, Tutorial, ItemClassification +from .Items import ChecksFinderItem, item_table +from .Locations import ChecksFinderLocation, advancement_table +from Options import PerGameCommonOptions from .Rules import set_rules, set_completion_rules -from ..AutoWorld import World, WebWorld +from worlds.AutoWorld import World, WebWorld client_version = 7 @@ -25,38 +25,34 @@ class ChecksFinderWorld(World): ChecksFinder is a game where you avoid mines and find checks inside the board with the mines! You win when you get all your items and beat the board! """ - game: str = "ChecksFinder" - option_definitions = checksfinder_options - topology_present = True + game = "ChecksFinder" + options_dataclass = PerGameCommonOptions web = ChecksFinderWeb() item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {name: data.id for name, data in advancement_table.items()} - def _get_checksfinder_data(self): - return { - 'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32), - 'seed_name': self.multiworld.seed_name, - 'player_name': self.multiworld.get_player_name(self.player), - 'player_id': self.player, - 'client_version': client_version, - 'race': self.multiworld.is_race, - } + def create_regions(self): + menu = Region("Menu", self.player, self.multiworld) + board = Region("Board", self.player, self.multiworld) + board.locations += [ChecksFinderLocation(self.player, loc_name, loc_data.id, board) + for loc_name, loc_data in advancement_table.items()] - def create_items(self): + connection = Entrance(self.player, "New Board", menu) + menu.exits.append(connection) + connection.connect(board) + self.multiworld.regions += [menu, board] + def create_items(self): # Generate item pool itempool = [] - # Add all required progression items - for (name, num) in required_items.items(): - itempool += [name] * num # Add the map width and height stuff - itempool += ["Map Width"] * (10-5) - itempool += ["Map Height"] * (10-5) + itempool += ["Map Width"] * 5 # 10 - 5 + itempool += ["Map Height"] * 5 # 10 - 5 # Add the map bombs - itempool += ["Map Bombs"] * (20-5) + itempool += ["Map Bombs"] * 15 # 20 - 5 # Convert itempool into real items - itempool = [item for item in map(lambda name: self.create_item(name), itempool)] + itempool = [self.create_item(item) for item in itempool] self.multiworld.itempool += itempool @@ -64,28 +60,16 @@ def set_rules(self): set_rules(self.multiworld, self.player) set_completion_rules(self.multiworld, self.player) - def create_regions(self): - menu = Region("Menu", self.player, self.multiworld) - board = Region("Board", self.player, self.multiworld) - board.locations += [ChecksFinderAdvancement(self.player, loc_name, loc_data.id, board) - for loc_name, loc_data in advancement_table.items() if loc_data.region == board.name] - - connection = Entrance(self.player, "New Board", menu) - menu.exits.append(connection) - connection.connect(board) - self.multiworld.regions += [menu, board] - def fill_slot_data(self): - slot_data = self._get_checksfinder_data() - for option_name in checksfinder_options: - option = getattr(self.multiworld, option_name)[self.player] - if slot_data.get(option_name, None) is None and type(option.value) in {str, int}: - slot_data[option_name] = int(option.value) - return slot_data + return { + "world_seed": self.random.getrandbits(32), + "seed_name": self.multiworld.seed_name, + "player_name": self.player_name, + "player_id": self.player, + "client_version": client_version, + "race": self.multiworld.is_race, + } - def create_item(self, name: str) -> Item: + def create_item(self, name: str) -> ChecksFinderItem: item_data = item_table[name] - item = ChecksFinderItem(name, - ItemClassification.progression if item_data.progression else ItemClassification.filler, - item_data.code, self.player) - return item + return ChecksFinderItem(name, ItemClassification.progression, item_data.code, self.player) diff --git a/worlds/checksfinder/docs/en_ChecksFinder.md b/worlds/checksfinder/docs/en_ChecksFinder.md index c9569376c5f..cb33ab39591 100644 --- a/worlds/checksfinder/docs/en_ChecksFinder.md +++ b/worlds/checksfinder/docs/en_ChecksFinder.md @@ -24,8 +24,3 @@ next to an icon, the number is how many you have gotten and the icon represents Victory is achieved when the player wins a board they were given after they have received all of their Map Width, Map Height, and Map Bomb items. The game will say at the bottom of the screen how many of each you have received. -## Unique Local Commands - -The following command is only available when using the ChecksFinderClient to play with Archipelago. - -- `/resync` Manually trigger a resync. diff --git a/worlds/checksfinder/docs/setup_en.md b/worlds/checksfinder/docs/setup_en.md index 673b34900af..e15763ab311 100644 --- a/worlds/checksfinder/docs/setup_en.md +++ b/worlds/checksfinder/docs/setup_en.md @@ -4,7 +4,6 @@ - ChecksFinder from the [Github releases Page for the game](https://github.com/jonloveslegos/ChecksFinder/releases) (latest version) -- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) ## Configuring your YAML file @@ -17,28 +16,15 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) You can customize your options by visiting the [ChecksFinder Player Options Page](/games/ChecksFinder/player-options) -### Generating a ChecksFinder game +## Joining a MultiWorld Game -**ChecksFinder is meant to be played _alongside_ another game! You may not be playing it for long periods of time if -you play it by itself with another person!** - -When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done, -the host will provide you with either a link to download your data file, or with a zip file containing everyone's data -files. You do not have a file inside that zip though! - -You need to start ChecksFinder client yourself, it is located within the Archipelago folder. - -### Connect to the MultiServer - -First start ChecksFinder. - -Once both ChecksFinder and the client are started. In the client at the top type in the spot labeled `Server` type the -`Ip Address` and `Port` separated with a `:` symbol. - -The client will then ask for the username you chose, input that in the text box at the bottom of the client. - -### Play the game - -When the console tells you that you have joined the room, you're all set. Congratulations on successfully joining a -multiworld game! +1. Start ChecksFinder +2. Enter the following information: + - Enter the server url (starting from `wss://` for https connection like archipelago.gg, and starting from `ws://` for http connection and local multiserver) + - Enter server port + - Enter the name of the slot you wish to connect to + - Enter the room password (optional) + - Press `Play Online` to connect +3. Start playing! +Game options and controls are described in the readme on the github repository for the game diff --git a/worlds/cv64/options.py b/worlds/cv64/options.py index 93b417ad26f..07e86347bda 100644 --- a/worlds/cv64/options.py +++ b/worlds/cv64/options.py @@ -1,5 +1,6 @@ from dataclasses import dataclass -from Options import OptionGroup, Choice, DefaultOnToggle, Range, Toggle, PerGameCommonOptions, StartInventoryPool +from Options import (OptionGroup, Choice, DefaultOnToggle, ItemsAccessibility, PerGameCommonOptions, Range, Toggle, + StartInventoryPool) class CharacterStages(Choice): @@ -521,6 +522,7 @@ class DeathLink(Choice): @dataclass class CV64Options(PerGameCommonOptions): + accessibility: ItemsAccessibility start_inventory_from_pool: StartInventoryPool character_stages: CharacterStages stage_shuffle: StageShuffle diff --git a/worlds/dark_souls_3/Bosses.py b/worlds/dark_souls_3/Bosses.py new file mode 100644 index 00000000000..008a2971320 --- /dev/null +++ b/worlds/dark_souls_3/Bosses.py @@ -0,0 +1,264 @@ +# In almost all cases, we leave boss and enemy randomization up to the static randomizer. But for +# Yhorm specifically we need to know where he ends up in order to ensure that the Storm Ruler is +# available before his fight. + +from dataclasses import dataclass, field +from typing import Set + + +@dataclass +class DS3BossInfo: + """The set of locations a given boss location blocks access to.""" + + name: str + """The boss's name.""" + + id: int + """The game's ID for this particular boss.""" + + dlc: bool = False + """This boss appears in one of the game's DLCs.""" + + before_storm_ruler: bool = False + """Whether this location appears before it's possible to get Storm Ruler in vanilla. + + This is used to determine whether it's safe to place Yhorm here if weapons + aren't randomized. + """ + + locations: Set[str] = field(default_factory=set) + """Additional individual locations that can't be accessed until the boss is dead.""" + + +# Note: the static randomizer splits up some bosses into separate fights for separate phases, each +# of which can be individually replaced by Yhorm. +all_bosses = [ + DS3BossInfo("Iudex Gundyr", 4000800, before_storm_ruler = True, locations = { + "CA: Coiled Sword - boss drop" + }), + DS3BossInfo("Vordt of the Boreal Valley", 3000800, before_storm_ruler = True, locations = { + "HWL: Soul of Boreal Valley Vordt" + }), + DS3BossInfo("Curse-rotted Greatwood", 3100800, locations = { + "US: Soul of the Rotted Greatwood", + "US: Transposing Kiln - boss drop", + "US: Wargod Wooden Shield - Pit of Hollows", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + "FS: Sunset Shield - by grave after killing Hodrick w/Sirris", + "US: Sunset Helm - Pit of Hollows after killing Hodrick w/Sirris", + "US: Sunset Armor - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Gauntlets - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Leggings - pit of hollows after killing Hodrick w/Sirris", + "FS: Sunless Talisman - Sirris, kill GA boss", + "FS: Sunless Veil - shop, Sirris quest, kill GA boss", + "FS: Sunless Armor - shop, Sirris quest, kill GA boss", + "FS: Sunless Gauntlets - shop, Sirris quest, kill GA boss", + "FS: Sunless Leggings - shop, Sirris quest, kill GA boss", + }), + DS3BossInfo("Crystal Sage", 3300850, locations = { + "RS: Soul of a Crystal Sage", + "FS: Sage's Big Hat - shop after killing RS boss", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + }), + DS3BossInfo("Deacons of the Deep", 3500800, locations = { + "CD: Soul of the Deacons of the Deep", + "CD: Small Doll - boss drop", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + }), + DS3BossInfo("Abyss Watchers", 3300801, before_storm_ruler = True, locations = { + "FK: Soul of the Blood of the Wolf", + "FK: Cinders of a Lord - Abyss Watcher", + "FS: Undead Legion Helm - shop after killing FK boss", + "FS: Undead Legion Armor - shop after killing FK boss", + "FS: Undead Legion Gauntlet - shop after killing FK boss", + "FS: Undead Legion Leggings - shop after killing FK boss", + "FS: Farron Ring - Hawkwood", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + }), + DS3BossInfo("High Lord Wolnir", 3800800, before_storm_ruler = True, locations = { + "CC: Soul of High Lord Wolnir", + "FS: Wolnir's Crown - shop after killing CC boss", + "CC: Homeward Bone - Irithyll bridge", + "CC: Pontiff's Right Eye - Irithyll bridge, miniboss drop", + }), + DS3BossInfo("Pontiff Sulyvahn", 3700850, locations = { + "IBV: Soul of Pontiff Sulyvahn", + }), + DS3BossInfo("Old Demon King", 3800830, locations = { + "SL: Soul of the Old Demon King", + }), + DS3BossInfo("Aldrich, Devourer of Gods", 3700800, locations = { + "AL: Soul of Aldrich", + "AL: Cinders of a Lord - Aldrich", + "FS: Smough's Helm - shop after killing AL boss", + "FS: Smough's Armor - shop after killing AL boss", + "FS: Smough's Gauntlets - shop after killing AL boss", + "FS: Smough's Leggings - shop after killing AL boss", + "AL: Sun Princess Ring - dark cathedral, after boss", + "FS: Leonhard's Garb - shop after killing Leonhard", + "FS: Leonhard's Gauntlets - shop after killing Leonhard", + "FS: Leonhard's Trousers - shop after killing Leonhard", + }), + DS3BossInfo("Dancer of the Boreal Valley", 3000899, locations = { + "HWL: Soul of the Dancer", + "FS: Dancer's Crown - shop after killing LC entry boss", + "FS: Dancer's Armor - shop after killing LC entry boss", + "FS: Dancer's Gauntlets - shop after killing LC entry boss", + "FS: Dancer's Leggings - shop after killing LC entry boss", + }), + DS3BossInfo("Dragonslayer Armour", 3010800, locations = { + "LC: Soul of Dragonslayer Armour", + "FS: Morne's Helm - shop after killing Eygon or LC boss", + "FS: Morne's Armor - shop after killing Eygon or LC boss", + "FS: Morne's Gauntlets - shop after killing Eygon or LC boss", + "FS: Morne's Leggings - shop after killing Eygon or LC boss", + "LC: Titanite Chunk - down stairs after boss", + }), + DS3BossInfo("Consumed King Oceiros", 3000830, locations = { + "CKG: Soul of Consumed Oceiros", + "CKG: Titanite Scale - tomb, chest #1", + "CKG: Titanite Scale - tomb, chest #2", + "CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC", + }), + DS3BossInfo("Champion Gundyr", 4000830, locations = { + "UG: Soul of Champion Gundyr", + "FS: Gundyr's Helm - shop after killing UG boss", + "FS: Gundyr's Armor - shop after killing UG boss", + "FS: Gundyr's Gauntlets - shop after killing UG boss", + "FS: Gundyr's Leggings - shop after killing UG boss", + "UG: Hornet Ring - environs, right of main path after killing FK boss", + "UG: Chaos Blade - environs, left of shrine", + "UG: Blacksmith Hammer - shrine, Andre's room", + "UG: Eyes of a Fire Keeper - shrine, Irina's room", + "UG: Coiled Sword Fragment - shrine, dead bonfire", + "UG: Soul of a Crestfallen Knight - environs, above shrine entrance", + "UG: Life Ring+3 - shrine, behind big throne", + "UG: Ring of Steel Protection+1 - environs, behind bell tower", + "FS: Ring of Sacrifice - Yuria shop", + "UG: Ember - shop", + "UG: Priestess Ring - shop", + "UG: Wolf Knight Helm - shop after killing FK boss", + "UG: Wolf Knight Armor - shop after killing FK boss", + "UG: Wolf Knight Gauntlets - shop after killing FK boss", + "UG: Wolf Knight Leggings - shop after killing FK boss", + }), + DS3BossInfo("Ancient Wyvern", 3200800), + DS3BossInfo("King of the Storm", 3200850, locations = { + "AP: Soul of the Nameless King", + "FS: Golden Crown - shop after killing AP boss", + "FS: Dragonscale Armor - shop after killing AP boss", + "FS: Golden Bracelets - shop after killing AP boss", + "FS: Dragonscale Waistcloth - shop after killing AP boss", + "AP: Titanite Slab - plaza", + "AP: Covetous Gold Serpent Ring+2 - plaza", + "AP: Dragonslayer Helm - plaza", + "AP: Dragonslayer Armor - plaza", + "AP: Dragonslayer Gauntlets - plaza", + "AP: Dragonslayer Leggings - plaza", + }), + DS3BossInfo("Nameless King", 3200851, locations = { + "AP: Soul of the Nameless King", + "FS: Golden Crown - shop after killing AP boss", + "FS: Dragonscale Armor - shop after killing AP boss", + "FS: Golden Bracelets - shop after killing AP boss", + "FS: Dragonscale Waistcloth - shop after killing AP boss", + "AP: Titanite Slab - plaza", + "AP: Covetous Gold Serpent Ring+2 - plaza", + "AP: Dragonslayer Helm - plaza", + "AP: Dragonslayer Armor - plaza", + "AP: Dragonslayer Gauntlets - plaza", + "AP: Dragonslayer Leggings - plaza", + }), + DS3BossInfo("Lothric, Younger Prince", 3410830, locations = { + "GA: Soul of the Twin Princes", + "GA: Cinders of a Lord - Lothric Prince", + }), + DS3BossInfo("Lorian, Elder Prince", 3410832, locations = { + "GA: Soul of the Twin Princes", + "GA: Cinders of a Lord - Lothric Prince", + "FS: Lorian's Helm - shop after killing GA boss", + "FS: Lorian's Armor - shop after killing GA boss", + "FS: Lorian's Gauntlets - shop after killing GA boss", + "FS: Lorian's Leggings - shop after killing GA boss", + }), + DS3BossInfo("Champion's Gravetender and Gravetender Greatwolf", 4500860, dlc = True, + locations = {"PW1: Valorheart - boss drop"}), + DS3BossInfo("Sister Friede", 4500801, dlc = True, locations = { + "PW2: Soul of Sister Friede", + "PW2: Titanite Slab - boss drop", + "PW1: Titanite Slab - Corvian", + "FS: Ordained Hood - shop after killing PW2 boss", + "FS: Ordained Dress - shop after killing PW2 boss", + "FS: Ordained Trousers - shop after killing PW2 boss", + }), + DS3BossInfo("Blackflame Friede", 4500800, dlc = True, locations = { + "PW2: Soul of Sister Friede", + "PW1: Titanite Slab - Corvian", + "FS: Ordained Hood - shop after killing PW2 boss", + "FS: Ordained Dress - shop after killing PW2 boss", + "FS: Ordained Trousers - shop after killing PW2 boss", + }), + DS3BossInfo("Demon Prince", 5000801, dlc = True, locations = { + "DH: Soul of the Demon Prince", + "DH: Small Envoy Banner - boss drop", + }), + DS3BossInfo("Halflight, Spear of the Church", 5100800, dlc = True, locations = { + "RC: Titanite Slab - mid boss drop", + "RC: Titanite Slab - ashes, NPC drop", + "RC: Titanite Slab - ashes, mob drop", + "RC: Filianore's Spear Ornament - mid boss drop", + "RC: Crucifix of the Mad King - ashes, NPC drop", + "RC: Shira's Crown - Shira's room after killing ashes NPC", + "RC: Shira's Armor - Shira's room after killing ashes NPC", + "RC: Shira's Gloves - Shira's room after killing ashes NPC", + "RC: Shira's Trousers - Shira's room after killing ashes NPC", + }), + DS3BossInfo("Darkeater Midir", 5100850, dlc = True, locations = { + "RC: Soul of Darkeater Midir", + "RC: Spears of the Church - hidden boss drop", + }), + DS3BossInfo("Slave Knight Gael 1", 5110801, dlc = True, locations = { + "RC: Soul of Slave Knight Gael", + "RC: Blood of the Dark Soul - end boss drop", + # These are accessible before you trigger the boss, but once you do you + # have to beat it before getting them. + "RC: Titanite Slab - ashes, mob drop", + "RC: Titanite Slab - ashes, NPC drop", + "RC: Sacred Chime of Filianore - ashes, NPC drop", + "RC: Crucifix of the Mad King - ashes, NPC drop", + "RC: Shira's Crown - Shira's room after killing ashes NPC", + "RC: Shira's Armor - Shira's room after killing ashes NPC", + "RC: Shira's Gloves - Shira's room after killing ashes NPC", + "RC: Shira's Trousers - Shira's room after killing ashes NPC", + }), + DS3BossInfo("Slave Knight Gael 2", 5110800, dlc = True, locations = { + "RC: Soul of Slave Knight Gael", + "RC: Blood of the Dark Soul - end boss drop", + # These are accessible before you trigger the boss, but once you do you + # have to beat it before getting them. + "RC: Titanite Slab - ashes, mob drop", + "RC: Titanite Slab - ashes, NPC drop", + "RC: Sacred Chime of Filianore - ashes, NPC drop", + "RC: Crucifix of the Mad King - ashes, NPC drop", + "RC: Shira's Crown - Shira's room after killing ashes NPC", + "RC: Shira's Armor - Shira's room after killing ashes NPC", + "RC: Shira's Gloves - Shira's room after killing ashes NPC", + "RC: Shira's Trousers - Shira's room after killing ashes NPC", + }), + DS3BossInfo("Lords of Cinder", 4100800, locations = { + "KFF: Soul of the Lords", + "FS: Billed Mask - Yuria after killing KFF boss", + "FS: Black Dress - Yuria after killing KFF boss", + "FS: Black Gauntlets - Yuria after killing KFF boss", + "FS: Black Leggings - Yuria after killing KFF boss" + }), +] + +default_yhorm_location = DS3BossInfo("Yhorm the Giant", 3900800, locations = { + "PC: Soul of Yhorm the Giant", + "PC: Cinders of a Lord - Yhorm the Giant", + "PC: Siegbräu - Siegward after killing boss", +}) diff --git a/worlds/dark_souls_3/Items.py b/worlds/dark_souls_3/Items.py index 3dd5cb2d3c3..19cd79a9941 100644 --- a/worlds/dark_souls_3/Items.py +++ b/worlds/dark_souls_3/Items.py @@ -1,7 +1,9 @@ +from dataclasses import dataclass +import dataclasses from enum import IntEnum -from typing import NamedTuple +from typing import Any, cast, ClassVar, Dict, Generator, List, Optional, Set -from BaseClasses import Item +from BaseClasses import Item, ItemClassification class DS3ItemCategory(IntEnum): @@ -14,1267 +16,1677 @@ class DS3ItemCategory(IntEnum): RING = 6 SPELL = 7 MISC = 8 - KEY = 9 + UNIQUE = 9 BOSS = 10 - SKIP = 11 + SOUL = 11 + UPGRADE = 12 + HEALING = 13 + @property + def is_infusible(self) -> bool: + """Returns whether this category can be infused.""" + return self in [ + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, + DS3ItemCategory.SHIELD_INFUSIBLE + ] + + @property + def upgrade_level(self) -> Optional[int]: + """The maximum upgrade level for this category, or None if it's not upgradable.""" + if self == DS3ItemCategory.WEAPON_UPGRADE_5: return 5 + if self in [ + DS3ItemCategory.WEAPON_UPGRADE_10, + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE + ]: return 10 + return None + + +@dataclass +class Infusion(IntEnum): + """Infusions supported by Dark Souls III. + + The value of each infusion is the number added to the base weapon's ID to get the infused ID. + """ + + HEAVY = 100 + SHARP = 200 + REFINED = 300 + SIMPLE = 400 + CRYSTAL = 500 + FIRE = 600 + CHAOS = 700 + LIGHTNING = 800 + DEEP = 900 + DARK = 1000 + POISON = 1100 + BLOOD = 1200 + RAW = 1300 + BLESSED = 1400 + HOLLOW = 1500 + + @property + def prefix(self): + """The prefix to add to a weapon name with this infusion.""" + return self.name.title() + + +class UsefulIf(IntEnum): + """An enum that indicates when an item should be upgraded to ItemClassification.useful. + + This is used for rings with +x variants that may or may not be the best in class depending on + the player's settings. + """ + + DEFAULT = 0 + """Follows DS3ItemData.classification as written.""" + + BASE = 1 + """Useful only if the DLC and NG+ locations are disabled.""" + + NO_DLC = 2 + """Useful if the DLC is disabled, whether or not NG+ locations are.""" + + NO_NGP = 3 + """Useful if NG+ locations is disabled, whether or not the DLC is.""" + + +@dataclass +class DS3ItemData: + __item_id: ClassVar[int] = 100000 + """The next item ID to use when creating item data.""" -class DS3ItemData(NamedTuple): name: str - ds3_code: int - is_dlc: bool + ds3_code: Optional[int] category: DS3ItemCategory + base_ds3_code: Optional[int] = None + """If this is an upgradable weapon, the base ID of the weapon it upgrades from. + + Otherwise, or if the weapon isn't upgraded, this is the same as ds3_code. + """ + + base_name: Optional[str] = None + """The name of the individual item, if this is a multi-item group.""" + + classification: ItemClassification = ItemClassification.filler + """How important this item is to the game progression.""" + + ap_code: Optional[int] = None + """The Archipelago ID for this item.""" + + is_dlc: bool = False + """Whether this item is only found in one of the two DLC packs.""" + + count: int = 1 + """The number of copies of this item included in each drop.""" + + inject: bool = False + """If this is set, the randomizer will try to inject this item into the game. + + This is used for items such as covenant rewards that aren't realistically reachable in a + randomizer run, but are still fun to have available to the player. If there are more locations + available than there are items in the item pool, these items will be used to help make up the + difference. + """ + + souls: Optional[int] = None + """If this is a consumable item that gives souls, the number of souls it gives.""" + + useful_if: UsefulIf = UsefulIf.DEFAULT + """Whether and when this item should be marked as "useful".""" + + filler: bool = False + """Whether this is a candidate for a filler item to be added to fill out extra locations.""" + + skip: bool = False + """Whether to omit this item from randomization and replace it with filler or unique items.""" + + @property + def unique(self): + """Whether this item should be unique, appearing only once in the randomizer.""" + return self.category not in { + DS3ItemCategory.MISC, DS3ItemCategory.SOUL, DS3ItemCategory.UPGRADE, + DS3ItemCategory.HEALING, + } + + def __post_init__(self): + self.ap_code = self.ap_code or DS3ItemData.__item_id + if not self.base_name: self.base_name = self.name + if not self.base_ds3_code: self.base_ds3_code = self.ds3_code + DS3ItemData.__item_id += 1 + + def item_groups(self) -> List[str]: + """The names of item groups this item should appear in. + + This is computed from the properties assigned to this item.""" + names = [] + if self.classification == ItemClassification.progression: names.append("Progression") + if self.name.startswith("Cinders of a Lord -"): names.append("Cinders") + + names.append({ + DS3ItemCategory.WEAPON_UPGRADE_5: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE: "Weapons", + DS3ItemCategory.SHIELD: "Shields", + DS3ItemCategory.SHIELD_INFUSIBLE: "Shields", + DS3ItemCategory.ARMOR: "Armor", + DS3ItemCategory.RING: "Rings", + DS3ItemCategory.SPELL: "Spells", + DS3ItemCategory.MISC: "Miscellaneous", + DS3ItemCategory.UNIQUE: "Unique", + DS3ItemCategory.BOSS: "Boss Souls", + DS3ItemCategory.SOUL: "Small Souls", + DS3ItemCategory.UPGRADE: "Upgrade", + DS3ItemCategory.HEALING: "Healing", + }[self.category]) + + return names + + def counts(self, counts: List[int]) -> Generator["DS3ItemData", None, None]: + """Returns an iterable of copies of this item with the given counts.""" + yield self + for count in counts: + yield dataclasses.replace( + self, + ap_code = None, + name = "{} x{}".format(self.base_name, count), + base_name = self.base_name, + count = count, + filler = False, # Don't count multiples as filler by default + ) + + @property + def is_infused(self) -> bool: + """Returns whether this item is an infused weapon.""" + return cast(int, self.ds3_code) - cast(int, self.base_ds3_code) >= 100 + + def infuse(self, infusion: Infusion) -> "DS3ItemData": + """Returns this item with the given infusion applied.""" + if not self.category.is_infusible: raise RuntimeError(f"{self.name} is not infusible.") + if self.is_infused: + raise RuntimeError(f"{self.name} is already infused.") + + # We can't change the name or AP code when infusing/upgrading weapons, because they both + # need to match what's in item_name_to_id. We don't want to add every possible + # infusion/upgrade combination to that map because it's way too many items. + return dataclasses.replace( + self, + name = self.name, + ds3_code = cast(int, self.ds3_code) + infusion.value, + filler = False, + ) + + @property + def is_upgraded(self) -> bool: + """Returns whether this item is a weapon that's upgraded beyond level 0.""" + return (cast(int, self.ds3_code) - cast(int, self.base_ds3_code)) % 100 != 0 + + def upgrade(self, level: int) -> "DS3ItemData": + """Upgrades this item to the given level.""" + if not self.category.upgrade_level: raise RuntimeError(f"{self.name} is not upgradable.") + if level > self.category.upgrade_level: + raise RuntimeError(f"{self.name} can't be upgraded to +{level}.") + if self.is_upgraded: + raise RuntimeError(f"{self.name} is already upgraded.") + + # We can't change the name or AP code when infusing/upgrading weapons, because they both + # need to match what's in item_name_to_id. We don't want to add every possible + # infusion/upgrade combination to that map because it's way too many items. + return dataclasses.replace( + self, + name = self.name, + ds3_code = cast(int, self.ds3_code) + level, + filler = False, + ) + + def __hash__(self) -> int: + return (self.name, self.ds3_code).__hash__() + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return self.name == other.name and self.ds3_code == other.ds3_code + else: + return False + class DarkSouls3Item(Item): game: str = "Dark Souls III" + data: DS3ItemData + + @property + def level(self) -> Optional[int]: + """This item's upgrade level, if it's a weapon.""" + return cast(int, self.data.ds3_code) % 100 if self.data.category.upgrade_level else None + + def __init__( + self, + player: int, + data: DS3ItemData, + classification = None): + super().__init__(data.name, classification or data.classification, data.ap_code, player) + self.data = data @staticmethod - def get_name_to_id() -> dict: - base_id = 100000 - return {item_data.name: id for id, item_data in enumerate(_all_items, base_id)} - - -key_item_names = { - "Small Lothric Banner", - "Basin of Vows", - "Small Doll", - "Storm Ruler", - "Grand Archives Key", - "Cinders of a Lord - Abyss Watcher", - "Cinders of a Lord - Yhorm the Giant", - "Cinders of a Lord - Aldrich", - "Cinders of a Lord - Lothric Prince", - "Mortician's Ashes", - "Cell Key", - #"Tower Key", #Not a relevant key item atm - "Jailbreaker's Key", - "Prisoner Chief's Ashes", - "Old Cell Key", - "Jailer's Key Ring", - "Contraption Key", - "Small Envoy Banner" -} + def event(name: str, player: int) -> "DarkSouls3Item": + data = DS3ItemData(name, None, DS3ItemCategory.MISC, + skip = True, classification = ItemClassification.progression) + data.ap_code = None + return DarkSouls3Item(player, data) -_vanilla_items = [DS3ItemData(row[0], row[1], False, row[2]) for row in [ +_vanilla_items = [ # Ammunition - ("Standard Arrow", 0x00061A80, DS3ItemCategory.SKIP), - ("Fire Arrow", 0x00061AE4, DS3ItemCategory.SKIP), - ("Poison Arrow", 0x00061B48, DS3ItemCategory.SKIP), - ("Large Arrow", 0x00061BAC, DS3ItemCategory.SKIP), - ("Feather Arrow", 0x00061C10, DS3ItemCategory.SKIP), - ("Moonlight Arrow", 0x00061C74, DS3ItemCategory.SKIP), - ("Wood Arrow", 0x00061CD8, DS3ItemCategory.SKIP), - ("Dark Arrow", 0x00061D3C, DS3ItemCategory.SKIP), - ("Dragonslayer Greatarrow", 0x00062250, DS3ItemCategory.SKIP), - ("Dragonslayer Lightning Arrow", 0x00062318, DS3ItemCategory.SKIP), - ("Onislayer Greatarrow", 0x0006237C, DS3ItemCategory.SKIP), - ("Standard Bolt", 0x00062A20, DS3ItemCategory.SKIP), - ("Heavy Bolt", 0x00062A84, DS3ItemCategory.SKIP), - ("Sniper Bolt", 0x00062AE8, DS3ItemCategory.SKIP), - ("Wood Bolt", 0x00062B4C, DS3ItemCategory.SKIP), - ("Lightning Bolt", 0x00062BB0, DS3ItemCategory.SKIP), - ("Splintering Bolt", 0x00062C14, DS3ItemCategory.SKIP), - ("Exploding Bolt", 0x00062C78, DS3ItemCategory.SKIP), + *DS3ItemData("Standard Arrow", 0x00061A80, DS3ItemCategory.MISC).counts([12]), + DS3ItemData("Standard Arrow x8", 0x00061A80, DS3ItemCategory.MISC, count = 8, filler = True), + DS3ItemData("Fire Arrow", 0x00061AE4, DS3ItemCategory.MISC), + DS3ItemData("Fire Arrow x8", 0x00061AE4, DS3ItemCategory.MISC, count = 8, filler = True), + *DS3ItemData("Poison Arrow", 0x00061B48, DS3ItemCategory.MISC).counts([18]), + DS3ItemData("Poison Arrow x8", 0x00061B48, DS3ItemCategory.MISC, count = 8, filler = True), + DS3ItemData("Large Arrow", 0x00061BAC, DS3ItemCategory.MISC), + DS3ItemData("Feather Arrow", 0x00061C10, DS3ItemCategory.MISC), + *DS3ItemData("Moonlight Arrow", 0x00061C74, DS3ItemCategory.MISC).counts([6]), + DS3ItemData("Wood Arrow", 0x00061CD8, DS3ItemCategory.MISC), + DS3ItemData("Dark Arrow", 0x00061D3C, DS3ItemCategory.MISC), + *DS3ItemData("Dragonslayer Greatarrow", 0x00062250, DS3ItemCategory.MISC).counts([5]), + *DS3ItemData("Dragonslayer Lightning Arrow", 0x00062318, DS3ItemCategory.MISC).counts([10]), + *DS3ItemData("Onislayer Greatarrow", 0x0006237C, DS3ItemCategory.MISC).counts([8]), + DS3ItemData("Standard Bolt", 0x00062A20, DS3ItemCategory.MISC), + DS3ItemData("Heavy Bolt", 0x00062A84, DS3ItemCategory.MISC), + *DS3ItemData("Sniper Bolt", 0x00062AE8, DS3ItemCategory.MISC).counts([11]), + DS3ItemData("Wood Bolt", 0x00062B4C, DS3ItemCategory.MISC), + *DS3ItemData("Lightning Bolt", 0x00062BB0, DS3ItemCategory.MISC).counts([9]), + *DS3ItemData("Lightning Bolt", 0x00062BB0, DS3ItemCategory.MISC).counts([12]), + DS3ItemData("Splintering Bolt", 0x00062C14, DS3ItemCategory.MISC), + *DS3ItemData("Exploding Bolt", 0x00062C78, DS3ItemCategory.MISC).counts([6]), # Weapons - ("Dagger", 0x000F4240, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Bandit's Knife", 0x000F6950, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Parrying Dagger", 0x000F9060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Rotten Ghru Dagger", 0x000FDE80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Harpe", 0x00102CA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Scholar's Candlestick", 0x001053B0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Tailbone Short Sword", 0x00107AC0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Corvian Greatknife", 0x0010A1D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Handmaid's Dagger", 0x00111700, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Shortsword", 0x001E8480, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Longsword", 0x001EAB90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Broadsword", 0x001ED2A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Broken Straight Sword", 0x001EF9B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lothric Knight Sword", 0x001F6EE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Sunlight Straight Sword", 0x00203230, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Rotten Ghru Curved Sword", 0x00205940, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Irithyll Straight Sword", 0x0020A760, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Cleric's Candlestick", 0x0020F580, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Morion Blade", 0x002143A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Astora's Straight Sword", 0x002191C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Barbed Straight Sword", 0x0021B8D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Executioner's Greatsword", 0x0021DFE0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Anri's Straight Sword", 0x002206F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Estoc", 0x002DC6C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Mail Breaker", 0x002DEDD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Rapier", 0x002E14E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Ricard's Rapier", 0x002E3BF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crystal Sage's Rapier", 0x002E6300, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Irithyll Rapier", 0x002E8A10, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Shotel", 0x003D3010, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Scimitar", 0x003D7E30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Falchion", 0x003DA540, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Carthus Curved Sword", 0x003DCC50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Carthus Curved Greatsword", 0x003DF360, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Pontiff Knight Curved Sword", 0x003E1A70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Storm Curved Sword", 0x003E4180, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Painting Guardian's Curved Sword", 0x003E6890, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crescent Moon Sword", 0x003E8FA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Carthus Shotel", 0x003EB6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Uchigatana", 0x004C4B40, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Washing Pole", 0x004C7250, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Chaos Blade", 0x004C9960, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Black Blade", 0x004CC070, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Bloodlust", 0x004CE780, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Darkdrift", 0x004D0E90, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Bastard Sword", 0x005B8D80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Claymore", 0x005BDBA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Zweihander", 0x005C29C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Greatsword", 0x005C50D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Astora Greatsword", 0x005C9EF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Murakumo", 0x005CC600, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lothric Knight Greatsword", 0x005D1420, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Flamberge", 0x005DB060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Exile Greatsword", 0x005DD770, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Greatsword of Judgment", 0x005E2590, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Profaned Greatsword", 0x005E4CA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Cathedral Knight Greatsword", 0x005E73B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Farron Greatsword", 0x005E9AC0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Yhorm's Great Machete", 0x005F0FF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Dark Sword", 0x005F3700, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Black Knight Sword", 0x005F5E10, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Lorian's Greatsword", 0x005F8520, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Twin Princes' Greatsword", 0x005FAC30, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Lothric's Holy Sword", 0x005FD340, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Wolnir's Holy Sword", 0x005FFA50, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Wolf Knight's Greatsword", 0x00602160, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Greatsword of Artorias", 0x0060216A, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Hollowslayer Greatsword", 0x00604870, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Moonlight Greatsword", 0x00606F80, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Drakeblood Greatsword", 0x00609690, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Firelink Greatsword", 0x0060BDA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Fume Ultra Greatsword", 0x0060E4B0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Old Wolf Curved Sword", 0x00610BC0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Storm Ruler", 0x006132D0, DS3ItemCategory.KEY), - ("Hand Axe", 0x006ACFC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Battle Axe", 0x006AF6D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Deep Battle Axe", 0x006AFA54, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Brigand Axe", 0x006B1DE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crescent Axe", 0x006B6C00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Axe", 0x006B9310, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Butcher Knife", 0x006BE130, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Dragonslayer's Axe", 0x006C0840, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Thrall Axe", 0x006C5660, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dragonslayer Greataxe", 0x006C7D70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Demon's Greataxe", 0x006CA480, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Eleonora", 0x006CCB90, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Man Serpent Hatchet", 0x006D19B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Club", 0x007A1200, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Mace", 0x007A3910, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Morning Star", 0x007A6020, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Reinforced Club", 0x007A8730, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Large Club", 0x007AFC60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Club", 0x007B4A80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Mace", 0x007BBFB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Great Wooden Hammer", 0x007C8300, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gargoyle Flame Hammer", 0x007CAA10, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Vordt's Great Hammer", 0x007CD120, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Old King's Great Hammer", 0x007CF830, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Heysel Pick", 0x007D6D60, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Warpick", 0x007DBB80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Pickaxe", 0x007DE290, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dragon Tooth", 0x007E09A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Smough's Great Hammer", 0x007E30B0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Blacksmith Hammer", 0x007E57C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Morne's Great Hammer", 0x007E7ED0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Spiked Mace", 0x007EA5E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Spear", 0x00895440, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Winged Spear", 0x00897B50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Partizan", 0x0089C970, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Greatlance", 0x008A8CC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lothric Knight Long Spear", 0x008AB3D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gargoyle Flame Spear", 0x008B01F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Rotten Ghru Spear", 0x008B2900, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Tailbone Spear", 0x008B5010, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Soldering Iron", 0x008B7720, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Arstor's Spear", 0x008BEC50, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Saint Bident", 0x008C1360, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Yorshka's Spear", 0x008C3A70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pike", 0x008C6180, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Heavy Four-pronged Plow", 0x008ADAE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dragonslayer Spear", 0x008CAFA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Great Scythe", 0x00989680, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Lucerne", 0x0098BD90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Glaive", 0x0098E4A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Halberd", 0x00990BB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Black Knight Greataxe", 0x009959D0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pontiff Knight Great Scythe", 0x0099A7F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Great Corvian Scythe", 0x0099CF00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Winged Knight Halberd", 0x0099F610, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gundyr's Halberd", 0x009A1D20, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Red Hilted Halberd", 0x009AB960, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Black Knight Glaive", 0x009AE070, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Immolation Tinder", 0x009B0780, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Claw", 0x00A7D8C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Caestus", 0x00A7FFD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Manikin Claws", 0x00A826E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Demon's Fist", 0x00A84DF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Dark Hand", 0x00A87500, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Whip", 0x00B71B00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Witch's Locks", 0x00B7B740, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Notched Whip", 0x00B7DE50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Spotted Whip", 0x00B80560, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Talisman", 0x00C72090, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sorcerer's Staff", 0x00C747A0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Storyteller's Staff", 0x00C76EB0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Mendicant's Staff", 0x00C795C0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Man-grub's Staff", 0x00C7E3E0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Archdeacon's Great Staff", 0x00C80AF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Golden Ritual Spear", 0x00C83200, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Yorshka's Chime", 0x00C88020, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sage's Crystal Staff", 0x00C8CE40, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Heretic's Staff", 0x00C8F550, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Court Sorcerer's Staff", 0x00C91C60, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Witchtree Branch", 0x00C94370, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Izalith Staff", 0x00C96A80, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Cleric's Sacred Chime", 0x00C99190, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Priest's Chime", 0x00C9B8A0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Saint-tree Bellvine", 0x00C9DFB0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Caitha's Chime", 0x00CA06C0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Crystal Chime", 0x00CA2DD0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Sunlight Talisman", 0x00CA54E0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Canvas Talisman", 0x00CA7BF0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sunless Talisman", 0x00CAA300, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Saint's Talisman", 0x00CACA10, DS3ItemCategory.WEAPON_UPGRADE_10), - ("White Hair Talisman", 0x00CAF120, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pyromancy Flame", 0x00CC77C0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Dragonslayer Greatbow", 0x00CF8500, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Short Bow", 0x00D5C690, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Composite Bow", 0x00D5EDA0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Light Crossbow", 0x00D63BC0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Arbalest", 0x00D662D0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Longbow", 0x00D689E0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Dragonrider Bow", 0x00D6B0F0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Avelyn", 0x00D6FF10, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Knight's Crossbow", 0x00D72620, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Heavy Crossbow", 0x00D74D30, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Darkmoon Longbow", 0x00D79B50, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Onislayer Greatbow", 0x00D7C260, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Black Bow of Pharis", 0x00D7E970, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sniper Crossbow", 0x00D83790, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sellsword Twinblades", 0x00F42400, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Warden Twinblades", 0x00F47220, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Winged Knight Twinaxes", 0x00F49930, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Dancer's Enchanted Swords", 0x00F4C040, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Great Machete", 0x00F4E750, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Brigand Twindaggers", 0x00F50E60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Gotthard Twinswords", 0x00F53570, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Onikiri and Ubadachi", 0x00F58390, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Drang Twinspears", 0x00F5AAA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Drang Hammers", 0x00F61FD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dagger", 0x000F4240, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Bandit's Knife", 0x000F6950, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Parrying Dagger", 0x000F9060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Rotten Ghru Dagger", 0x000FDE80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Harpe", 0x00102CA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Scholar's Candlestick", 0x001053B0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Tailbone Short Sword", 0x00107AC0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Corvian Greatknife", 0x0010A1D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Handmaid's Dagger", 0x00111700, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Shortsword", 0x001E8480, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Longsword", 0x001EAB90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Broadsword", 0x001ED2A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Broken Straight Sword", 0x001EF9B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lothric Knight Sword", 0x001F6EE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Sunlight Straight Sword", 0x00203230, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Rotten Ghru Curved Sword", 0x00205940, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Irithyll Straight Sword", 0x0020A760, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Cleric's Candlestick", 0x0020F580, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Morion Blade", 0x002143A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Astora Straight Sword", 0x002191C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Barbed Straight Sword", 0x0021B8D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Executioner's Greatsword", 0x0021DFE0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Anri's Straight Sword", 0x002206F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Estoc", 0x002DC6C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Mail Breaker", 0x002DEDD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Rapier", 0x002E14E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Ricard's Rapier", 0x002E3BF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crystal Sage's Rapier", 0x002E6300, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Irithyll Rapier", 0x002E8A10, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Shotel", 0x003D3010, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Scimitar", 0x003D7E30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Falchion", 0x003DA540, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Carthus Curved Sword", 0x003DCC50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Carthus Curved Greatsword", 0x003DF360, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Pontiff Knight Curved Sword", 0x003E1A70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Storm Curved Sword", 0x003E4180, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Painting Guardian's Curved Sword", 0x003E6890, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crescent Moon Sword", 0x003E8FA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Carthus Shotel", 0x003EB6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Uchigatana", 0x004C4B40, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Washing Pole", 0x004C7250, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Chaos Blade", 0x004C9960, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Black Blade", 0x004CC070, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Bloodlust", 0x004CE780, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Darkdrift", 0x004D0E90, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Bastard Sword", 0x005B8D80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Claymore", 0x005BDBA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Zweihander", 0x005C29C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greatsword", 0x005C50D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Astora Greatsword", 0x005C9EF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Murakumo", 0x005CC600, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lothric Knight Greatsword", 0x005D1420, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Flamberge", 0x005DB060, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Exile Greatsword", 0x005DD770, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greatsword of Judgment", 0x005E2590, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Profaned Greatsword", 0x005E4CA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Cathedral Knight Greatsword", 0x005E73B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Farron Greatsword", 0x005E9AC0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Yhorm's Great Machete", 0x005F0FF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Dark Sword", 0x005F3700, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Black Knight Sword", 0x005F5E10, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Lorian's Greatsword", 0x005F8520, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Twin Princes' Greatsword", 0x005FAC30, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Lothric's Holy Sword", 0x005FD340, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Wolnir's Holy Sword", 0x005FFA50, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Wolf Knight's Greatsword", 0x00602160, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Greatsword of Artorias", 0x0060216A, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Hollowslayer Greatsword", 0x00604870, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Moonlight Greatsword", 0x00606F80, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Drakeblood Greatsword", 0x00609690, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Firelink Greatsword", 0x0060BDA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Fume Ultra Greatsword", 0x0060E4B0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Old Wolf Curved Sword", 0x00610BC0, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Storm Ruler", 0x006132D0, DS3ItemCategory.WEAPON_UPGRADE_5, + classification = ItemClassification.progression), + DS3ItemData("Hand Axe", 0x006ACFC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Battle Axe", 0x006AF6D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Deep Battle Axe", 0x006AFA54, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Brigand Axe", 0x006B1DE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crescent Axe", 0x006B6C00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greataxe", 0x006B9310, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Butcher Knife", 0x006BE130, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Dragonslayer's Axe", 0x006C0840, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Thrall Axe", 0x006C5660, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dragonslayer Greataxe", 0x006C7D70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Demon's Greataxe", 0x006CA480, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Eleonora", 0x006CCB90, DS3ItemCategory.WEAPON_UPGRADE_5, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Man Serpent Hatchet", 0x006D19B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Club", 0x007A1200, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Mace", 0x007A3910, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Morning Star", 0x007A6020, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Reinforced Club", 0x007A8730, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Large Club", 0x007AFC60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Great Club", 0x007B4A80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Great Mace", 0x007BBFB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Great Wooden Hammer", 0x007C8300, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gargoyle Flame Hammer", 0x007CAA10, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Vordt's Great Hammer", 0x007CD120, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Old King's Great Hammer", 0x007CF830, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Heysel Pick", 0x007D6D60, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Warpick", 0x007DBB80, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Pickaxe", 0x007DE290, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dragon Tooth", 0x007E09A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Smough's Great Hammer", 0x007E30B0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Blacksmith Hammer", 0x007E57C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Morne's Great Hammer", 0x007E7ED0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Spiked Mace", 0x007EA5E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Spear", 0x00895440, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Winged Spear", 0x00897B50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Partizan", 0x0089C970, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Greatlance", 0x008A8CC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lothric Knight Long Spear", 0x008AB3D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gargoyle Flame Spear", 0x008B01F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Rotten Ghru Spear", 0x008B2900, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Tailbone Spear", 0x008B5010, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Soldering Iron", 0x008B7720, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Dragonslayer Swordspear", 0x008BC540, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Arstor's Spear", 0x008BEC50, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Saint Bident", 0x008C1360, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Yorshka's Spear", 0x008C3A70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pike", 0x008C6180, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Heavy Four-pronged Plow", 0x008ADAE0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dragonslayer Spear", 0x008CAFA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Great Scythe", 0x00989680, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Lucerne", 0x0098BD90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Glaive", 0x0098E4A0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Halberd", 0x00990BB0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Black Knight Greataxe", 0x009959D0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pontiff Knight Great Scythe", 0x0099A7F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Great Corvian Scythe", 0x0099CF00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Winged Knight Halberd", 0x0099F610, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gundyr's Halberd", 0x009A1D20, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Red Hilted Halberd", 0x009AB960, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Black Knight Glaive", 0x009AE070, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Immolation Tinder", 0x009B0780, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Claw", 0x00A7D8C0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Caestus", 0x00A7FFD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Manikin Claws", 0x00A826E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Demon's Fist", 0x00A84DF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Dark Hand", 0x00A87500, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Whip", 0x00B71B00, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Witch's Locks", 0x00B7B740, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Notched Whip", 0x00B7DE50, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Spotted Whip", 0x00B80560, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Talisman", 0x00C72090, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sorcerer's Staff", 0x00C747A0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Storyteller's Staff", 0x00C76EB0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Mendicant's Staff", 0x00C795C0, DS3ItemCategory.WEAPON_UPGRADE_10, + classification = ItemClassification.progression, # Crow trade + inject = True), # This is just a random drop normally, but we need it in-logic + DS3ItemData("Man-grub's Staff", 0x00C7E3E0, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Archdeacon's Great Staff", 0x00C80AF0, DS3ItemCategory.WEAPON_UPGRADE_5, + inject = True), # Covenant reward + DS3ItemData("Golden Ritual Spear", 0x00C83200, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Yorshka's Chime", 0x00C88020, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sage's Crystal Staff", 0x00C8CE40, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Heretic's Staff", 0x00C8F550, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Court Sorcerer's Staff", 0x00C91C60, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Witchtree Branch", 0x00C94370, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Izalith Staff", 0x00C96A80, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Cleric's Sacred Chime", 0x00C99190, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Priest's Chime", 0x00C9B8A0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Saint-tree Bellvine", 0x00C9DFB0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Caitha's Chime", 0x00CA06C0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Crystal Chime", 0x00CA2DD0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Sunlight Talisman", 0x00CA54E0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Canvas Talisman", 0x00CA7BF0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sunless Talisman", 0x00CAA300, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Saint's Talisman", 0x00CACA10, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("White Hair Talisman", 0x00CAF120, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pyromancy Flame", 0x00CC77C0, DS3ItemCategory.WEAPON_UPGRADE_10, + classification = ItemClassification.progression), + DS3ItemData("Dragonslayer Greatbow", 0x00CF8500, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Short Bow", 0x00D5C690, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Composite Bow", 0x00D5EDA0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Light Crossbow", 0x00D63BC0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Arbalest", 0x00D662D0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Longbow", 0x00D689E0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Dragonrider Bow", 0x00D6B0F0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Avelyn", 0x00D6FF10, DS3ItemCategory.WEAPON_UPGRADE_10, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Knight's Crossbow", 0x00D72620, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Heavy Crossbow", 0x00D74D30, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Darkmoon Longbow", 0x00D79B50, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Onislayer Greatbow", 0x00D7C260, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Black Bow of Pharis", 0x00D7E970, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sniper Crossbow", 0x00D83790, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sellsword Twinblades", 0x00F42400, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Warden Twinblades", 0x00F47220, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Winged Knight Twinaxes", 0x00F49930, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Dancer's Enchanted Swords", 0x00F4C040, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Great Machete", 0x00F4E750, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Brigand Twindaggers", 0x00F50E60, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Gotthard Twinswords", 0x00F53570, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Onikiri and Ubadachi", 0x00F58390, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Drang Twinspears", 0x00F5AAA0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Drang Hammers", 0x00F61FD0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), # Shields - ("Buckler", 0x01312D00, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Small Leather Shield", 0x01315410, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Round Shield", 0x0131A230, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Large Leather Shield", 0x0131C940, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Hawkwood's Shield", 0x01323E70, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Iron Round Shield", 0x01326580, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Wooden Shield", 0x0132DAB0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Kite Shield", 0x013301C0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Ghru Rotshield", 0x013328D0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Havel's Greatshield", 0x013376F0, DS3ItemCategory.SHIELD), - ("Target Shield", 0x01339E00, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Elkhorn Round Shield", 0x0133C510, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Warrior's Round Shield", 0x0133EC20, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Caduceus Round Shield", 0x01341330, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Red and White Shield", 0x01343A40, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Blessed Red and White Shield+1", 0x01343FB9, DS3ItemCategory.SHIELD), - ("Plank Shield", 0x01346150, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Leather Shield", 0x01348860, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Crimson Parma", 0x0134AF70, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Eastern Iron Shield", 0x0134D680, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Llewellyn Shield", 0x0134FD90, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Golden Falcon Shield", 0x01354BB0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Sacred Bloom Shield", 0x013572C0, DS3ItemCategory.SHIELD), - ("Ancient Dragon Greatshield", 0x013599D0, DS3ItemCategory.SHIELD), - ("Lothric Knight Shield", 0x01409650, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Knight Shield", 0x01410B80, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Pontiff Knight Shield", 0x014159A0, DS3ItemCategory.SHIELD), - ("Carthus Shield", 0x014180B0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Black Knight Shield", 0x0141F5E0, DS3ItemCategory.SHIELD), - ("Silver Knight Shield", 0x01424400, DS3ItemCategory.SHIELD), - ("Spiked Shield", 0x01426B10, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Pierce Shield", 0x01429220, DS3ItemCategory.SHIELD_INFUSIBLE), - ("East-West Shield", 0x0142B930, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Sunlight Shield", 0x0142E040, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Crest Shield", 0x01430750, DS3ItemCategory.SHIELD), - ("Dragon Crest Shield", 0x01432E60, DS3ItemCategory.SHIELD), - ("Spider Shield", 0x01435570, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Grass Crest Shield", 0x01437C80, DS3ItemCategory.SHIELD), - ("Sunset Shield", 0x0143A390, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Golden Wing Crest Shield", 0x0143CAA0, DS3ItemCategory.SHIELD), - ("Blue Wooden Shield", 0x0143F1B0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Silver Eagle Kite Shield", 0x014418C0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Stone Parma", 0x01443FD0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Spirit Tree Crest Shield", 0x014466E0, DS3ItemCategory.SHIELD), - ("Porcine Shield", 0x01448DF0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Shield of Want", 0x0144B500, DS3ItemCategory.SHIELD), - ("Wargod Wooden Shield", 0x0144DC10, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Lothric Knight Greatshield", 0x014FD890, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Cathedral Knight Greatshield", 0x014FFFA0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Dragonslayer Greatshield", 0x01504DC0, DS3ItemCategory.SHIELD), - ("Moaning Shield", 0x015074D0, DS3ItemCategory.SHIELD), - ("Yhorm's Greatshield", 0x0150C2F0, DS3ItemCategory.SHIELD), - ("Black Iron Greatshield", 0x0150EA00, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Wolf Knight's Greatshield", 0x01511110, DS3ItemCategory.SHIELD), - ("Twin Dragon Greatshield", 0x01513820, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Greatshield of Glory", 0x01515F30, DS3ItemCategory.SHIELD), - ("Curse Ward Greatshield", 0x01518640, DS3ItemCategory.SHIELD), - ("Bonewheel Shield", 0x0151AD50, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Stone Greatshield", 0x0151D460, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Buckler", 0x01312D00, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Small Leather Shield", 0x01315410, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Round Shield", 0x0131A230, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Large Leather Shield", 0x0131C940, DS3ItemCategory.SHIELD_INFUSIBLE, + classification = ItemClassification.progression, # Crow trade + inject = True), # This is a shop/infinite drop item, but we need it in logic + DS3ItemData("Hawkwood's Shield", 0x01323E70, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Iron Round Shield", 0x01326580, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Wooden Shield", 0x0132DAB0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Kite Shield", 0x013301C0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Ghru Rotshield", 0x013328D0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Havel's Greatshield", 0x013376F0, DS3ItemCategory.SHIELD), + DS3ItemData("Target Shield", 0x01339E00, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Elkhorn Round Shield", 0x0133C510, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Warrior's Round Shield", 0x0133EC20, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Caduceus Round Shield", 0x01341330, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Red and White Shield", 0x01343A40, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Blessed Red and White Shield+1", 0x01343FB9, DS3ItemCategory.SHIELD), + DS3ItemData("Plank Shield", 0x01346150, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Leather Shield", 0x01348860, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Crimson Parma", 0x0134AF70, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Eastern Iron Shield", 0x0134D680, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Llewellyn Shield", 0x0134FD90, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Golden Falcon Shield", 0x01354BB0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Sacred Bloom Shield", 0x013572C0, DS3ItemCategory.SHIELD), + DS3ItemData("Ancient Dragon Greatshield", 0x013599D0, DS3ItemCategory.SHIELD), + DS3ItemData("Lothric Knight Shield", 0x01409650, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Knight Shield", 0x01410B80, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Pontiff Knight Shield", 0x014159A0, DS3ItemCategory.SHIELD), + DS3ItemData("Carthus Shield", 0x014180B0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Black Knight Shield", 0x0141F5E0, DS3ItemCategory.SHIELD), + DS3ItemData("Silver Knight Shield", 0x01424400, DS3ItemCategory.SHIELD), + DS3ItemData("Spiked Shield", 0x01426B10, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Pierce Shield", 0x01429220, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("East-West Shield", 0x0142B930, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Sunlight Shield", 0x0142E040, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Crest Shield", 0x01430750, DS3ItemCategory.SHIELD), + DS3ItemData("Dragon Crest Shield", 0x01432E60, DS3ItemCategory.SHIELD), + DS3ItemData("Spider Shield", 0x01435570, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Grass Crest Shield", 0x01437C80, DS3ItemCategory.SHIELD, + classification = ItemClassification.useful), + DS3ItemData("Sunset Shield", 0x0143A390, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Golden Wing Crest Shield", 0x0143CAA0, DS3ItemCategory.SHIELD), + DS3ItemData("Blue Wooden Shield", 0x0143F1B0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Silver Eagle Kite Shield", 0x014418C0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Stone Parma", 0x01443FD0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Spirit Tree Crest Shield", 0x014466E0, DS3ItemCategory.SHIELD), + DS3ItemData("Porcine Shield", 0x01448DF0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Shield of Want", 0x0144B500, DS3ItemCategory.SHIELD), + DS3ItemData("Wargod Wooden Shield", 0x0144DC10, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Lothric Knight Greatshield", 0x014FD890, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Cathedral Knight Greatshield", 0x014FFFA0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Dragonslayer Greatshield", 0x01504DC0, DS3ItemCategory.SHIELD), + DS3ItemData("Moaning Shield", 0x015074D0, DS3ItemCategory.SHIELD, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Yhorm's Greatshield", 0x0150C2F0, DS3ItemCategory.SHIELD), + DS3ItemData("Black Iron Greatshield", 0x0150EA00, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Wolf Knight's Greatshield", 0x01511110, DS3ItemCategory.SHIELD, + inject = True), # Covenant reward + DS3ItemData("Twin Dragon Greatshield", 0x01513820, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Greatshield of Glory", 0x01515F30, DS3ItemCategory.SHIELD), + DS3ItemData("Curse Ward Greatshield", 0x01518640, DS3ItemCategory.SHIELD), + DS3ItemData("Bonewheel Shield", 0x0151AD50, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Stone Greatshield", 0x0151D460, DS3ItemCategory.SHIELD_INFUSIBLE), # Armor - ("Fallen Knight Helm", 0x1121EAC0, DS3ItemCategory.ARMOR), - ("Fallen Knight Armor", 0x1121EEA8, DS3ItemCategory.ARMOR), - ("Fallen Knight Gauntlets", 0x1121F290, DS3ItemCategory.ARMOR), - ("Fallen Knight Trousers", 0x1121F678, DS3ItemCategory.ARMOR), - ("Knight Helm", 0x11298BE0, DS3ItemCategory.ARMOR), - ("Knight Armor", 0x11298FC8, DS3ItemCategory.ARMOR), - ("Knight Gauntlets", 0x112993B0, DS3ItemCategory.ARMOR), - ("Knight Leggings", 0x11299798, DS3ItemCategory.ARMOR), - ("Firelink Helm", 0x11406F40, DS3ItemCategory.ARMOR), - ("Firelink Armor", 0x11407328, DS3ItemCategory.ARMOR), - ("Firelink Gauntlets", 0x11407710, DS3ItemCategory.ARMOR), - ("Firelink Leggings", 0x11407AF8, DS3ItemCategory.ARMOR), - ("Sellsword Helm", 0x11481060, DS3ItemCategory.ARMOR), - ("Sellsword Armor", 0x11481448, DS3ItemCategory.ARMOR), - ("Sellsword Gauntlet", 0x11481830, DS3ItemCategory.ARMOR), - ("Sellsword Trousers", 0x11481C18, DS3ItemCategory.ARMOR), - ("Herald Helm", 0x114FB180, DS3ItemCategory.ARMOR), - ("Herald Armor", 0x114FB568, DS3ItemCategory.ARMOR), - ("Herald Gloves", 0x114FB950, DS3ItemCategory.ARMOR), - ("Herald Trousers", 0x114FBD38, DS3ItemCategory.ARMOR), - ("Sunless Veil", 0x115752A0, DS3ItemCategory.ARMOR), - ("Sunless Armor", 0x11575688, DS3ItemCategory.ARMOR), - ("Sunless Gauntlets", 0x11575A70, DS3ItemCategory.ARMOR), - ("Sunless Leggings", 0x11575E58, DS3ItemCategory.ARMOR), - ("Black Hand Hat", 0x115EF3C0, DS3ItemCategory.ARMOR), - ("Black Hand Armor", 0x115EF7A8, DS3ItemCategory.ARMOR), - ("Assassin Gloves", 0x115EFB90, DS3ItemCategory.ARMOR), - ("Assassin Trousers", 0x115EFF78, DS3ItemCategory.ARMOR), - ("Assassin Hood", 0x11607A60, DS3ItemCategory.ARMOR), - ("Assassin Armor", 0x11607E48, DS3ItemCategory.ARMOR), - ("Xanthous Crown", 0x116694E0, DS3ItemCategory.ARMOR), - ("Xanthous Overcoat", 0x116698C8, DS3ItemCategory.ARMOR), - ("Xanthous Gloves", 0x11669CB0, DS3ItemCategory.ARMOR), - ("Xanthous Trousers", 0x1166A098, DS3ItemCategory.ARMOR), - ("Northern Helm", 0x116E3600, DS3ItemCategory.ARMOR), - ("Northern Armor", 0x116E39E8, DS3ItemCategory.ARMOR), - ("Northern Gloves", 0x116E3DD0, DS3ItemCategory.ARMOR), - ("Northern Trousers", 0x116E41B8, DS3ItemCategory.ARMOR), - ("Morne's Helm", 0x1175D720, DS3ItemCategory.ARMOR), - ("Morne's Armor", 0x1175DB08, DS3ItemCategory.ARMOR), - ("Morne's Gauntlets", 0x1175DEF0, DS3ItemCategory.ARMOR), - ("Morne's Leggings", 0x1175E2D8, DS3ItemCategory.ARMOR), - ("Silver Mask", 0x117D7840, DS3ItemCategory.ARMOR), - ("Leonhard's Garb", 0x117D7C28, DS3ItemCategory.ARMOR), - ("Leonhard's Gauntlets", 0x117D8010, DS3ItemCategory.ARMOR), - ("Leonhard's Trousers", 0x117D83F8, DS3ItemCategory.ARMOR), - ("Sneering Mask", 0x11851960, DS3ItemCategory.ARMOR), - ("Pale Shade Robe", 0x11851D48, DS3ItemCategory.ARMOR), - ("Pale Shade Gloves", 0x11852130, DS3ItemCategory.ARMOR), - ("Pale Shade Trousers", 0x11852518, DS3ItemCategory.ARMOR), - ("Sunset Helm", 0x118CBA80, DS3ItemCategory.ARMOR), - ("Sunset Armor", 0x118CBE68, DS3ItemCategory.ARMOR), - ("Sunset Gauntlets", 0x118CC250, DS3ItemCategory.ARMOR), - ("Sunset Leggings", 0x118CC638, DS3ItemCategory.ARMOR), - ("Old Sage's Blindfold", 0x11945BA0, DS3ItemCategory.ARMOR), - ("Cornyx's Garb", 0x11945F88, DS3ItemCategory.ARMOR), - ("Cornyx's Wrap", 0x11946370, DS3ItemCategory.ARMOR), - ("Cornyx's Skirt", 0x11946758, DS3ItemCategory.ARMOR), - ("Executioner Helm", 0x119BFCC0, DS3ItemCategory.ARMOR), - ("Executioner Armor", 0x119C00A8, DS3ItemCategory.ARMOR), - ("Executioner Gauntlets", 0x119C0490, DS3ItemCategory.ARMOR), - ("Executioner Leggings", 0x119C0878, DS3ItemCategory.ARMOR), - ("Billed Mask", 0x11A39DE0, DS3ItemCategory.ARMOR), - ("Black Dress", 0x11A3A1C8, DS3ItemCategory.ARMOR), - ("Black Gauntlets", 0x11A3A5B0, DS3ItemCategory.ARMOR), - ("Black Leggings", 0x11A3A998, DS3ItemCategory.ARMOR), - ("Pyromancer Crown", 0x11AB3F00, DS3ItemCategory.ARMOR), - ("Pyromancer Garb", 0x11AB42E8, DS3ItemCategory.ARMOR), - ("Pyromancer Wrap", 0x11AB46D0, DS3ItemCategory.ARMOR), - ("Pyromancer Trousers", 0x11AB4AB8, DS3ItemCategory.ARMOR), - ("Court Sorcerer Hood", 0x11BA8140, DS3ItemCategory.ARMOR), - ("Court Sorcerer Robe", 0x11BA8528, DS3ItemCategory.ARMOR), - ("Court Sorcerer Gloves", 0x11BA8910, DS3ItemCategory.ARMOR), - ("Court Sorcerer Trousers", 0x11BA8CF8, DS3ItemCategory.ARMOR), - ("Sorcerer Hood", 0x11C9C380, DS3ItemCategory.ARMOR), - ("Sorcerer Robe", 0x11C9C768, DS3ItemCategory.ARMOR), - ("Sorcerer Gloves", 0x11C9CB50, DS3ItemCategory.ARMOR), - ("Sorcerer Trousers", 0x11C9CF38, DS3ItemCategory.ARMOR), - ("Clandestine Coat", 0x11CB4E08, DS3ItemCategory.ARMOR), - ("Cleric Hat", 0x11D905C0, DS3ItemCategory.ARMOR), - ("Cleric Blue Robe", 0x11D909A8, DS3ItemCategory.ARMOR), - ("Cleric Gloves", 0x11D90D90, DS3ItemCategory.ARMOR), - ("Cleric Trousers", 0x11D91178, DS3ItemCategory.ARMOR), - ("Steel Soldier Helm", 0x12625A00, DS3ItemCategory.ARMOR), - ("Deserter Armor", 0x12625DE8, DS3ItemCategory.ARMOR), - ("Deserter Trousers", 0x126265B8, DS3ItemCategory.ARMOR), - ("Thief Mask", 0x12656740, DS3ItemCategory.ARMOR), - ("Sage's Big Hat", 0x129020C0, DS3ItemCategory.ARMOR), - ("Aristocrat's Mask", 0x129F6300, DS3ItemCategory.ARMOR), - ("Jailer Robe", 0x129F66E8, DS3ItemCategory.ARMOR), - ("Jailer Gloves", 0x129F6AD0, DS3ItemCategory.ARMOR), - ("Jailer Trousers", 0x129F6EB8, DS3ItemCategory.ARMOR), - ("Grave Warden Hood", 0x12BDE780, DS3ItemCategory.ARMOR), - ("Grave Warden Robe", 0x12BDEB68, DS3ItemCategory.ARMOR), - ("Grave Warden Wrap", 0x12BDEF50, DS3ItemCategory.ARMOR), - ("Grave Warden Skirt", 0x12BDF338, DS3ItemCategory.ARMOR), - ("Worker Hat", 0x12CD29C0, DS3ItemCategory.ARMOR), - ("Worker Garb", 0x12CD2DA8, DS3ItemCategory.ARMOR), - ("Worker Gloves", 0x12CD3190, DS3ItemCategory.ARMOR), - ("Worker Trousers", 0x12CD3578, DS3ItemCategory.ARMOR), - ("Thrall Hood", 0x12D4CAE0, DS3ItemCategory.ARMOR), - ("Evangelist Hat", 0x12DC6C00, DS3ItemCategory.ARMOR), - ("Evangelist Robe", 0x12DC6FE8, DS3ItemCategory.ARMOR), - ("Evangelist Gloves", 0x12DC73D0, DS3ItemCategory.ARMOR), - ("Evangelist Trousers", 0x12DC77B8, DS3ItemCategory.ARMOR), - ("Scholar's Robe", 0x12E41108, DS3ItemCategory.ARMOR), - ("Winged Knight Helm", 0x12EBAE40, DS3ItemCategory.ARMOR), - ("Winged Knight Armor", 0x12EBB228, DS3ItemCategory.ARMOR), - ("Winged Knight Gauntlets", 0x12EBB610, DS3ItemCategory.ARMOR), - ("Winged Knight Leggings", 0x12EBB9F8, DS3ItemCategory.ARMOR), - ("Cathedral Knight Helm", 0x130291A0, DS3ItemCategory.ARMOR), - ("Cathedral Knight Armor", 0x13029588, DS3ItemCategory.ARMOR), - ("Cathedral Knight Gauntlets", 0x13029970, DS3ItemCategory.ARMOR), - ("Cathedral Knight Leggings", 0x13029D58, DS3ItemCategory.ARMOR), - ("Lothric Knight Helm", 0x13197500, DS3ItemCategory.ARMOR), - ("Lothric Knight Armor", 0x131978E8, DS3ItemCategory.ARMOR), - ("Lothric Knight Gauntlets", 0x13197CD0, DS3ItemCategory.ARMOR), - ("Lothric Knight Leggings", 0x131980B8, DS3ItemCategory.ARMOR), - ("Outrider Knight Helm", 0x1328B740, DS3ItemCategory.ARMOR), - ("Outrider Knight Armor", 0x1328BB28, DS3ItemCategory.ARMOR), - ("Outrider Knight Gauntlets", 0x1328BF10, DS3ItemCategory.ARMOR), - ("Outrider Knight Leggings", 0x1328C2F8, DS3ItemCategory.ARMOR), - ("Black Knight Helm", 0x1337F980, DS3ItemCategory.ARMOR), - ("Black Knight Armor", 0x1337FD68, DS3ItemCategory.ARMOR), - ("Black Knight Gauntlets", 0x13380150, DS3ItemCategory.ARMOR), - ("Black Knight Leggings", 0x13380538, DS3ItemCategory.ARMOR), - ("Dark Mask", 0x133F9AA0, DS3ItemCategory.ARMOR), - ("Dark Armor", 0x133F9E88, DS3ItemCategory.ARMOR), - ("Dark Gauntlets", 0x133FA270, DS3ItemCategory.ARMOR), - ("Dark Leggings", 0x133FA658, DS3ItemCategory.ARMOR), - ("Exile Mask", 0x13473BC0, DS3ItemCategory.ARMOR), - ("Exile Armor", 0x13473FA8, DS3ItemCategory.ARMOR), - ("Exile Gauntlets", 0x13474390, DS3ItemCategory.ARMOR), - ("Exile Leggings", 0x13474778, DS3ItemCategory.ARMOR), - ("Pontiff Knight Crown", 0x13567E00, DS3ItemCategory.ARMOR), - ("Pontiff Knight Armor", 0x135681E8, DS3ItemCategory.ARMOR), - ("Pontiff Knight Gauntlets", 0x135685D0, DS3ItemCategory.ARMOR), - ("Pontiff Knight Leggings", 0x135689B8, DS3ItemCategory.ARMOR), - ("Golden Crown", 0x1365C040, DS3ItemCategory.ARMOR), - ("Dragonscale Armor", 0x1365C428, DS3ItemCategory.ARMOR), - ("Golden Bracelets", 0x1365C810, DS3ItemCategory.ARMOR), - ("Dragonscale Waistcloth", 0x1365CBF8, DS3ItemCategory.ARMOR), - ("Wolnir's Crown", 0x136D6160, DS3ItemCategory.ARMOR), - ("Undead Legion Helm", 0x13750280, DS3ItemCategory.ARMOR), - ("Undead Legion Armor", 0x13750668, DS3ItemCategory.ARMOR), - ("Undead Legion Gauntlets", 0x13750A50, DS3ItemCategory.ARMOR), - ("Undead Legion Leggings", 0x13750E38, DS3ItemCategory.ARMOR), - ("Fire Witch Helm", 0x13938700, DS3ItemCategory.ARMOR), - ("Fire Witch Armor", 0x13938AE8, DS3ItemCategory.ARMOR), - ("Fire Witch Gauntlets", 0x13938ED0, DS3ItemCategory.ARMOR), - ("Fire Witch Leggings", 0x139392B8, DS3ItemCategory.ARMOR), - ("Lorian's Helm", 0x13A2C940, DS3ItemCategory.ARMOR), - ("Lorian's Armor", 0x13A2CD28, DS3ItemCategory.ARMOR), - ("Lorian's Gauntlets", 0x13A2D110, DS3ItemCategory.ARMOR), - ("Lorian's Leggings", 0x13A2D4F8, DS3ItemCategory.ARMOR), - ("Hood of Prayer", 0x13AA6A60, DS3ItemCategory.ARMOR), - ("Robe of Prayer", 0x13AA6E48, DS3ItemCategory.ARMOR), - ("Skirt of Prayer", 0x13AA7618, DS3ItemCategory.ARMOR), - ("Dancer's Crown", 0x13C14DC0, DS3ItemCategory.ARMOR), - ("Dancer's Armor", 0x13C151A8, DS3ItemCategory.ARMOR), - ("Dancer's Gauntlets", 0x13C15590, DS3ItemCategory.ARMOR), - ("Dancer's Leggings", 0x13C15978, DS3ItemCategory.ARMOR), - ("Gundyr's Helm", 0x13D09000, DS3ItemCategory.ARMOR), - ("Gundyr's Armor", 0x13D093E8, DS3ItemCategory.ARMOR), - ("Gundyr's Gauntlets", 0x13D097D0, DS3ItemCategory.ARMOR), - ("Gundyr's Leggings", 0x13D09BB8, DS3ItemCategory.ARMOR), - ("Archdeacon White Crown", 0x13EF1480, DS3ItemCategory.ARMOR), - ("Archdeacon Holy Garb", 0x13EF1868, DS3ItemCategory.ARMOR), - ("Archdeacon Skirt", 0x13EF2038, DS3ItemCategory.ARMOR), - ("Deacon Robe", 0x13F6B988, DS3ItemCategory.ARMOR), - ("Deacon Skirt", 0x13F6C158, DS3ItemCategory.ARMOR), - ("Fire Keeper Robe", 0x140D9CE8, DS3ItemCategory.ARMOR), - ("Fire Keeper Gloves", 0x140DA0D0, DS3ItemCategory.ARMOR), - ("Fire Keeper Skirt", 0x140DA4B8, DS3ItemCategory.ARMOR), - ("Chain Helm", 0x142C1D80, DS3ItemCategory.ARMOR), - ("Chain Armor", 0x142C2168, DS3ItemCategory.ARMOR), - ("Leather Gauntlets", 0x142C2550, DS3ItemCategory.ARMOR), - ("Chain Leggings", 0x142C2938, DS3ItemCategory.ARMOR), - ("Nameless Knight Helm", 0x143B5FC0, DS3ItemCategory.ARMOR), - ("Nameless Knight Armor", 0x143B63A8, DS3ItemCategory.ARMOR), - ("Nameless Knight Gauntlets", 0x143B6790, DS3ItemCategory.ARMOR), - ("Nameless Knight Leggings", 0x143B6B78, DS3ItemCategory.ARMOR), - ("Elite Knight Helm", 0x144AA200, DS3ItemCategory.ARMOR), - ("Elite Knight Armor", 0x144AA5E8, DS3ItemCategory.ARMOR), - ("Elite Knight Gauntlets", 0x144AA9D0, DS3ItemCategory.ARMOR), - ("Elite Knight Leggings", 0x144AADB8, DS3ItemCategory.ARMOR), - ("Faraam Helm", 0x1459E440, DS3ItemCategory.ARMOR), - ("Faraam Armor", 0x1459E828, DS3ItemCategory.ARMOR), - ("Faraam Gauntlets", 0x1459EC10, DS3ItemCategory.ARMOR), - ("Faraam Boots", 0x1459EFF8, DS3ItemCategory.ARMOR), - ("Catarina Helm", 0x14692680, DS3ItemCategory.ARMOR), - ("Catarina Armor", 0x14692A68, DS3ItemCategory.ARMOR), - ("Catarina Gauntlets", 0x14692E50, DS3ItemCategory.ARMOR), - ("Catarina Leggings", 0x14693238, DS3ItemCategory.ARMOR), - ("Standard Helm", 0x1470C7A0, DS3ItemCategory.ARMOR), - ("Hard Leather Armor", 0x1470CB88, DS3ItemCategory.ARMOR), - ("Hard Leather Gauntlets", 0x1470CF70, DS3ItemCategory.ARMOR), - ("Hard Leather Boots", 0x1470D358, DS3ItemCategory.ARMOR), - ("Havel's Helm", 0x147868C0, DS3ItemCategory.ARMOR), - ("Havel's Armor", 0x14786CA8, DS3ItemCategory.ARMOR), - ("Havel's Gauntlets", 0x14787090, DS3ItemCategory.ARMOR), - ("Havel's Leggings", 0x14787478, DS3ItemCategory.ARMOR), - ("Brigand Hood", 0x148009E0, DS3ItemCategory.ARMOR), - ("Brigand Armor", 0x14800DC8, DS3ItemCategory.ARMOR), - ("Brigand Gauntlets", 0x148011B0, DS3ItemCategory.ARMOR), - ("Brigand Trousers", 0x14801598, DS3ItemCategory.ARMOR), - ("Pharis's Hat", 0x1487AB00, DS3ItemCategory.ARMOR), - ("Leather Armor", 0x1487AEE8, DS3ItemCategory.ARMOR), - ("Leather Gloves", 0x1487B2D0, DS3ItemCategory.ARMOR), - ("Leather Boots", 0x1487B6B8, DS3ItemCategory.ARMOR), - ("Ragged Mask", 0x148F4C20, DS3ItemCategory.ARMOR), - ("Master's Attire", 0x148F5008, DS3ItemCategory.ARMOR), - ("Master's Gloves", 0x148F53F0, DS3ItemCategory.ARMOR), - ("Loincloth", 0x148F57D8, DS3ItemCategory.ARMOR), - ("Old Sorcerer Hat", 0x1496ED40, DS3ItemCategory.ARMOR), - ("Old Sorcerer Coat", 0x1496F128, DS3ItemCategory.ARMOR), - ("Old Sorcerer Gauntlets", 0x1496F510, DS3ItemCategory.ARMOR), - ("Old Sorcerer Boots", 0x1496F8F8, DS3ItemCategory.ARMOR), - ("Conjurator Hood", 0x149E8E60, DS3ItemCategory.ARMOR), - ("Conjurator Robe", 0x149E9248, DS3ItemCategory.ARMOR), - ("Conjurator Manchettes", 0x149E9630, DS3ItemCategory.ARMOR), - ("Conjurator Boots", 0x149E9A18, DS3ItemCategory.ARMOR), - ("Black Leather Armor", 0x14A63368, DS3ItemCategory.ARMOR), - ("Black Leather Gloves", 0x14A63750, DS3ItemCategory.ARMOR), - ("Black Leather Boots", 0x14A63B38, DS3ItemCategory.ARMOR), - ("Symbol of Avarice", 0x14ADD0A0, DS3ItemCategory.ARMOR), - ("Creighton's Steel Mask", 0x14B571C0, DS3ItemCategory.ARMOR), - ("Mirrah Chain Mail", 0x14B575A8, DS3ItemCategory.ARMOR), - ("Mirrah Chain Gloves", 0x14B57990, DS3ItemCategory.ARMOR), - ("Mirrah Chain Leggings", 0x14B57D78, DS3ItemCategory.ARMOR), - ("Maiden Hood", 0x14BD12E0, DS3ItemCategory.ARMOR), - ("Maiden Robe", 0x14BD16C8, DS3ItemCategory.ARMOR), - ("Maiden Gloves", 0x14BD1AB0, DS3ItemCategory.ARMOR), - ("Maiden Skirt", 0x14BD1E98, DS3ItemCategory.ARMOR), - ("Alva Helm", 0x14C4B400, DS3ItemCategory.ARMOR), - ("Alva Armor", 0x14C4B7E8, DS3ItemCategory.ARMOR), - ("Alva Gauntlets", 0x14C4BBD0, DS3ItemCategory.ARMOR), - ("Alva Leggings", 0x14C4BFB8, DS3ItemCategory.ARMOR), - ("Shadow Mask", 0x14D3F640, DS3ItemCategory.ARMOR), - ("Shadow Garb", 0x14D3FA28, DS3ItemCategory.ARMOR), - ("Shadow Gauntlets", 0x14D3FE10, DS3ItemCategory.ARMOR), - ("Shadow Leggings", 0x14D401F8, DS3ItemCategory.ARMOR), - ("Eastern Helm", 0x14E33880, DS3ItemCategory.ARMOR), - ("Eastern Armor", 0x14E33C68, DS3ItemCategory.ARMOR), - ("Eastern Gauntlets", 0x14E34050, DS3ItemCategory.ARMOR), - ("Eastern Leggings", 0x14E34438, DS3ItemCategory.ARMOR), - ("Helm of Favor", 0x14F27AC0, DS3ItemCategory.ARMOR), - ("Embraced Armor of Favor", 0x14F27EA8, DS3ItemCategory.ARMOR), - ("Gauntlets of Favor", 0x14F28290, DS3ItemCategory.ARMOR), - ("Leggings of Favor", 0x14F28678, DS3ItemCategory.ARMOR), - ("Brass Helm", 0x1501BD00, DS3ItemCategory.ARMOR), - ("Brass Armor", 0x1501C0E8, DS3ItemCategory.ARMOR), - ("Brass Gauntlets", 0x1501C4D0, DS3ItemCategory.ARMOR), - ("Brass Leggings", 0x1501C8B8, DS3ItemCategory.ARMOR), - ("Silver Knight Helm", 0x1510FF40, DS3ItemCategory.ARMOR), - ("Silver Knight Armor", 0x15110328, DS3ItemCategory.ARMOR), - ("Silver Knight Gauntlets", 0x15110710, DS3ItemCategory.ARMOR), - ("Silver Knight Leggings", 0x15110AF8, DS3ItemCategory.ARMOR), - ("Lucatiel's Mask", 0x15204180, DS3ItemCategory.ARMOR), - ("Mirrah Vest", 0x15204568, DS3ItemCategory.ARMOR), - ("Mirrah Gloves", 0x15204950, DS3ItemCategory.ARMOR), - ("Mirrah Trousers", 0x15204D38, DS3ItemCategory.ARMOR), - ("Iron Helm", 0x152F83C0, DS3ItemCategory.ARMOR), - ("Armor of the Sun", 0x152F87A8, DS3ItemCategory.ARMOR), - ("Iron Bracelets", 0x152F8B90, DS3ItemCategory.ARMOR), - ("Iron Leggings", 0x152F8F78, DS3ItemCategory.ARMOR), - ("Drakeblood Helm", 0x153EC600, DS3ItemCategory.ARMOR), - ("Drakeblood Armor", 0x153EC9E8, DS3ItemCategory.ARMOR), - ("Drakeblood Gauntlets", 0x153ECDD0, DS3ItemCategory.ARMOR), - ("Drakeblood Leggings", 0x153ED1B8, DS3ItemCategory.ARMOR), - ("Drang Armor", 0x154E0C28, DS3ItemCategory.ARMOR), - ("Drang Gauntlets", 0x154E1010, DS3ItemCategory.ARMOR), - ("Drang Shoes", 0x154E13F8, DS3ItemCategory.ARMOR), - ("Black Iron Helm", 0x155D4A80, DS3ItemCategory.ARMOR), - ("Black Iron Armor", 0x155D4E68, DS3ItemCategory.ARMOR), - ("Black Iron Gauntlets", 0x155D5250, DS3ItemCategory.ARMOR), - ("Black Iron Leggings", 0x155D5638, DS3ItemCategory.ARMOR), - ("Painting Guardian Hood", 0x156C8CC0, DS3ItemCategory.ARMOR), - ("Painting Guardian Gown", 0x156C90A8, DS3ItemCategory.ARMOR), - ("Painting Guardian Gloves", 0x156C9490, DS3ItemCategory.ARMOR), - ("Painting Guardian Waistcloth", 0x156C9878, DS3ItemCategory.ARMOR), - ("Wolf Knight Helm", 0x157BCF00, DS3ItemCategory.ARMOR), - ("Wolf Knight Armor", 0x157BD2E8, DS3ItemCategory.ARMOR), - ("Wolf Knight Gauntlets", 0x157BD6D0, DS3ItemCategory.ARMOR), - ("Wolf Knight Leggings", 0x157BDAB8, DS3ItemCategory.ARMOR), - ("Dragonslayer Helm", 0x158B1140, DS3ItemCategory.ARMOR), - ("Dragonslayer Armor", 0x158B1528, DS3ItemCategory.ARMOR), - ("Dragonslayer Gauntlets", 0x158B1910, DS3ItemCategory.ARMOR), - ("Dragonslayer Leggings", 0x158B1CF8, DS3ItemCategory.ARMOR), - ("Smough's Helm", 0x159A5380, DS3ItemCategory.ARMOR), - ("Smough's Armor", 0x159A5768, DS3ItemCategory.ARMOR), - ("Smough's Gauntlets", 0x159A5B50, DS3ItemCategory.ARMOR), - ("Smough's Leggings", 0x159A5F38, DS3ItemCategory.ARMOR), - ("Helm of Thorns", 0x15B8D800, DS3ItemCategory.ARMOR), - ("Armor of Thorns", 0x15B8DBE8, DS3ItemCategory.ARMOR), - ("Gauntlets of Thorns", 0x15B8DFD0, DS3ItemCategory.ARMOR), - ("Leggings of Thorns", 0x15B8E3B8, DS3ItemCategory.ARMOR), - ("Crown of Dusk", 0x15D75C80, DS3ItemCategory.ARMOR), - ("Antiquated Dress", 0x15D76068, DS3ItemCategory.ARMOR), - ("Antiquated Gloves", 0x15D76450, DS3ItemCategory.ARMOR), - ("Antiquated Skirt", 0x15D76838, DS3ItemCategory.ARMOR), - ("Karla's Pointed Hat", 0x15E69EC0, DS3ItemCategory.ARMOR), - ("Karla's Coat", 0x15E6A2A8, DS3ItemCategory.ARMOR), - ("Karla's Gloves", 0x15E6A690, DS3ItemCategory.ARMOR), - ("Karla's Trousers", 0x15E6AA78, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Helm", 0x1121EAC0, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Armor", 0x1121EEA8, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Gauntlets", 0x1121F290, DS3ItemCategory.ARMOR), + DS3ItemData("Fallen Knight Trousers", 0x1121F678, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Helm", 0x11298BE0, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Armor", 0x11298FC8, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Gauntlets", 0x112993B0, DS3ItemCategory.ARMOR), + DS3ItemData("Knight Leggings", 0x11299798, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Helm", 0x11406F40, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Armor", 0x11407328, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Gauntlets", 0x11407710, DS3ItemCategory.ARMOR), + DS3ItemData("Firelink Leggings", 0x11407AF8, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Helm", 0x11481060, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Armor", 0x11481448, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Gauntlet", 0x11481830, DS3ItemCategory.ARMOR), + DS3ItemData("Sellsword Trousers", 0x11481C18, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Helm", 0x114FB180, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Armor", 0x114FB568, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Gloves", 0x114FB950, DS3ItemCategory.ARMOR), + DS3ItemData("Herald Trousers", 0x114FBD38, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Veil", 0x115752A0, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Armor", 0x11575688, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Gauntlets", 0x11575A70, DS3ItemCategory.ARMOR), + DS3ItemData("Sunless Leggings", 0x11575E58, DS3ItemCategory.ARMOR), + DS3ItemData("Black Hand Hat", 0x115EF3C0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Hand Armor", 0x115EF7A8, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Gloves", 0x115EFB90, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Trousers", 0x115EFF78, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Hood", 0x11607A60, DS3ItemCategory.ARMOR), + DS3ItemData("Assassin Armor", 0x11607E48, DS3ItemCategory.ARMOR), + DS3ItemData("Xanthous Crown", 0x116694E0, DS3ItemCategory.ARMOR, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Xanthous Overcoat", 0x116698C8, DS3ItemCategory.ARMOR), + DS3ItemData("Xanthous Gloves", 0x11669CB0, DS3ItemCategory.ARMOR), + DS3ItemData("Xanthous Trousers", 0x1166A098, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Helm", 0x116E3600, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Armor", 0x116E39E8, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Gloves", 0x116E3DD0, DS3ItemCategory.ARMOR), + DS3ItemData("Northern Trousers", 0x116E41B8, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Helm", 0x1175D720, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Armor", 0x1175DB08, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Gauntlets", 0x1175DEF0, DS3ItemCategory.ARMOR), + DS3ItemData("Morne's Leggings", 0x1175E2D8, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Mask", 0x117D7840, DS3ItemCategory.ARMOR), + DS3ItemData("Leonhard's Garb", 0x117D7C28, DS3ItemCategory.ARMOR), + DS3ItemData("Leonhard's Gauntlets", 0x117D8010, DS3ItemCategory.ARMOR), + DS3ItemData("Leonhard's Trousers", 0x117D83F8, DS3ItemCategory.ARMOR), + DS3ItemData("Sneering Mask", 0x11851960, DS3ItemCategory.ARMOR), + DS3ItemData("Pale Shade Robe", 0x11851D48, DS3ItemCategory.ARMOR), + DS3ItemData("Pale Shade Gloves", 0x11852130, DS3ItemCategory.ARMOR), + DS3ItemData("Pale Shade Trousers", 0x11852518, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Helm", 0x118CBA80, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Armor", 0x118CBE68, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Gauntlets", 0x118CC250, DS3ItemCategory.ARMOR), + DS3ItemData("Sunset Leggings", 0x118CC638, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sage's Blindfold", 0x11945BA0, DS3ItemCategory.ARMOR), + DS3ItemData("Cornyx's Garb", 0x11945F88, DS3ItemCategory.ARMOR), + DS3ItemData("Cornyx's Wrap", 0x11946370, DS3ItemCategory.ARMOR), + DS3ItemData("Cornyx's Skirt", 0x11946758, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Helm", 0x119BFCC0, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Armor", 0x119C00A8, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Gauntlets", 0x119C0490, DS3ItemCategory.ARMOR), + DS3ItemData("Executioner Leggings", 0x119C0878, DS3ItemCategory.ARMOR), + DS3ItemData("Billed Mask", 0x11A39DE0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Dress", 0x11A3A1C8, DS3ItemCategory.ARMOR), + DS3ItemData("Black Gauntlets", 0x11A3A5B0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leggings", 0x11A3A998, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Crown", 0x11AB3F00, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Garb", 0x11AB42E8, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Wrap", 0x11AB46D0, DS3ItemCategory.ARMOR), + DS3ItemData("Pyromancer Trousers", 0x11AB4AB8, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Hood", 0x11BA8140, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Robe", 0x11BA8528, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Gloves", 0x11BA8910, DS3ItemCategory.ARMOR), + DS3ItemData("Court Sorcerer Trousers", 0x11BA8CF8, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Hood", 0x11C9C380, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Robe", 0x11C9C768, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Gloves", 0x11C9CB50, DS3ItemCategory.ARMOR), + DS3ItemData("Sorcerer Trousers", 0x11C9CF38, DS3ItemCategory.ARMOR), + DS3ItemData("Clandestine Coat", 0x11CB4E08, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Hat", 0x11D905C0, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Blue Robe", 0x11D909A8, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Gloves", 0x11D90D90, DS3ItemCategory.ARMOR), + DS3ItemData("Cleric Trousers", 0x11D91178, DS3ItemCategory.ARMOR), + DS3ItemData("Steel Soldier Helm", 0x12625A00, DS3ItemCategory.ARMOR), + DS3ItemData("Deserter Armor", 0x12625DE8, DS3ItemCategory.ARMOR), + DS3ItemData("Deserter Trousers", 0x126265B8, DS3ItemCategory.ARMOR), + DS3ItemData("Thief Mask", 0x12656740, DS3ItemCategory.ARMOR), + DS3ItemData("Sage's Big Hat", 0x129020C0, DS3ItemCategory.ARMOR), + DS3ItemData("Aristocrat's Mask", 0x129F6300, DS3ItemCategory.ARMOR), + DS3ItemData("Jailer Robe", 0x129F66E8, DS3ItemCategory.ARMOR), + DS3ItemData("Jailer Gloves", 0x129F6AD0, DS3ItemCategory.ARMOR), + DS3ItemData("Jailer Trousers", 0x129F6EB8, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Hood", 0x12BDE780, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Robe", 0x12BDEB68, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Wrap", 0x12BDEF50, DS3ItemCategory.ARMOR), + DS3ItemData("Grave Warden Skirt", 0x12BDF338, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Hat", 0x12CD29C0, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Garb", 0x12CD2DA8, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Gloves", 0x12CD3190, DS3ItemCategory.ARMOR), + DS3ItemData("Worker Trousers", 0x12CD3578, DS3ItemCategory.ARMOR), + DS3ItemData("Thrall Hood", 0x12D4CAE0, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Hat", 0x12DC6C00, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Robe", 0x12DC6FE8, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Gloves", 0x12DC73D0, DS3ItemCategory.ARMOR), + DS3ItemData("Evangelist Trousers", 0x12DC77B8, DS3ItemCategory.ARMOR), + DS3ItemData("Scholar's Robe", 0x12E41108, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Helm", 0x12EBAE40, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Armor", 0x12EBB228, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Gauntlets", 0x12EBB610, DS3ItemCategory.ARMOR), + DS3ItemData("Winged Knight Leggings", 0x12EBB9F8, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Helm", 0x130291A0, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Armor", 0x13029588, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Gauntlets", 0x13029970, DS3ItemCategory.ARMOR), + DS3ItemData("Cathedral Knight Leggings", 0x13029D58, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Helm", 0x13197500, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Armor", 0x131978E8, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Gauntlets", 0x13197CD0, DS3ItemCategory.ARMOR), + DS3ItemData("Lothric Knight Leggings", 0x131980B8, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Helm", 0x1328B740, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Armor", 0x1328BB28, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Gauntlets", 0x1328BF10, DS3ItemCategory.ARMOR), + DS3ItemData("Outrider Knight Leggings", 0x1328C2F8, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Helm", 0x1337F980, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Armor", 0x1337FD68, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Gauntlets", 0x13380150, DS3ItemCategory.ARMOR), + DS3ItemData("Black Knight Leggings", 0x13380538, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Mask", 0x133F9AA0, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Armor", 0x133F9E88, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Gauntlets", 0x133FA270, DS3ItemCategory.ARMOR), + DS3ItemData("Dark Leggings", 0x133FA658, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Mask", 0x13473BC0, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Armor", 0x13473FA8, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Gauntlets", 0x13474390, DS3ItemCategory.ARMOR), + DS3ItemData("Exile Leggings", 0x13474778, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Crown", 0x13567E00, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Armor", 0x135681E8, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Gauntlets", 0x135685D0, DS3ItemCategory.ARMOR), + DS3ItemData("Pontiff Knight Leggings", 0x135689B8, DS3ItemCategory.ARMOR), + DS3ItemData("Golden Crown", 0x1365C040, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonscale Armor", 0x1365C428, DS3ItemCategory.ARMOR), + DS3ItemData("Golden Bracelets", 0x1365C810, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonscale Waistcloth", 0x1365CBF8, DS3ItemCategory.ARMOR), + DS3ItemData("Wolnir's Crown", 0x136D6160, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Helm", 0x13750280, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Armor", 0x13750668, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Gauntlet", 0x13750A50, DS3ItemCategory.ARMOR), + DS3ItemData("Undead Legion Leggings", 0x13750E38, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Helm", 0x13938700, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Armor", 0x13938AE8, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Gauntlets", 0x13938ED0, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Witch Leggings", 0x139392B8, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Helm", 0x13A2C940, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Armor", 0x13A2CD28, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Gauntlets", 0x13A2D110, DS3ItemCategory.ARMOR), + DS3ItemData("Lorian's Leggings", 0x13A2D4F8, DS3ItemCategory.ARMOR), + DS3ItemData("Hood of Prayer", 0x13AA6A60, DS3ItemCategory.ARMOR), + DS3ItemData("Robe of Prayer", 0x13AA6E48, DS3ItemCategory.ARMOR), + DS3ItemData("Skirt of Prayer", 0x13AA7618, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Crown", 0x13C14DC0, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Armor", 0x13C151A8, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Gauntlets", 0x13C15590, DS3ItemCategory.ARMOR), + DS3ItemData("Dancer's Leggings", 0x13C15978, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Helm", 0x13D09000, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Armor", 0x13D093E8, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Gauntlets", 0x13D097D0, DS3ItemCategory.ARMOR), + DS3ItemData("Gundyr's Leggings", 0x13D09BB8, DS3ItemCategory.ARMOR), + DS3ItemData("Archdeacon White Crown", 0x13EF1480, DS3ItemCategory.ARMOR), + DS3ItemData("Archdeacon Holy Garb", 0x13EF1868, DS3ItemCategory.ARMOR), + DS3ItemData("Archdeacon Skirt", 0x13EF2038, DS3ItemCategory.ARMOR), + DS3ItemData("Deacon Robe", 0x13F6B988, DS3ItemCategory.ARMOR), + DS3ItemData("Deacon Skirt", 0x13F6C158, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Keeper Robe", 0x140D9CE8, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Keeper Gloves", 0x140DA0D0, DS3ItemCategory.ARMOR), + DS3ItemData("Fire Keeper Skirt", 0x140DA4B8, DS3ItemCategory.ARMOR), + DS3ItemData("Chain Helm", 0x142C1D80, DS3ItemCategory.ARMOR), + DS3ItemData("Chain Armor", 0x142C2168, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Gauntlets", 0x142C2550, DS3ItemCategory.ARMOR), + DS3ItemData("Chain Leggings", 0x142C2938, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Helm", 0x143B5FC0, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Armor", 0x143B63A8, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Gauntlets", 0x143B6790, DS3ItemCategory.ARMOR), + DS3ItemData("Nameless Knight Leggings", 0x143B6B78, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Helm", 0x144AA200, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Armor", 0x144AA5E8, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Gauntlets", 0x144AA9D0, DS3ItemCategory.ARMOR), + DS3ItemData("Elite Knight Leggings", 0x144AADB8, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Helm", 0x1459E440, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Armor", 0x1459E828, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Gauntlets", 0x1459EC10, DS3ItemCategory.ARMOR), + DS3ItemData("Faraam Boots", 0x1459EFF8, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Helm", 0x14692680, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Armor", 0x14692A68, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Gauntlets", 0x14692E50, DS3ItemCategory.ARMOR), + DS3ItemData("Catarina Leggings", 0x14693238, DS3ItemCategory.ARMOR), + DS3ItemData("Standard Helm", 0x1470C7A0, DS3ItemCategory.ARMOR), + DS3ItemData("Hard Leather Armor", 0x1470CB88, DS3ItemCategory.ARMOR), + DS3ItemData("Hard Leather Gauntlets", 0x1470CF70, DS3ItemCategory.ARMOR), + DS3ItemData("Hard Leather Boots", 0x1470D358, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Helm", 0x147868C0, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Armor", 0x14786CA8, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Gauntlets", 0x14787090, DS3ItemCategory.ARMOR), + DS3ItemData("Havel's Leggings", 0x14787478, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Hood", 0x148009E0, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Armor", 0x14800DC8, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Gauntlets", 0x148011B0, DS3ItemCategory.ARMOR), + DS3ItemData("Brigand Trousers", 0x14801598, DS3ItemCategory.ARMOR), + DS3ItemData("Pharis's Hat", 0x1487AB00, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Armor", 0x1487AEE8, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Gloves", 0x1487B2D0, DS3ItemCategory.ARMOR), + DS3ItemData("Leather Boots", 0x1487B6B8, DS3ItemCategory.ARMOR), + DS3ItemData("Ragged Mask", 0x148F4C20, DS3ItemCategory.ARMOR), + DS3ItemData("Master's Attire", 0x148F5008, DS3ItemCategory.ARMOR), + DS3ItemData("Master's Gloves", 0x148F53F0, DS3ItemCategory.ARMOR), + DS3ItemData("Loincloth", 0x148F57D8, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Hat", 0x1496ED40, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Coat", 0x1496F128, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Gauntlets", 0x1496F510, DS3ItemCategory.ARMOR), + DS3ItemData("Old Sorcerer Boots", 0x1496F8F8, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Hood", 0x149E8E60, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Robe", 0x149E9248, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Manchettes", 0x149E9630, DS3ItemCategory.ARMOR), + DS3ItemData("Conjurator Boots", 0x149E9A18, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leather Armor", 0x14A63368, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leather Gloves", 0x14A63750, DS3ItemCategory.ARMOR), + DS3ItemData("Black Leather Boots", 0x14A63B38, DS3ItemCategory.ARMOR), + DS3ItemData("Symbol of Avarice", 0x14ADD0A0, DS3ItemCategory.ARMOR), + DS3ItemData("Creighton's Steel Mask", 0x14B571C0, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Chain Mail", 0x14B575A8, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Chain Gloves", 0x14B57990, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Chain Leggings", 0x14B57D78, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Hood", 0x14BD12E0, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Robe", 0x14BD16C8, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Gloves", 0x14BD1AB0, DS3ItemCategory.ARMOR), + DS3ItemData("Maiden Skirt", 0x14BD1E98, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Helm", 0x14C4B400, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Armor", 0x14C4B7E8, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Gauntlets", 0x14C4BBD0, DS3ItemCategory.ARMOR), + DS3ItemData("Alva Leggings", 0x14C4BFB8, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Mask", 0x14D3F640, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Garb", 0x14D3FA28, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Gauntlets", 0x14D3FE10, DS3ItemCategory.ARMOR), + DS3ItemData("Shadow Leggings", 0x14D401F8, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Helm", 0x14E33880, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Armor", 0x14E33C68, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Gauntlets", 0x14E34050, DS3ItemCategory.ARMOR), + DS3ItemData("Eastern Leggings", 0x14E34438, DS3ItemCategory.ARMOR), + DS3ItemData("Helm of Favor", 0x14F27AC0, DS3ItemCategory.ARMOR), + DS3ItemData("Embraced Armor of Favor", 0x14F27EA8, DS3ItemCategory.ARMOR), + DS3ItemData("Gauntlets of Favor", 0x14F28290, DS3ItemCategory.ARMOR), + DS3ItemData("Leggings of Favor", 0x14F28678, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Helm", 0x1501BD00, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Armor", 0x1501C0E8, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Gauntlets", 0x1501C4D0, DS3ItemCategory.ARMOR), + DS3ItemData("Brass Leggings", 0x1501C8B8, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Helm", 0x1510FF40, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Armor", 0x15110328, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Gauntlets", 0x15110710, DS3ItemCategory.ARMOR), + DS3ItemData("Silver Knight Leggings", 0x15110AF8, DS3ItemCategory.ARMOR), + DS3ItemData("Lucatiel's Mask", 0x15204180, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Vest", 0x15204568, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Gloves", 0x15204950, DS3ItemCategory.ARMOR), + DS3ItemData("Mirrah Trousers", 0x15204D38, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Helm", 0x152F83C0, DS3ItemCategory.ARMOR), + DS3ItemData("Armor of the Sun", 0x152F87A8, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Bracelets", 0x152F8B90, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Leggings", 0x152F8F78, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Helm", 0x153EC600, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Armor", 0x153EC9E8, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Gauntlets", 0x153ECDD0, DS3ItemCategory.ARMOR), + DS3ItemData("Drakeblood Leggings", 0x153ED1B8, DS3ItemCategory.ARMOR), + DS3ItemData("Drang Armor", 0x154E0C28, DS3ItemCategory.ARMOR), + DS3ItemData("Drang Gauntlets", 0x154E1010, DS3ItemCategory.ARMOR), + DS3ItemData("Drang Shoes", 0x154E13F8, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Helm", 0x155D4A80, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Armor", 0x155D4E68, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Gauntlets", 0x155D5250, DS3ItemCategory.ARMOR), + DS3ItemData("Black Iron Leggings", 0x155D5638, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Hood", 0x156C8CC0, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Gown", 0x156C90A8, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Gloves", 0x156C9490, DS3ItemCategory.ARMOR), + DS3ItemData("Painting Guardian Waistcloth", 0x156C9878, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Helm", 0x157BCF00, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Armor", 0x157BD2E8, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Gauntlets", 0x157BD6D0, DS3ItemCategory.ARMOR), + DS3ItemData("Wolf Knight Leggings", 0x157BDAB8, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Helm", 0x158B1140, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Armor", 0x158B1528, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Gauntlets", 0x158B1910, DS3ItemCategory.ARMOR), + DS3ItemData("Dragonslayer Leggings", 0x158B1CF8, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Helm", 0x159A5380, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Armor", 0x159A5768, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Gauntlets", 0x159A5B50, DS3ItemCategory.ARMOR), + DS3ItemData("Smough's Leggings", 0x159A5F38, DS3ItemCategory.ARMOR), + DS3ItemData("Helm of Thorns", 0x15B8D800, DS3ItemCategory.ARMOR), + DS3ItemData("Armor of Thorns", 0x15B8DBE8, DS3ItemCategory.ARMOR), + DS3ItemData("Gauntlets of Thorns", 0x15B8DFD0, DS3ItemCategory.ARMOR), + DS3ItemData("Leggings of Thorns", 0x15B8E3B8, DS3ItemCategory.ARMOR), + DS3ItemData("Crown of Dusk", 0x15D75C80, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Dress", 0x15D76068, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Gloves", 0x15D76450, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Skirt", 0x15D76838, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Pointed Hat", 0x15E69EC0, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Coat", 0x15E6A2A8, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Gloves", 0x15E6A690, DS3ItemCategory.ARMOR), + DS3ItemData("Karla's Trousers", 0x15E6AA78, DS3ItemCategory.ARMOR), # Covenants - ("Blade of the Darkmoon", 0x20002710, DS3ItemCategory.SKIP), - ("Watchdogs of Farron", 0x20002724, DS3ItemCategory.SKIP), - ("Aldrich Faithful", 0x2000272E, DS3ItemCategory.SKIP), - ("Warrior of Sunlight", 0x20002738, DS3ItemCategory.SKIP), - ("Mound-makers", 0x20002742, DS3ItemCategory.SKIP), - ("Way of Blue", 0x2000274C, DS3ItemCategory.SKIP), - ("Blue Sentinels", 0x20002756, DS3ItemCategory.SKIP), - ("Rosaria's Fingers", 0x20002760, DS3ItemCategory.SKIP), + DS3ItemData("Blade of the Darkmoon", 0x20002710, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Watchdogs of Farron", 0x20002724, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Aldrich Faithful", 0x2000272E, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Warrior of Sunlight", 0x20002738, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Mound-makers", 0x20002742, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Way of Blue", 0x2000274C, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Blue Sentinels", 0x20002756, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Rosaria's Fingers", 0x20002760, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Spears of the Church", 0x2000276A, DS3ItemCategory.UNIQUE, skip = True), # Rings - ("Life Ring", 0x20004E20, DS3ItemCategory.RING), - ("Life Ring+1", 0x20004E21, DS3ItemCategory.RING), - ("Life Ring+2", 0x20004E22, DS3ItemCategory.RING), - ("Life Ring+3", 0x20004E23, DS3ItemCategory.RING), - ("Chloranthy Ring", 0x20004E2A, DS3ItemCategory.RING), - ("Chloranthy Ring+1", 0x20004E2B, DS3ItemCategory.RING), - ("Chloranthy Ring+2", 0x20004E2C, DS3ItemCategory.RING), - ("Havel's Ring", 0x20004E34, DS3ItemCategory.RING), - ("Havel's Ring+1", 0x20004E35, DS3ItemCategory.RING), - ("Havel's Ring+2", 0x20004E36, DS3ItemCategory.RING), - ("Ring of Favor", 0x20004E3E, DS3ItemCategory.RING), - ("Ring of Favor+1", 0x20004E3F, DS3ItemCategory.RING), - ("Ring of Favor+2", 0x20004E40, DS3ItemCategory.RING), - ("Ring of Steel Protection", 0x20004E48, DS3ItemCategory.RING), - ("Ring of Steel Protection+1", 0x20004E49, DS3ItemCategory.RING), - ("Ring of Steel Protection+2", 0x20004E4A, DS3ItemCategory.RING), - ("Flame Stoneplate Ring", 0x20004E52, DS3ItemCategory.RING), - ("Flame Stoneplate Ring+1", 0x20004E53, DS3ItemCategory.RING), - ("Flame Stoneplate Ring+2", 0x20004E54, DS3ItemCategory.RING), - ("Thunder Stoneplate Ring", 0x20004E5C, DS3ItemCategory.RING), - ("Thunder Stoneplate Ring+1", 0x20004E5D, DS3ItemCategory.RING), - ("Thunder Stoneplate Ring+2", 0x20004E5E, DS3ItemCategory.RING), - ("Magic Stoneplate Ring", 0x20004E66, DS3ItemCategory.RING), - ("Magic Stoneplate Ring+1", 0x20004E67, DS3ItemCategory.RING), - ("Magic Stoneplate Ring+2", 0x20004E68, DS3ItemCategory.RING), - ("Dark Stoneplate Ring", 0x20004E70, DS3ItemCategory.RING), - ("Dark Stoneplate Ring+1", 0x20004E71, DS3ItemCategory.RING), - ("Dark Stoneplate Ring+2", 0x20004E72, DS3ItemCategory.RING), - ("Speckled Stoneplate Ring", 0x20004E7A, DS3ItemCategory.RING), - ("Speckled Stoneplate Ring+1", 0x20004E7B, DS3ItemCategory.RING), - ("Bloodbite Ring", 0x20004E84, DS3ItemCategory.RING), - ("Bloodbite Ring+1", 0x20004E85, DS3ItemCategory.RING), - ("Poisonbite Ring", 0x20004E8E, DS3ItemCategory.RING), - ("Poisonbite Ring+1", 0x20004E8F, DS3ItemCategory.RING), - ("Cursebite Ring", 0x20004E98, DS3ItemCategory.RING), - ("Fleshbite Ring", 0x20004EA2, DS3ItemCategory.RING), - ("Fleshbite Ring+1", 0x20004EA3, DS3ItemCategory.RING), - ("Wood Grain Ring", 0x20004EAC, DS3ItemCategory.RING), - ("Wood Grain Ring+1", 0x20004EAD, DS3ItemCategory.RING), - ("Wood Grain Ring+2", 0x20004EAE, DS3ItemCategory.RING), - ("Scholar Ring", 0x20004EB6, DS3ItemCategory.RING), - ("Priestess Ring", 0x20004EC0, DS3ItemCategory.RING), - ("Red Tearstone Ring", 0x20004ECA, DS3ItemCategory.RING), - ("Blue Tearstone Ring", 0x20004ED4, DS3ItemCategory.RING), - ("Wolf Ring", 0x20004EDE, DS3ItemCategory.RING), - ("Wolf Ring+1", 0x20004EDF, DS3ItemCategory.RING), - ("Wolf Ring+2", 0x20004EE0, DS3ItemCategory.RING), - ("Leo Ring", 0x20004EE8, DS3ItemCategory.RING), - ("Ring of Sacrifice", 0x20004EF2, DS3ItemCategory.RING), - ("Young Dragon Ring", 0x20004F06, DS3ItemCategory.RING), - ("Bellowing Dragoncrest Ring", 0x20004F07, DS3ItemCategory.RING), - ("Great Swamp Ring", 0x20004F10, DS3ItemCategory.RING), - ("Witch's Ring", 0x20004F11, DS3ItemCategory.RING), - ("Morne's Ring", 0x20004F1A, DS3ItemCategory.RING), - ("Ring of the Sun's First Born", 0x20004F1B, DS3ItemCategory.RING), - ("Lingering Dragoncrest Ring", 0x20004F2E, DS3ItemCategory.RING), - ("Lingering Dragoncrest Ring+1", 0x20004F2F, DS3ItemCategory.RING), - ("Lingering Dragoncrest Ring+2", 0x20004F30, DS3ItemCategory.RING), - ("Sage Ring", 0x20004F38, DS3ItemCategory.RING), - ("Sage Ring+1", 0x20004F39, DS3ItemCategory.RING), - ("Sage Ring+2", 0x20004F3A, DS3ItemCategory.RING), - ("Slumbering Dragoncrest Ring", 0x20004F42, DS3ItemCategory.RING), - ("Dusk Crown Ring", 0x20004F4C, DS3ItemCategory.RING), - ("Saint's Ring", 0x20004F56, DS3ItemCategory.RING), - ("Deep Ring", 0x20004F60, DS3ItemCategory.RING), - ("Darkmoon Ring", 0x20004F6A, DS3ItemCategory.RING), - ("Hawk Ring", 0x20004F92, DS3ItemCategory.RING), - ("Hornet Ring", 0x20004F9C, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring", 0x20004FA6, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring+1", 0x20004FA7, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring+2", 0x20004FA8, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring", 0x20004FB0, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring+1", 0x20004FB1, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring+2", 0x20004FB2, DS3ItemCategory.RING), - ("Sun Princess Ring", 0x20004FBA, DS3ItemCategory.RING), - ("Silvercat Ring", 0x20004FC4, DS3ItemCategory.RING), - ("Skull Ring", 0x20004FCE, DS3ItemCategory.RING), - ("Untrue White Ring", 0x20004FD8, DS3ItemCategory.RING), - ("Carthus Milkring", 0x20004FE2, DS3ItemCategory.RING), - ("Knight's Ring", 0x20004FEC, DS3ItemCategory.RING), - ("Hunter's Ring", 0x20004FF6, DS3ItemCategory.RING), - ("Knight Slayer's Ring", 0x20005000, DS3ItemCategory.RING), - ("Magic Clutch Ring", 0x2000500A, DS3ItemCategory.RING), - ("Lightning Clutch Ring", 0x20005014, DS3ItemCategory.RING), - ("Fire Clutch Ring", 0x2000501E, DS3ItemCategory.RING), - ("Dark Clutch Ring", 0x20005028, DS3ItemCategory.RING), - ("Flynn's Ring", 0x2000503C, DS3ItemCategory.RING), - ("Prisoner's Chain", 0x20005046, DS3ItemCategory.RING), - ("Untrue Dark Ring", 0x20005050, DS3ItemCategory.RING), - ("Obscuring Ring", 0x20005064, DS3ItemCategory.RING), - ("Ring of the Evil Eye", 0x2000506E, DS3ItemCategory.RING), - ("Ring of the Evil Eye+1", 0x2000506F, DS3ItemCategory.RING), - ("Ring of the Evil Eye+2", 0x20005070, DS3ItemCategory.RING), - ("Calamity Ring", 0x20005078, DS3ItemCategory.RING), - ("Farron Ring", 0x20005082, DS3ItemCategory.RING), - ("Aldrich's Ruby", 0x2000508C, DS3ItemCategory.RING), - ("Aldrich's Sapphire", 0x20005096, DS3ItemCategory.RING), - ("Lloyd's Sword Ring", 0x200050B4, DS3ItemCategory.RING), - ("Lloyd's Shield Ring", 0x200050BE, DS3ItemCategory.RING), - ("Estus Ring", 0x200050DC, DS3ItemCategory.RING), - ("Ashen Estus Ring", 0x200050E6, DS3ItemCategory.RING), - ("Carthus Bloodring", 0x200050FA, DS3ItemCategory.RING), - ("Reversal Ring", 0x20005104, DS3ItemCategory.RING), - ("Pontiff's Right Eye", 0x2000510E, DS3ItemCategory.RING), - ("Pontiff's Left Eye", 0x20005136, DS3ItemCategory.RING), - ("Dragonscale Ring", 0x2000515E, DS3ItemCategory.RING), + DS3ItemData("Life Ring", 0x20004E20, DS3ItemCategory.RING), + DS3ItemData("Life Ring+1", 0x20004E21, DS3ItemCategory.RING), + DS3ItemData("Life Ring+2", 0x20004E22, DS3ItemCategory.RING), + DS3ItemData("Life Ring+3", 0x20004E23, DS3ItemCategory.RING), + DS3ItemData("Chloranthy Ring", 0x20004E2A, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Chloranthy Ring+1", 0x20004E2B, DS3ItemCategory.RING), + DS3ItemData("Chloranthy Ring+2", 0x20004E2C, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Havel's Ring", 0x20004E34, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Havel's Ring+1", 0x20004E35, DS3ItemCategory.RING), + DS3ItemData("Havel's Ring+2", 0x20004E36, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Ring of Favor", 0x20004E3E, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Ring of Favor+1", 0x20004E3F, DS3ItemCategory.RING), + DS3ItemData("Ring of Favor+2", 0x20004E40, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Ring of Steel Protection", 0x20004E48, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Ring of Steel Protection+1", 0x20004E49, DS3ItemCategory.RING), + DS3ItemData("Ring of Steel Protection+2", 0x20004E4A, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Flame Stoneplate Ring", 0x20004E52, DS3ItemCategory.RING), + DS3ItemData("Flame Stoneplate Ring+1", 0x20004E53, DS3ItemCategory.RING), + DS3ItemData("Flame Stoneplate Ring+2", 0x20004E54, DS3ItemCategory.RING), + DS3ItemData("Thunder Stoneplate Ring", 0x20004E5C, DS3ItemCategory.RING), + DS3ItemData("Thunder Stoneplate Ring+1", 0x20004E5D, DS3ItemCategory.RING), + DS3ItemData("Thunder Stoneplate Ring+2", 0x20004E5E, DS3ItemCategory.RING), + DS3ItemData("Magic Stoneplate Ring", 0x20004E66, DS3ItemCategory.RING), + DS3ItemData("Magic Stoneplate Ring+1", 0x20004E67, DS3ItemCategory.RING), + DS3ItemData("Magic Stoneplate Ring+2", 0x20004E68, DS3ItemCategory.RING), + DS3ItemData("Dark Stoneplate Ring", 0x20004E70, DS3ItemCategory.RING), + DS3ItemData("Dark Stoneplate Ring+1", 0x20004E71, DS3ItemCategory.RING), + DS3ItemData("Dark Stoneplate Ring+2", 0x20004E72, DS3ItemCategory.RING), + DS3ItemData("Speckled Stoneplate Ring", 0x20004E7A, DS3ItemCategory.RING), + DS3ItemData("Speckled Stoneplate Ring+1", 0x20004E7B, DS3ItemCategory.RING), + DS3ItemData("Bloodbite Ring", 0x20004E84, DS3ItemCategory.RING), + DS3ItemData("Bloodbite Ring+1", 0x20004E85, DS3ItemCategory.RING), + DS3ItemData("Poisonbite Ring", 0x20004E8E, DS3ItemCategory.RING), + DS3ItemData("Poisonbite Ring+1", 0x20004E8F, DS3ItemCategory.RING), + DS3ItemData("Cursebite Ring", 0x20004E98, DS3ItemCategory.RING), + DS3ItemData("Fleshbite Ring", 0x20004EA2, DS3ItemCategory.RING), + DS3ItemData("Fleshbite Ring+1", 0x20004EA3, DS3ItemCategory.RING), + DS3ItemData("Wood Grain Ring", 0x20004EAC, DS3ItemCategory.RING), + DS3ItemData("Wood Grain Ring+1", 0x20004EAD, DS3ItemCategory.RING), + DS3ItemData("Wood Grain Ring+2", 0x20004EAE, DS3ItemCategory.RING), + DS3ItemData("Scholar Ring", 0x20004EB6, DS3ItemCategory.RING), + DS3ItemData("Priestess Ring", 0x20004EC0, DS3ItemCategory.RING), + DS3ItemData("Red Tearstone Ring", 0x20004ECA, DS3ItemCategory.RING), + DS3ItemData("Blue Tearstone Ring", 0x20004ED4, DS3ItemCategory.RING), + DS3ItemData("Wolf Ring", 0x20004EDE, DS3ItemCategory.RING, + inject = True), # Covenant reward + DS3ItemData("Wolf Ring+1", 0x20004EDF, DS3ItemCategory.RING), + DS3ItemData("Wolf Ring+2", 0x20004EE0, DS3ItemCategory.RING), + DS3ItemData("Leo Ring", 0x20004EE8, DS3ItemCategory.RING), + DS3ItemData("Ring of Sacrifice", 0x20004EF2, DS3ItemCategory.RING, filler = True), + DS3ItemData("Young Dragon Ring", 0x20004F06, DS3ItemCategory.RING), + DS3ItemData("Bellowing Dragoncrest Ring", 0x20004F07, DS3ItemCategory.RING), + DS3ItemData("Great Swamp Ring", 0x20004F10, DS3ItemCategory.RING), + DS3ItemData("Witch's Ring", 0x20004F11, DS3ItemCategory.RING), + DS3ItemData("Morne's Ring", 0x20004F1A, DS3ItemCategory.RING), + DS3ItemData("Ring of the Sun's First Born", 0x20004F1B, DS3ItemCategory.RING), + DS3ItemData("Lingering Dragoncrest Ring", 0x20004F2E, DS3ItemCategory.RING), + DS3ItemData("Lingering Dragoncrest Ring+1", 0x20004F2F, DS3ItemCategory.RING), + DS3ItemData("Lingering Dragoncrest Ring+2", 0x20004F30, DS3ItemCategory.RING), + DS3ItemData("Sage Ring", 0x20004F38, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_NGP), + DS3ItemData("Sage Ring+1", 0x20004F39, DS3ItemCategory.RING), + DS3ItemData("Sage Ring+2", 0x20004F3A, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Slumbering Dragoncrest Ring", 0x20004F42, DS3ItemCategory.RING), + DS3ItemData("Dusk Crown Ring", 0x20004F4C, DS3ItemCategory.RING), + DS3ItemData("Saint's Ring", 0x20004F56, DS3ItemCategory.RING), + DS3ItemData("Deep Ring", 0x20004F60, DS3ItemCategory.RING), + DS3ItemData("Darkmoon Ring", 0x20004F6A, DS3ItemCategory.RING, + inject = True), # Covenant reward + DS3ItemData("Hawk Ring", 0x20004F92, DS3ItemCategory.RING), + DS3ItemData("Hornet Ring", 0x20004F9C, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring", 0x20004FA6, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring+1", 0x20004FA7, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring+2", 0x20004FA8, DS3ItemCategory.RING), + DS3ItemData("Covetous Silver Serpent Ring", 0x20004FB0, DS3ItemCategory.RING, + useful_if = UsefulIf.BASE), + DS3ItemData("Covetous Silver Serpent Ring+1", 0x20004FB1, DS3ItemCategory.RING), + DS3ItemData("Covetous Silver Serpent Ring+2", 0x20004FB2, DS3ItemCategory.RING, + useful_if = UsefulIf.NO_DLC), + DS3ItemData("Sun Princess Ring", 0x20004FBA, DS3ItemCategory.RING), + DS3ItemData("Silvercat Ring", 0x20004FC4, DS3ItemCategory.RING), + DS3ItemData("Skull Ring", 0x20004FCE, DS3ItemCategory.RING), + DS3ItemData("Untrue White Ring", 0x20004FD8, DS3ItemCategory.RING, skip = True), + DS3ItemData("Carthus Milkring", 0x20004FE2, DS3ItemCategory.RING), + DS3ItemData("Knight's Ring", 0x20004FEC, DS3ItemCategory.RING), + DS3ItemData("Hunter's Ring", 0x20004FF6, DS3ItemCategory.RING), + DS3ItemData("Knight Slayer's Ring", 0x20005000, DS3ItemCategory.RING), + DS3ItemData("Magic Clutch Ring", 0x2000500A, DS3ItemCategory.RING), + DS3ItemData("Lightning Clutch Ring", 0x20005014, DS3ItemCategory.RING), + DS3ItemData("Fire Clutch Ring", 0x2000501E, DS3ItemCategory.RING), + DS3ItemData("Dark Clutch Ring", 0x20005028, DS3ItemCategory.RING), + DS3ItemData("Flynn's Ring", 0x2000503C, DS3ItemCategory.RING), + DS3ItemData("Prisoner's Chain", 0x20005046, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Untrue Dark Ring", 0x20005050, DS3ItemCategory.RING), + DS3ItemData("Obscuring Ring", 0x20005064, DS3ItemCategory.RING), + DS3ItemData("Ring of the Evil Eye", 0x2000506E, DS3ItemCategory.RING), + DS3ItemData("Ring of the Evil Eye+1", 0x2000506F, DS3ItemCategory.RING), + DS3ItemData("Ring of the Evil Eye+2", 0x20005070, DS3ItemCategory.RING), + DS3ItemData("Calamity Ring", 0x20005078, DS3ItemCategory.RING), + DS3ItemData("Farron Ring", 0x20005082, DS3ItemCategory.RING), + DS3ItemData("Aldrich's Ruby", 0x2000508C, DS3ItemCategory.RING), + DS3ItemData("Aldrich's Sapphire", 0x20005096, DS3ItemCategory.RING), + DS3ItemData("Lloyd's Sword Ring", 0x200050B4, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Lloyd's Shield Ring", 0x200050BE, DS3ItemCategory.RING), + DS3ItemData("Estus Ring", 0x200050DC, DS3ItemCategory.RING), + DS3ItemData("Ashen Estus Ring", 0x200050E6, DS3ItemCategory.RING), + DS3ItemData("Horsehoof Ring", 0x200050F0, DS3ItemCategory.RING), + DS3ItemData("Carthus Bloodring", 0x200050FA, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Reversal Ring", 0x20005104, DS3ItemCategory.RING), + DS3ItemData("Pontiff's Right Eye", 0x2000510E, DS3ItemCategory.RING), + DS3ItemData("Pontiff's Left Eye", 0x20005136, DS3ItemCategory.RING), + DS3ItemData("Dragonscale Ring", 0x2000515E, DS3ItemCategory.RING), # Items - ("Roster of Knights", 0x4000006C, DS3ItemCategory.SKIP), - ("Cracked Red Eye Orb", 0x4000006F, DS3ItemCategory.SKIP), - ("Divine Blessing", 0x400000F0, DS3ItemCategory.MISC), - ("Hidden Blessing", 0x400000F1, DS3ItemCategory.MISC), - ("Silver Pendant", 0x400000F2, DS3ItemCategory.SKIP), - ("Green Blossom", 0x40000104, DS3ItemCategory.MISC), - ("Budding Green Blossom", 0x40000106, DS3ItemCategory.MISC), - ("Bloodred Moss Clump", 0x4000010E, DS3ItemCategory.SKIP), - ("Purple Moss Clump", 0x4000010F, DS3ItemCategory.MISC), - ("Blooming Purple Moss Clump", 0x40000110, DS3ItemCategory.SKIP), - ("Purging Stone", 0x40000112, DS3ItemCategory.SKIP), - ("Rime-blue Moss Clump", 0x40000114, DS3ItemCategory.SKIP), - ("Repair Powder", 0x40000118, DS3ItemCategory.MISC), - ("Kukri", 0x40000122, DS3ItemCategory.SKIP), - ("Firebomb", 0x40000124, DS3ItemCategory.MISC), - ("Dung Pie", 0x40000125, DS3ItemCategory.SKIP), - ("Alluring Skull", 0x40000126, DS3ItemCategory.MISC), - ("Undead Hunter Charm", 0x40000128, DS3ItemCategory.MISC), - ("Black Firebomb", 0x40000129, DS3ItemCategory.MISC), - ("Rope Firebomb", 0x4000012B, DS3ItemCategory.MISC), - ("Lightning Urn", 0x4000012C, DS3ItemCategory.MISC), - ("Rope Black Firebomb", 0x4000012E, DS3ItemCategory.MISC), - ("Stalk Dung Pie", 0x4000012F, DS3ItemCategory.SKIP), - ("Duel Charm", 0x40000130, DS3ItemCategory.MISC), - ("Throwing Knife", 0x40000136, DS3ItemCategory.MISC), - ("Poison Throwing Knife", 0x40000137, DS3ItemCategory.MISC), - ("Charcoal Pine Resin", 0x4000014A, DS3ItemCategory.MISC), - ("Gold Pine Resin", 0x4000014B, DS3ItemCategory.MISC), - ("Human Pine Resin", 0x4000014E, DS3ItemCategory.MISC), - ("Carthus Rouge", 0x4000014F, DS3ItemCategory.MISC), - ("Pale Pine Resin", 0x40000150, DS3ItemCategory.MISC), - ("Charcoal Pine Bundle", 0x40000154, DS3ItemCategory.MISC), - ("Gold Pine Bundle", 0x40000155, DS3ItemCategory.MISC), - ("Rotten Pine Resin", 0x40000157, DS3ItemCategory.MISC), - ("Homeward Bone", 0x4000015E, DS3ItemCategory.MISC), - ("Coiled Sword Fragment", 0x4000015F, DS3ItemCategory.MISC), - ("Wolf's Blood Swordgrass", 0x4000016E, DS3ItemCategory.MISC), - ("Human Dregs", 0x4000016F, DS3ItemCategory.SKIP), - ("Forked Pale Tongue", 0x40000170, DS3ItemCategory.MISC), - ("Proof of a Concord Well Kept", 0x40000171, DS3ItemCategory.SKIP), - ("Prism Stone", 0x40000172, DS3ItemCategory.SKIP), - ("Binoculars", 0x40000173, DS3ItemCategory.MISC), - ("Proof of a Concord Kept", 0x40000174, DS3ItemCategory.SKIP), - ("Pale Tongue", 0x40000175, DS3ItemCategory.MISC), - ("Vertebra Shackle", 0x40000176, DS3ItemCategory.SKIP), - ("Sunlight Medal", 0x40000177, DS3ItemCategory.SKIP), - ("Dragon Head Stone", 0x40000179, DS3ItemCategory.MISC), - ("Dragon Torso Stone", 0x4000017A, DS3ItemCategory.MISC), - ("Rubbish", 0x4000017C, DS3ItemCategory.SKIP), - ("Dried Finger", 0x40000181, DS3ItemCategory.SKIP), - ("Twinkling Dragon Head Stone", 0x40000183, DS3ItemCategory.MISC), - ("Twinkling Dragon Torso Stone", 0x40000184, DS3ItemCategory.MISC), - ("Fire Keeper Soul", 0x40000186, DS3ItemCategory.MISC), - ("Fading Soul", 0x40000190, DS3ItemCategory.MISC), - ("Soul of a Deserted Corpse", 0x40000191, DS3ItemCategory.MISC), - ("Large Soul of a Deserted Corpse", 0x40000192, DS3ItemCategory.MISC), - ("Soul of an Unknown Traveler", 0x40000193, DS3ItemCategory.MISC), - ("Large Soul of an Unknown Traveler", 0x40000194, DS3ItemCategory.MISC), - ("Soul of a Nameless Soldier", 0x40000195, DS3ItemCategory.MISC), - ("Large Soul of a Nameless Soldier", 0x40000196, DS3ItemCategory.MISC), - ("Soul of a Weary Warrior", 0x40000197, DS3ItemCategory.MISC), - ("Large Soul of a Weary Warrior", 0x40000198, DS3ItemCategory.MISC), - ("Soul of a Crestfallen Knight", 0x40000199, DS3ItemCategory.MISC), - ("Large Soul of a Crestfallen Knight", 0x4000019A, DS3ItemCategory.MISC), - ("Soul of a Proud Paladin", 0x4000019B, DS3ItemCategory.MISC), - ("Large Soul of a Proud Paladin", 0x4000019C, DS3ItemCategory.MISC), - ("Soul of an Intrepid Hero", 0x4000019D, DS3ItemCategory.MISC), - ("Large Soul of an Intrepid Hero", 0x4000019E, DS3ItemCategory.MISC), - ("Soul of a Seasoned Warrior", 0x4000019F, DS3ItemCategory.MISC), - ("Large Soul of a Seasoned Warrior", 0x400001A0, DS3ItemCategory.MISC), - ("Soul of an Old Hand", 0x400001A1, DS3ItemCategory.MISC), - ("Soul of a Venerable Old Hand", 0x400001A2, DS3ItemCategory.MISC), - ("Soul of a Champion", 0x400001A3, DS3ItemCategory.MISC), - ("Soul of a Great Champion", 0x400001A4, DS3ItemCategory.MISC), - ("Seed of a Giant Tree", 0x400001B8, DS3ItemCategory.SKIP), - ("Young White Branch", 0x400001C6, DS3ItemCategory.SKIP), - ("Rusted Coin", 0x400001C7, DS3ItemCategory.MISC), - ("Siegbräu", 0x400001C8, DS3ItemCategory.SKIP), - ("Rusted Gold Coin", 0x400001C9, DS3ItemCategory.MISC), - ("Blue Bug Pellet", 0x400001CA, DS3ItemCategory.SKIP), - ("Red Bug Pellet", 0x400001CB, DS3ItemCategory.SKIP), - ("Yellow Bug Pellet", 0x400001CC, DS3ItemCategory.SKIP), - ("Black Bug Pellet", 0x400001CD, DS3ItemCategory.SKIP), - ("Young White Branch", 0x400001CF, DS3ItemCategory.SKIP), - ("Dark Sigil", 0x400001EA, DS3ItemCategory.SKIP), - ("Ember", 0x400001F4, DS3ItemCategory.MISC), - ("Soul of Champion Gundyr", 0x400002C8, DS3ItemCategory.BOSS), - ("Soul of the Dancer", 0x400002CA, DS3ItemCategory.BOSS), - ("Soul of a Crystal Sage", 0x400002CB, DS3ItemCategory.BOSS), - ("Soul of the Blood of the Wolf", 0x400002CD, DS3ItemCategory.BOSS), - ("Soul of Consumed Oceiros", 0x400002CE, DS3ItemCategory.BOSS), - ("Soul of Boreal Valley Vordt", 0x400002CF, DS3ItemCategory.BOSS), - ("Soul of the Old Demon King", 0x400002D0, DS3ItemCategory.BOSS), - ("Soul of Dragonslayer Armour", 0x400002D1, DS3ItemCategory.BOSS), - ("Soul of the Nameless King", 0x400002D2, DS3ItemCategory.BOSS), - ("Soul of Pontiff Sulyvahn", 0x400002D4, DS3ItemCategory.BOSS), - ("Soul of Aldrich", 0x400002D5, DS3ItemCategory.BOSS), - ("Soul of High Lord Wolnir", 0x400002D6, DS3ItemCategory.BOSS), - ("Soul of the Rotted Greatwood", 0x400002D7, DS3ItemCategory.BOSS), - ("Soul of Rosaria", 0x400002D8, DS3ItemCategory.MISC), - ("Soul of the Deacons of the Deep", 0x400002D9, DS3ItemCategory.BOSS), - ("Soul of the Twin Princes", 0x400002DB, DS3ItemCategory.BOSS), - ("Soul of Yhorm the Giant", 0x400002DC, DS3ItemCategory.BOSS), - ("Soul of the Lords", 0x400002DD, DS3ItemCategory.MISC), - ("Soul of a Demon", 0x400002E3, DS3ItemCategory.BOSS), - ("Soul of a Stray Demon", 0x400002E7, DS3ItemCategory.BOSS), - ("Titanite Shard", 0x400003E8, DS3ItemCategory.MISC), - ("Large Titanite Shard", 0x400003E9, DS3ItemCategory.MISC), - ("Titanite Chunk", 0x400003EA, DS3ItemCategory.MISC), - ("Titanite Slab", 0x400003EB, DS3ItemCategory.MISC), - ("Titanite Scale", 0x400003FC, DS3ItemCategory.MISC), - ("Twinkling Titanite", 0x40000406, DS3ItemCategory.MISC), - ("Heavy Gem", 0x4000044C, DS3ItemCategory.MISC), - ("Sharp Gem", 0x40000456, DS3ItemCategory.MISC), - ("Refined Gem", 0x40000460, DS3ItemCategory.MISC), - ("Crystal Gem", 0x4000046A, DS3ItemCategory.MISC), - ("Simple Gem", 0x40000474, DS3ItemCategory.MISC), - ("Fire Gem", 0x4000047E, DS3ItemCategory.MISC), - ("Chaos Gem", 0x40000488, DS3ItemCategory.MISC), - ("Lightning Gem", 0x40000492, DS3ItemCategory.MISC), - ("Deep Gem", 0x4000049C, DS3ItemCategory.MISC), - ("Dark Gem", 0x400004A6, DS3ItemCategory.MISC), - ("Poison Gem", 0x400004B0, DS3ItemCategory.MISC), - ("Blood Gem", 0x400004BA, DS3ItemCategory.MISC), - ("Raw Gem", 0x400004C4, DS3ItemCategory.MISC), - ("Blessed Gem", 0x400004CE, DS3ItemCategory.MISC), - ("Hollow Gem", 0x400004D8, DS3ItemCategory.MISC), - ("Shriving Stone", 0x400004E2, DS3ItemCategory.MISC), - ("Lift Chamber Key", 0x400007D1, DS3ItemCategory.KEY), - ("Small Doll", 0x400007D5, DS3ItemCategory.KEY), - ("Jailbreaker's Key", 0x400007D7, DS3ItemCategory.KEY), - ("Jailer's Key Ring", 0x400007D8, DS3ItemCategory.KEY), - ("Grave Key", 0x400007D9, DS3ItemCategory.KEY), - ("Cell Key", 0x400007DA, DS3ItemCategory.KEY), - ("Dungeon Ground Floor Key", 0x400007DB, DS3ItemCategory.KEY), - ("Old Cell Key", 0x400007DC, DS3ItemCategory.KEY), - ("Grand Archives Key", 0x400007DE, DS3ItemCategory.KEY), - ("Tower Key", 0x400007DF, DS3ItemCategory.KEY), - ("Small Lothric Banner", 0x40000836, DS3ItemCategory.KEY), - ("Farron Coal", 0x40000837, DS3ItemCategory.MISC), - ("Sage's Coal", 0x40000838, DS3ItemCategory.MISC), - ("Giant's Coal", 0x40000839, DS3ItemCategory.MISC), - ("Profaned Coal", 0x4000083A, DS3ItemCategory.MISC), - ("Mortician's Ashes", 0x4000083B, DS3ItemCategory.MISC), - ("Dreamchaser's Ashes", 0x4000083C, DS3ItemCategory.MISC), - ("Paladin's Ashes", 0x4000083D, DS3ItemCategory.MISC), - ("Grave Warden's Ashes", 0x4000083E, DS3ItemCategory.MISC), - ("Greirat's Ashes", 0x4000083F, DS3ItemCategory.MISC), - ("Orbeck's Ashes", 0x40000840, DS3ItemCategory.MISC), - ("Cornyx's Ashes", 0x40000841, DS3ItemCategory.MISC), - ("Karla's Ashes", 0x40000842, DS3ItemCategory.MISC), - ("Irina's Ashes", 0x40000843, DS3ItemCategory.MISC), - ("Yuria's Ashes", 0x40000844, DS3ItemCategory.MISC), - ("Basin of Vows", 0x40000845, DS3ItemCategory.KEY), - ("Loretta's Bone", 0x40000846, DS3ItemCategory.KEY), - ("Braille Divine Tome of Carim", 0x40000847, DS3ItemCategory.MISC), - ("Braille Divine Tome of Lothric", 0x40000848, DS3ItemCategory.MISC), - ("Cinders of a Lord - Abyss Watcher", 0x4000084B, DS3ItemCategory.KEY), - ("Cinders of a Lord - Aldrich", 0x4000084C, DS3ItemCategory.KEY), - ("Cinders of a Lord - Yhorm the Giant", 0x4000084D, DS3ItemCategory.KEY), - ("Cinders of a Lord - Lothric Prince", 0x4000084E, DS3ItemCategory.KEY), - ("Great Swamp Pyromancy Tome", 0x4000084F, DS3ItemCategory.MISC), - ("Carthus Pyromancy Tome", 0x40000850, DS3ItemCategory.MISC), - ("Izalith Pyromancy Tome", 0x40000851, DS3ItemCategory.MISC), - ("Quelana Pyromancy Tome", 0x40000852, DS3ItemCategory.MISC), - ("Grave Warden Pyromancy Tome", 0x40000853, DS3ItemCategory.MISC), - ("Sage's Scroll", 0x40000854, DS3ItemCategory.MISC), - ("Logan's Scroll", 0x40000855, DS3ItemCategory.MISC), - ("Crystal Scroll", 0x40000856, DS3ItemCategory.MISC), - ("Transposing Kiln", 0x40000857, DS3ItemCategory.MISC), - ("Coiled Sword", 0x40000859, DS3ItemCategory.SKIP), # Useless - ("Eyes of a Fire Keeper", 0x4000085A, DS3ItemCategory.KEY), - ("Sword of Avowal", 0x4000085B, DS3ItemCategory.KEY), - ("Golden Scroll", 0x4000085C, DS3ItemCategory.MISC), - ("Estus Shard", 0x4000085D, DS3ItemCategory.MISC), - ("Hawkwood's Swordgrass", 0x4000085E, DS3ItemCategory.SKIP), - ("Undead Bone Shard", 0x4000085F, DS3ItemCategory.MISC), - ("Deep Braille Divine Tome", 0x40000860, DS3ItemCategory.MISC), - ("Londor Braille Divine Tome", 0x40000861, DS3ItemCategory.MISC), - ("Excrement-covered Ashes", 0x40000862, DS3ItemCategory.MISC), - ("Prisoner Chief's Ashes", 0x40000863, DS3ItemCategory.MISC), - ("Xanthous Ashes", 0x40000864, DS3ItemCategory.MISC), - ("Hollow's Ashes", 0x40000865, DS3ItemCategory.MISC), - ("Patches' Ashes", 0x40000866, DS3ItemCategory.MISC), - ("Dragon Chaser's Ashes", 0x40000867, DS3ItemCategory.MISC), - ("Easterner's Ashes", 0x40000868, DS3ItemCategory.MISC), + DS3ItemData("White Sign Soapstone", 0x40000064, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Red Sign Soapstone", 0x40000066, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Red Eye Orb", 0x40000066, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Roster of Knights", 0x4000006C, DS3ItemCategory.UNIQUE, skip = True), + *DS3ItemData("Cracked Red Eye Orb", 0x4000006F, DS3ItemCategory.MISC, skip = True).counts([5]), + DS3ItemData("Black Eye Orb", 0x40000073, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Divine Blessing", 0x400000F0, DS3ItemCategory.MISC), + DS3ItemData("Hidden Blessing", 0x400000F1, DS3ItemCategory.MISC), + *DS3ItemData("Green Blossom", 0x40000104, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + *DS3ItemData("Budding Green Blossom", 0x40000106, DS3ItemCategory.MISC).counts([2, 3]), + *DS3ItemData("Bloodred Moss Clump", 0x4000010E, DS3ItemCategory.MISC, filler = True).counts([3]), + *DS3ItemData("Purple Moss Clump", 0x4000010F, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + *DS3ItemData("Blooming Purple Moss Clump", 0x40000110, DS3ItemCategory.MISC).counts([3]), + *DS3ItemData("Purging Stone", 0x40000112, DS3ItemCategory.MISC, skip = True).counts([2, 3]), + *DS3ItemData("Rime-blue Moss Clump", 0x40000114, DS3ItemCategory.MISC, filler = True).counts([2, 4]), + *DS3ItemData("Repair Powder", 0x40000118, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + *DS3ItemData("Kukri", 0x40000122, DS3ItemCategory.MISC).counts([8, 9]), + DS3ItemData("Kukri x5", 0x40000122, DS3ItemCategory.MISC, count = 5, filler = True), + *DS3ItemData("Firebomb", 0x40000124, DS3ItemCategory.MISC).counts([3, 5, 6]), + DS3ItemData("Firebomb x2", 0x40000124, DS3ItemCategory.MISC, count = 2, filler = True), + *DS3ItemData("Dung Pie", 0x40000125, DS3ItemCategory.MISC).counts([2, 4]), + DS3ItemData("Dung Pie x3", 0x40000125, DS3ItemCategory.MISC, count = 3, filler = True), + *DS3ItemData("Alluring Skull", 0x40000126, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Undead Hunter Charm", 0x40000128, DS3ItemCategory.MISC).counts([2, 3]), + *DS3ItemData("Black Firebomb", 0x40000129, DS3ItemCategory.MISC, filler = True).counts([2, 3, 4]), + DS3ItemData("Rope Firebomb", 0x4000012B, DS3ItemCategory.MISC), + *DS3ItemData("Lightning Urn", 0x4000012C, DS3ItemCategory.MISC, filler = True).counts([3, 4, 6]), + DS3ItemData("Rope Black Firebomb", 0x4000012E, DS3ItemCategory.MISC), + *DS3ItemData("Stalk Dung Pie", 0x4000012F, DS3ItemCategory.MISC).counts([6]), + *DS3ItemData("Duel Charm", 0x40000130, DS3ItemCategory.MISC).counts([3]), + *DS3ItemData("Throwing Knife", 0x40000136, DS3ItemCategory.MISC).counts([6, 8]), + DS3ItemData("Throwing Knife x5", 0x40000136, DS3ItemCategory.MISC, count = 5, filler = True), + DS3ItemData("Poison Throwing Knife", 0x40000137, DS3ItemCategory.MISC), + *DS3ItemData("Charcoal Pine Resin", 0x4000014A, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Gold Pine Resin", 0x4000014B, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Human Pine Resin", 0x4000014E, DS3ItemCategory.MISC, filler = True).counts([2, 4]), + *DS3ItemData("Carthus Rouge", 0x4000014F, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Pale Pine Resin", 0x40000150, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Charcoal Pine Bundle", 0x40000154, DS3ItemCategory.MISC).counts([2]), + *DS3ItemData("Gold Pine Bundle", 0x40000155, DS3ItemCategory.MISC).counts([6]), + *DS3ItemData("Rotten Pine Resin", 0x40000157, DS3ItemCategory.MISC).counts([2, 4]), + *DS3ItemData("Homeward Bone", 0x4000015E, DS3ItemCategory.MISC, filler = True).counts([2, 3, 6]), + DS3ItemData("Coiled Sword Fragment", 0x4000015F, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Wolf's Blood Swordgrass", 0x4000016E, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Human Dregs", 0x4000016F, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Forked Pale Tongue", 0x40000170, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Proof of a Concord Well Kept", 0x40000171, DS3ItemCategory.MISC, skip = True), + *DS3ItemData("Prism Stone", 0x40000172, DS3ItemCategory.MISC, skip = True).counts([4, 6, 10]), + DS3ItemData("Binoculars", 0x40000173, DS3ItemCategory.MISC), + DS3ItemData("Proof of a Concord Kept", 0x40000174, DS3ItemCategory.MISC, skip = True), + # One is needed for Leonhard's quest, others are useful for restatting. + DS3ItemData("Pale Tongue", 0x40000175, DS3ItemCategory.MISC, + classification = ItemClassification.progression), + DS3ItemData("Vertebra Shackle", 0x40000176, DS3ItemCategory.MISC, + classification = ItemClassification.progression), # Crow trade + DS3ItemData("Sunlight Medal", 0x40000177, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Dragon Head Stone", 0x40000179, DS3ItemCategory.UNIQUE), + DS3ItemData("Dragon Torso Stone", 0x4000017A, DS3ItemCategory.UNIQUE), + DS3ItemData("Rubbish", 0x4000017C, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Dried Finger", 0x40000181, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Twinkling Dragon Head Stone", 0x40000183, DS3ItemCategory.UNIQUE), + DS3ItemData("Twinkling Dragon Torso Stone", 0x40000184, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Fire Keeper Soul", 0x40000186, DS3ItemCategory.UNIQUE), + # Allow souls up to 2k in value to be used as filler + DS3ItemData("Fading Soul", 0x40000190, DS3ItemCategory.SOUL, souls = 50), + DS3ItemData("Soul of a Deserted Corpse", 0x40000191, DS3ItemCategory.SOUL, souls = 200), + DS3ItemData("Large Soul of a Deserted Corpse", 0x40000192, DS3ItemCategory.SOUL, souls = 400), + DS3ItemData("Soul of an Unknown Traveler", 0x40000193, DS3ItemCategory.SOUL, souls = 800), + DS3ItemData("Large Soul of an Unknown Traveler", 0x40000194, DS3ItemCategory.SOUL, souls = 1000), + DS3ItemData("Soul of a Nameless Soldier", 0x40000195, DS3ItemCategory.SOUL, souls = 2000), + DS3ItemData("Large Soul of a Nameless Soldier", 0x40000196, DS3ItemCategory.SOUL, souls = 3000), + DS3ItemData("Soul of a Weary Warrior", 0x40000197, DS3ItemCategory.SOUL, souls = 5000), + DS3ItemData("Large Soul of a Weary Warrior", 0x40000198, DS3ItemCategory.SOUL, souls = 8000), + DS3ItemData("Soul of a Crestfallen Knight", 0x40000199, DS3ItemCategory.SOUL, souls = 10000), + DS3ItemData("Large Soul of a Crestfallen Knight", 0x4000019A, DS3ItemCategory.SOUL, souls = 20000), + DS3ItemData("Soul of a Proud Paladin", 0x4000019B, DS3ItemCategory.SOUL, souls = 500), + DS3ItemData("Large Soul of a Proud Paladin", 0x4000019C, DS3ItemCategory.SOUL, souls = 1000), + DS3ItemData("Soul of an Intrepid Hero", 0x4000019D, DS3ItemCategory.SOUL, souls = 2000), + DS3ItemData("Large Soul of an Intrepid Hero", 0x4000019E, DS3ItemCategory.SOUL, souls = 2500), + DS3ItemData("Soul of a Seasoned Warrior", 0x4000019F, DS3ItemCategory.SOUL, souls = 5000), + DS3ItemData("Large Soul of a Seasoned Warrior", 0x400001A0, DS3ItemCategory.SOUL, souls = 7500), + DS3ItemData("Soul of an Old Hand", 0x400001A1, DS3ItemCategory.SOUL, souls = 12500), + DS3ItemData("Soul of a Venerable Old Hand", 0x400001A2, DS3ItemCategory.SOUL, souls = 20000), + DS3ItemData("Soul of a Champion", 0x400001A3, DS3ItemCategory.SOUL, souls = 25000), + DS3ItemData("Soul of a Great Champion", 0x400001A4, DS3ItemCategory.SOUL, souls = 50000), + DS3ItemData("Seed of a Giant Tree", 0x400001B8, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression, inject = True), # Crow trade + *DS3ItemData("Mossfruit", 0x400001C4, DS3ItemCategory.MISC, filler = True).counts([2]), + DS3ItemData("Young White Branch", 0x400001C6, DS3ItemCategory.MISC), + *DS3ItemData("Rusted Coin", 0x400001C7, DS3ItemCategory.MISC, filler = True).counts([2]), + DS3ItemData("Siegbräu", 0x400001C8, DS3ItemCategory.MISC, + classification = ItemClassification.progression), # Crow trade + *DS3ItemData("Rusted Gold Coin", 0x400001C9, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Blue Bug Pellet", 0x400001CA, DS3ItemCategory.MISC, filler = True).counts([2]), + *DS3ItemData("Red Bug Pellet", 0x400001CB, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Yellow Bug Pellet", 0x400001CC, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + *DS3ItemData("Black Bug Pellet", 0x400001CD, DS3ItemCategory.MISC, filler = True).counts([2, 3]), + DS3ItemData("Young White Branch", 0x400001CF, DS3ItemCategory.MISC, skip = True), + DS3ItemData("Dark Sigil", 0x400001EA, DS3ItemCategory.MISC, skip = True), + *DS3ItemData("Ember", 0x400001F4, DS3ItemCategory.MISC, filler = True).counts([2]), + DS3ItemData("Hello Carving", 0x40000208, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Thank you Carving", 0x40000209, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Very good! Carving", 0x4000020A, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("I'm sorry Carving", 0x4000020B, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Help me! Carving", 0x4000020C, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Soul of Champion Gundyr", 0x400002C8, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Dancer", 0x400002CA, DS3ItemCategory.BOSS, souls = 10000, + classification = ItemClassification.progression), + DS3ItemData("Soul of a Crystal Sage", 0x400002CB, DS3ItemCategory.BOSS, souls = 3000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Blood of the Wolf", 0x400002CD, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Consumed Oceiros", 0x400002CE, DS3ItemCategory.BOSS, souls = 12000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Boreal Valley Vordt", 0x400002CF, DS3ItemCategory.BOSS, souls = 2000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Old Demon King", 0x400002D0, DS3ItemCategory.BOSS, souls = 10000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Dragonslayer Armour", 0x400002D1, DS3ItemCategory.BOSS, souls = 15000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Nameless King", 0x400002D2, DS3ItemCategory.BOSS, souls = 16000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Pontiff Sulyvahn", 0x400002D4, DS3ItemCategory.BOSS, souls = 12000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Aldrich", 0x400002D5, DS3ItemCategory.BOSS, souls = 15000, + classification = ItemClassification.progression), + DS3ItemData("Soul of High Lord Wolnir", 0x400002D6, DS3ItemCategory.BOSS, souls = 10000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Rotted Greatwood", 0x400002D7, DS3ItemCategory.BOSS, souls = 3000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Rosaria", 0x400002D8, DS3ItemCategory.BOSS, souls = 5000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Deacons of the Deep", 0x400002D9, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Twin Princes", 0x400002DB, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Yhorm the Giant", 0x400002DC, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Lords", 0x400002DD, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of a Demon", 0x400002E3, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of a Stray Demon", 0x400002E7, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + *DS3ItemData("Titanite Shard", 0x400003E8, DS3ItemCategory.UPGRADE).counts([2]), + *DS3ItemData("Large Titanite Shard", 0x400003E9, DS3ItemCategory.UPGRADE).counts([2, 3]), + *DS3ItemData("Titanite Chunk", 0x400003EA, DS3ItemCategory.UPGRADE).counts([2, 6]), + DS3ItemData("Titanite Slab", 0x400003EB, DS3ItemCategory.UPGRADE, + classification = ItemClassification.useful), + *DS3ItemData("Titanite Scale", 0x400003FC, DS3ItemCategory.UPGRADE).counts([2, 3]), + *DS3ItemData("Twinkling Titanite", 0x40000406, DS3ItemCategory.UPGRADE).counts([2, 3]), + DS3ItemData("Heavy Gem", 0x4000044C, DS3ItemCategory.UPGRADE), + DS3ItemData("Sharp Gem", 0x40000456, DS3ItemCategory.UPGRADE), + DS3ItemData("Refined Gem", 0x40000460, DS3ItemCategory.UPGRADE), + DS3ItemData("Crystal Gem", 0x4000046A, DS3ItemCategory.UPGRADE), + DS3ItemData("Simple Gem", 0x40000474, DS3ItemCategory.UPGRADE), + DS3ItemData("Fire Gem", 0x4000047E, DS3ItemCategory.UPGRADE), + DS3ItemData("Chaos Gem", 0x40000488, DS3ItemCategory.UPGRADE), + DS3ItemData("Lightning Gem", 0x40000492, DS3ItemCategory.UPGRADE), + DS3ItemData("Deep Gem", 0x4000049C, DS3ItemCategory.UPGRADE), + DS3ItemData("Dark Gem", 0x400004A6, DS3ItemCategory.UPGRADE), + DS3ItemData("Poison Gem", 0x400004B0, DS3ItemCategory.UPGRADE), + DS3ItemData("Blood Gem", 0x400004BA, DS3ItemCategory.UPGRADE), + DS3ItemData("Raw Gem", 0x400004C4, DS3ItemCategory.UPGRADE), + DS3ItemData("Blessed Gem", 0x400004CE, DS3ItemCategory.UPGRADE), + DS3ItemData("Hollow Gem", 0x400004D8, DS3ItemCategory.UPGRADE), + DS3ItemData("Shriving Stone", 0x400004E2, DS3ItemCategory.UPGRADE), + DS3ItemData("Lift Chamber Key", 0x400007D1, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Small Doll", 0x400007D5, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Jailbreaker's Key", 0x400007D7, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Jailer's Key Ring", 0x400007D8, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grave Key", 0x400007D9, DS3ItemCategory.UNIQUE), + DS3ItemData("Cell Key", 0x400007DA, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Dungeon Ground Floor Key", 0x400007DB, DS3ItemCategory.UNIQUE), + DS3ItemData("Old Cell Key", 0x400007DC, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grand Archives Key", 0x400007DE, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Tower Key", 0x400007DF, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Small Lothric Banner", 0x40000836, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Farron Coal", 0x40000837, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Sage's Coal", 0x40000838, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Giant's Coal", 0x40000839, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Profaned Coal", 0x4000083A, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Mortician's Ashes", 0x4000083B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Dreamchaser's Ashes", 0x4000083C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Paladin's Ashes", 0x4000083D, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grave Warden's Ashes", 0x4000083E, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Greirat's Ashes", 0x4000083F, DS3ItemCategory.UNIQUE), + DS3ItemData("Orbeck's Ashes", 0x40000840, DS3ItemCategory.UNIQUE), + DS3ItemData("Cornyx's Ashes", 0x40000841, DS3ItemCategory.UNIQUE), + DS3ItemData("Karla's Ashes", 0x40000842, DS3ItemCategory.UNIQUE), + DS3ItemData("Irina's Ashes", 0x40000843, DS3ItemCategory.UNIQUE), + DS3ItemData("Yuria's Ashes", 0x40000844, DS3ItemCategory.UNIQUE), + DS3ItemData("Basin of Vows", 0x40000845, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Loretta's Bone", 0x40000846, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Braille Divine Tome of Carim", 0x40000847, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Braille Divine Tome of Lothric", 0x40000848, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Abyss Watcher", 0x4000084B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Aldrich", 0x4000084C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Yhorm the Giant", 0x4000084D, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Cinders of a Lord - Lothric Prince", 0x4000084E, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Great Swamp Pyromancy Tome", 0x4000084F, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Carthus Pyromancy Tome", 0x40000850, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Izalith Pyromancy Tome", 0x40000851, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Quelana Pyromancy Tome", 0x40000852, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Grave Warden Pyromancy Tome", 0x40000853, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Sage's Scroll", 0x40000854, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Logan's Scroll", 0x40000855, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Crystal Scroll", 0x40000856, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Transposing Kiln", 0x40000857, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Coiled Sword", 0x40000859, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Eyes of a Fire Keeper", 0x4000085A, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), # Allow players to do any ending + DS3ItemData("Sword of Avowal", 0x4000085B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Golden Scroll", 0x4000085C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Estus Shard", 0x4000085D, DS3ItemCategory.HEALING, + classification = ItemClassification.useful), + DS3ItemData("Hawkwood's Swordgrass", 0x4000085E, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Undead Bone Shard", 0x4000085F, DS3ItemCategory.HEALING, + classification = ItemClassification.useful), + DS3ItemData("Deep Braille Divine Tome", 0x40000860, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Londor Braille Divine Tome", 0x40000861, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Excrement-covered Ashes", 0x40000862, DS3ItemCategory.UNIQUE, + classification = ItemClassification.useful), + DS3ItemData("Prisoner Chief's Ashes", 0x40000863, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Xanthous Ashes", 0x40000864, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Hollow's Ashes", 0x40000865, DS3ItemCategory.UNIQUE), + DS3ItemData("Patches' Ashes", 0x40000866, DS3ItemCategory.UNIQUE), + DS3ItemData("Dragon Chaser's Ashes", 0x40000867, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Easterner's Ashes", 0x40000868, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + + # Fake item for controlling access to Archdragon Peak. The real drop isn't actually an item as + # such, so we have to inject this because there's no slot for it to come from. + DS3ItemData("Path of the Dragon", 0x40002346, DS3ItemCategory.UNIQUE, + inject = True, classification = ItemClassification.progression), # Spells - ("Farron Dart", 0x40124F80, DS3ItemCategory.SPELL), - ("Great Farron Dart", 0x40127690, DS3ItemCategory.SPELL), - ("Soul Arrow", 0x4013D620, DS3ItemCategory.SPELL), - ("Great Soul Arrow", 0x4013DA08, DS3ItemCategory.SPELL), - ("Heavy Soul Arrow", 0x4013DDF0, DS3ItemCategory.SPELL), - ("Great Heavy Soul Arrow", 0x4013E1D8, DS3ItemCategory.SPELL), - ("Homing Soulmass", 0x4013E5C0, DS3ItemCategory.SPELL), - ("Homing Crystal Soulmass", 0x4013E9A8, DS3ItemCategory.SPELL), - ("Soul Spear", 0x4013ED90, DS3ItemCategory.SPELL), - ("Crystal Soul Spear", 0x4013F178, DS3ItemCategory.SPELL), - ("Deep Soul", 0x4013F560, DS3ItemCategory.SPELL), - ("Great Deep Soul", 0x4013F948, DS3ItemCategory.SPELL), - ("Magic Weapon", 0x4013FD30, DS3ItemCategory.SPELL), - ("Great Magic Weapon", 0x40140118, DS3ItemCategory.SPELL), - ("Crystal Magic Weapon", 0x40140500, DS3ItemCategory.SPELL), - ("Magic Shield", 0x40144B50, DS3ItemCategory.SPELL), - ("Great Magic Shield", 0x40144F38, DS3ItemCategory.SPELL), - ("Hidden Weapon", 0x40147260, DS3ItemCategory.SPELL), - ("Hidden Body", 0x40147648, DS3ItemCategory.SPELL), - ("Cast Light", 0x40149970, DS3ItemCategory.SPELL), - ("Repair", 0x4014A528, DS3ItemCategory.SPELL), - ("Spook", 0x4014A910, DS3ItemCategory.SPELL), - ("Chameleon", 0x4014ACF8, DS3ItemCategory.SPELL), - ("Aural Decoy", 0x4014B0E0, DS3ItemCategory.SPELL), - ("White Dragon Breath", 0x4014E790, DS3ItemCategory.SPELL), - ("Farron Hail", 0x4014EF60, DS3ItemCategory.SPELL), - ("Crystal Hail", 0x4014F348, DS3ItemCategory.SPELL), - ("Soul Greatsword", 0x4014F730, DS3ItemCategory.SPELL), - ("Farron Flashsword", 0x4014FB18, DS3ItemCategory.SPELL), - ("Affinity", 0x401875B8, DS3ItemCategory.SPELL), - ("Dark Edge", 0x40189CC8, DS3ItemCategory.SPELL), - ("Soul Stream", 0x4018B820, DS3ItemCategory.SPELL), - ("Twisted Wall of Light", 0x40193138, DS3ItemCategory.SPELL), - ("Pestilent Mist", 0x401A8CE0, DS3ItemCategory.SPELL), # Originally called "Pestilent Mercury" pre 1.15 - ("Fireball", 0x40249F00, DS3ItemCategory.SPELL), - ("Fire Orb", 0x4024A6D0, DS3ItemCategory.SPELL), - ("Firestorm", 0x4024AAB8, DS3ItemCategory.SPELL), - ("Fire Surge", 0x4024B288, DS3ItemCategory.SPELL), - ("Black Serpent", 0x4024BA58, DS3ItemCategory.SPELL), - ("Combustion", 0x4024C610, DS3ItemCategory.SPELL), - ("Great Combustion", 0x4024C9F8, DS3ItemCategory.SPELL), - ("Poison Mist", 0x4024ED20, DS3ItemCategory.SPELL), - ("Toxic Mist", 0x4024F108, DS3ItemCategory.SPELL), - ("Acid Surge", 0x4024F4F0, DS3ItemCategory.SPELL), - ("Iron Flesh", 0x40251430, DS3ItemCategory.SPELL), - ("Flash Sweat", 0x40251818, DS3ItemCategory.SPELL), - ("Carthus Flame Arc", 0x402527B8, DS3ItemCategory.SPELL), - ("Rapport", 0x40252BA0, DS3ItemCategory.SPELL), - ("Power Within", 0x40253B40, DS3ItemCategory.SPELL), - ("Great Chaos Fire Orb", 0x40256250, DS3ItemCategory.SPELL), - ("Chaos Storm", 0x40256638, DS3ItemCategory.SPELL), - ("Fire Whip", 0x40256A20, DS3ItemCategory.SPELL), - ("Black Flame", 0x40256E08, DS3ItemCategory.SPELL), - ("Profaned Flame", 0x402575D8, DS3ItemCategory.SPELL), - ("Chaos Bed Vestiges", 0x402579C0, DS3ItemCategory.SPELL), - ("Warmth", 0x4025B070, DS3ItemCategory.SPELL), - ("Profuse Sweat", 0x402717D0, DS3ItemCategory.SPELL), - ("Black Fire Orb", 0x4027D350, DS3ItemCategory.SPELL), - ("Bursting Fireball", 0x4027FA60, DS3ItemCategory.SPELL), - ("Boulder Heave", 0x40282170, DS3ItemCategory.SPELL), - ("Sacred Flame", 0x40284880, DS3ItemCategory.SPELL), - ("Carthus Beacon", 0x40286F90, DS3ItemCategory.SPELL), - ("Heal Aid", 0x403540D0, DS3ItemCategory.SPELL), - ("Heal", 0x403567E0, DS3ItemCategory.SPELL), - ("Med Heal", 0x40356BC8, DS3ItemCategory.SPELL), - ("Great Heal", 0x40356FB0, DS3ItemCategory.SPELL), - ("Soothing Sunlight", 0x40357398, DS3ItemCategory.SPELL), - ("Replenishment", 0x40357780, DS3ItemCategory.SPELL), - ("Bountiful Sunlight", 0x40357B68, DS3ItemCategory.SPELL), - ("Bountiful Light", 0x40358338, DS3ItemCategory.SPELL), - ("Caressing Tears", 0x40358720, DS3ItemCategory.SPELL), - ("Tears of Denial", 0x4035B600, DS3ItemCategory.SPELL), - ("Homeward", 0x4035B9E8, DS3ItemCategory.SPELL), - ("Force", 0x4035DD10, DS3ItemCategory.SPELL), - ("Wrath of the Gods", 0x4035E0F8, DS3ItemCategory.SPELL), - ("Emit Force", 0x4035E4E0, DS3ItemCategory.SPELL), - ("Seek Guidance", 0x40360420, DS3ItemCategory.SPELL), - ("Lightning Spear", 0x40362B30, DS3ItemCategory.SPELL), - ("Great Lightning Spear", 0x40362F18, DS3ItemCategory.SPELL), - ("Sunlight Spear", 0x40363300, DS3ItemCategory.SPELL), - ("Lightning Storm", 0x403636E8, DS3ItemCategory.SPELL), - ("Gnaw", 0x40363AD0, DS3ItemCategory.SPELL), - ("Dorhys' Gnawing", 0x40363EB8, DS3ItemCategory.SPELL), - ("Magic Barrier", 0x40365240, DS3ItemCategory.SPELL), - ("Great Magic Barrier", 0x40365628, DS3ItemCategory.SPELL), - ("Sacred Oath", 0x40365DF8, DS3ItemCategory.SPELL), - ("Vow of Silence", 0x4036A448, DS3ItemCategory.SPELL), - ("Lightning Blade", 0x4036C770, DS3ItemCategory.SPELL), - ("Darkmoon Blade", 0x4036CB58, DS3ItemCategory.SPELL), - ("Dark Blade", 0x40378AC0, DS3ItemCategory.SPELL), - ("Dead Again", 0x40387520, DS3ItemCategory.SPELL), - ("Lightning Stake", 0x40389C30, DS3ItemCategory.SPELL), - ("Divine Pillars of Light", 0x4038C340, DS3ItemCategory.SPELL), - ("Lifehunt Scythe", 0x4038EA50, DS3ItemCategory.SPELL), - ("Blessed Weapon", 0x40395F80, DS3ItemCategory.SPELL), - ("Deep Protection", 0x40398690, DS3ItemCategory.SPELL), - ("Atonement", 0x4039ADA0, DS3ItemCategory.SPELL), -]] - -_dlc_items = [DS3ItemData(row[0], row[1], True, row[2]) for row in [ + DS3ItemData("Farron Dart", 0x40124F80, DS3ItemCategory.SPELL), + DS3ItemData("Great Farron Dart", 0x40127690, DS3ItemCategory.SPELL), + DS3ItemData("Soul Arrow", 0x4013D620, DS3ItemCategory.SPELL), + DS3ItemData("Great Soul Arrow", 0x4013DA08, DS3ItemCategory.SPELL), + DS3ItemData("Heavy Soul Arrow", 0x4013DDF0, DS3ItemCategory.SPELL), + DS3ItemData("Great Heavy Soul Arrow", 0x4013E1D8, DS3ItemCategory.SPELL), + DS3ItemData("Homing Soulmass", 0x4013E5C0, DS3ItemCategory.SPELL), + DS3ItemData("Homing Crystal Soulmass", 0x4013E9A8, DS3ItemCategory.SPELL), + DS3ItemData("Soul Spear", 0x4013ED90, DS3ItemCategory.SPELL), + DS3ItemData("Crystal Soul Spear", 0x4013F178, DS3ItemCategory.SPELL), + DS3ItemData("Deep Soul", 0x4013F560, DS3ItemCategory.SPELL), + DS3ItemData("Great Deep Soul", 0x4013F948, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Magic Weapon", 0x4013FD30, DS3ItemCategory.SPELL), + DS3ItemData("Great Magic Weapon", 0x40140118, DS3ItemCategory.SPELL), + DS3ItemData("Crystal Magic Weapon", 0x40140500, DS3ItemCategory.SPELL), + DS3ItemData("Magic Shield", 0x40144B50, DS3ItemCategory.SPELL), + DS3ItemData("Great Magic Shield", 0x40144F38, DS3ItemCategory.SPELL), + DS3ItemData("Hidden Weapon", 0x40147260, DS3ItemCategory.SPELL), + DS3ItemData("Hidden Body", 0x40147648, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Cast Light", 0x40149970, DS3ItemCategory.SPELL), + DS3ItemData("Repair", 0x4014A528, DS3ItemCategory.SPELL), + DS3ItemData("Spook", 0x4014A910, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Chameleon", 0x4014ACF8, DS3ItemCategory.SPELL, + classification = ItemClassification.progression), + DS3ItemData("Aural Decoy", 0x4014B0E0, DS3ItemCategory.SPELL), + DS3ItemData("White Dragon Breath", 0x4014E790, DS3ItemCategory.SPELL), + DS3ItemData("Farron Hail", 0x4014EF60, DS3ItemCategory.SPELL), + DS3ItemData("Crystal Hail", 0x4014F348, DS3ItemCategory.SPELL), + DS3ItemData("Soul Greatsword", 0x4014F730, DS3ItemCategory.SPELL), + DS3ItemData("Farron Flashsword", 0x4014FB18, DS3ItemCategory.SPELL), + DS3ItemData("Affinity", 0x401875B8, DS3ItemCategory.SPELL), + DS3ItemData("Dark Edge", 0x40189CC8, DS3ItemCategory.SPELL), + DS3ItemData("Soul Stream", 0x4018B820, DS3ItemCategory.SPELL), + DS3ItemData("Twisted Wall of Light", 0x40193138, DS3ItemCategory.SPELL), + DS3ItemData("Pestilent Mist", 0x401A8CE0, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), # Originally called "Pestilent Mercury" pre 1.15 + DS3ItemData("Fireball", 0x40249F00, DS3ItemCategory.SPELL), + DS3ItemData("Fire Orb", 0x4024A6D0, DS3ItemCategory.SPELL), + DS3ItemData("Firestorm", 0x4024AAB8, DS3ItemCategory.SPELL), + DS3ItemData("Fire Surge", 0x4024B288, DS3ItemCategory.SPELL), + DS3ItemData("Black Serpent", 0x4024BA58, DS3ItemCategory.SPELL), + DS3ItemData("Combustion", 0x4024C610, DS3ItemCategory.SPELL), + DS3ItemData("Great Combustion", 0x4024C9F8, DS3ItemCategory.SPELL), + DS3ItemData("Poison Mist", 0x4024ED20, DS3ItemCategory.SPELL), + DS3ItemData("Toxic Mist", 0x4024F108, DS3ItemCategory.SPELL), + DS3ItemData("Acid Surge", 0x4024F4F0, DS3ItemCategory.SPELL), + DS3ItemData("Iron Flesh", 0x40251430, DS3ItemCategory.SPELL), + DS3ItemData("Flash Sweat", 0x40251818, DS3ItemCategory.SPELL), + DS3ItemData("Carthus Flame Arc", 0x402527B8, DS3ItemCategory.SPELL), + DS3ItemData("Rapport", 0x40252BA0, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Power Within", 0x40253B40, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Great Chaos Fire Orb", 0x40256250, DS3ItemCategory.SPELL), + DS3ItemData("Chaos Storm", 0x40256638, DS3ItemCategory.SPELL), + DS3ItemData("Fire Whip", 0x40256A20, DS3ItemCategory.SPELL), + DS3ItemData("Black Flame", 0x40256E08, DS3ItemCategory.SPELL), + DS3ItemData("Profaned Flame", 0x402575D8, DS3ItemCategory.SPELL), + DS3ItemData("Chaos Bed Vestiges", 0x402579C0, DS3ItemCategory.SPELL), + DS3ItemData("Warmth", 0x4025B070, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Profuse Sweat", 0x402717D0, DS3ItemCategory.SPELL), + DS3ItemData("Black Fire Orb", 0x4027D350, DS3ItemCategory.SPELL), + DS3ItemData("Bursting Fireball", 0x4027FA60, DS3ItemCategory.SPELL), + DS3ItemData("Boulder Heave", 0x40282170, DS3ItemCategory.SPELL), + DS3ItemData("Sacred Flame", 0x40284880, DS3ItemCategory.SPELL), + DS3ItemData("Carthus Beacon", 0x40286F90, DS3ItemCategory.SPELL), + DS3ItemData("Heal Aid", 0x403540D0, DS3ItemCategory.SPELL), + DS3ItemData("Heal", 0x403567E0, DS3ItemCategory.SPELL), + DS3ItemData("Med Heal", 0x40356BC8, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Great Heal", 0x40356FB0, DS3ItemCategory.SPELL), + DS3ItemData("Soothing Sunlight", 0x40357398, DS3ItemCategory.SPELL), + DS3ItemData("Replenishment", 0x40357780, DS3ItemCategory.SPELL), + DS3ItemData("Bountiful Sunlight", 0x40357B68, DS3ItemCategory.SPELL), + DS3ItemData("Bountiful Light", 0x40358338, DS3ItemCategory.SPELL), + DS3ItemData("Caressing Tears", 0x40358720, DS3ItemCategory.SPELL), + DS3ItemData("Tears of Denial", 0x4035B600, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Homeward", 0x4035B9E8, DS3ItemCategory.SPELL, + classification = ItemClassification.useful), + DS3ItemData("Force", 0x4035DD10, DS3ItemCategory.SPELL), + DS3ItemData("Wrath of the Gods", 0x4035E0F8, DS3ItemCategory.SPELL), + DS3ItemData("Emit Force", 0x4035E4E0, DS3ItemCategory.SPELL), + DS3ItemData("Seek Guidance", 0x40360420, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Spear", 0x40362B30, DS3ItemCategory.SPELL), + DS3ItemData("Great Lightning Spear", 0x40362F18, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Sunlight Spear", 0x40363300, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Storm", 0x403636E8, DS3ItemCategory.SPELL), + DS3ItemData("Gnaw", 0x40363AD0, DS3ItemCategory.SPELL), + DS3ItemData("Dorhys' Gnawing", 0x40363EB8, DS3ItemCategory.SPELL), + DS3ItemData("Magic Barrier", 0x40365240, DS3ItemCategory.SPELL), + DS3ItemData("Great Magic Barrier", 0x40365628, DS3ItemCategory.SPELL), + DS3ItemData("Sacred Oath", 0x40365DF8, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Vow of Silence", 0x4036A448, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Blade", 0x4036C770, DS3ItemCategory.SPELL), + DS3ItemData("Darkmoon Blade", 0x4036CB58, DS3ItemCategory.SPELL, + inject = True), # Covenant reward + DS3ItemData("Dark Blade", 0x40378AC0, DS3ItemCategory.SPELL), + DS3ItemData("Dead Again", 0x40387520, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Stake", 0x40389C30, DS3ItemCategory.SPELL), + DS3ItemData("Divine Pillars of Light", 0x4038C340, DS3ItemCategory.SPELL), + DS3ItemData("Lifehunt Scythe", 0x4038EA50, DS3ItemCategory.SPELL), + DS3ItemData("Blessed Weapon", 0x40395F80, DS3ItemCategory.SPELL), + DS3ItemData("Deep Protection", 0x40398690, DS3ItemCategory.SPELL), + DS3ItemData("Atonement", 0x4039ADA0, DS3ItemCategory.SPELL), +] + +_dlc_items = [ # Ammunition - ("Millwood Greatarrow", 0x000623E0, DS3ItemCategory.SKIP), + *DS3ItemData("Millwood Greatarrow", 0x000623E0, DS3ItemCategory.MISC).counts([5]), # Weapons - ("Aquamarine Dagger", 0x00116520, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Murky Hand Scythe", 0x00118C30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Onyx Blade", 0x00222E00, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Ringed Knight Straight Sword", 0x00225510, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Gael's Greatsword", 0x00227C20, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Follower Sabre", 0x003EDDC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Demon's Scar", 0x003F04D0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Frayed Blade", 0x004D35A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Herald Curved Greatsword", 0x006159E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Millwood Battle Axe", 0x006D67D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Earth Seeker", 0x006D8EE0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Quakestone Hammer", 0x007ECCF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Ledo's Great Hammer", 0x007EF400, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Follower Javelin", 0x008CD6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Ringed Knight Spear", 0x008CFDC0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Lothric War Banner", 0x008D24D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Crucifix of the Mad King", 0x008D4BE0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Splitleaf Greatsword", 0x009B2E90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Friede's Great Scythe", 0x009B55A0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Crow Talons", 0x00A89C10, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Rose of Ariandel", 0x00B82C70, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Pyromancer's Parting Flame", 0x00CC9ED0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Murky Longstaff", 0x00CCC5E0, DS3ItemCategory.WEAPON_UPGRADE_10), - ("Sacred Chime of Filianore", 0x00CCECF0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Preacher's Right Arm", 0x00CD1400, DS3ItemCategory.WEAPON_UPGRADE_5), - ("White Birch Bow", 0x00D77440, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Millwood Greatbow", 0x00D85EA0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Repeating Crossbow", 0x00D885B0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Giant Door Shield", 0x00F5F8C0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Valorheart", 0x00F646E0, DS3ItemCategory.WEAPON_UPGRADE_5), - ("Crow Quills", 0x00F66DF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), - ("Ringed Knight Paired Greatswords", 0x00F69500, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Aquamarine Dagger", 0x00116520, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Murky Hand Scythe", 0x00118C30, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Onyx Blade", 0x00222E00, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Ringed Knight Straight Sword", 0x00225510, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Gael's Greatsword", 0x00227C20, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Follower Sabre", 0x003EDDC0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Demon's Scar", 0x003F04D0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Frayed Blade", 0x004D35A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Harald Curved Greatsword", 0x006159E0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Millwood Battle Axe", 0x006D67D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Earth Seeker", 0x006D8EE0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Quakestone Hammer", 0x007ECCF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Ledo's Great Hammer", 0x007EF400, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Follower Javelin", 0x008CD6B0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Ringed Knight Spear", 0x008CFDC0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Lothric War Banner", 0x008D24D0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Crucifix of the Mad King", 0x008D4BE0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Splitleaf Greatsword", 0x009B2E90, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Friede's Great Scythe", 0x009B55A0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Crow Talons", 0x00A89C10, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Rose of Ariandel", 0x00B82C70, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Pyromancer's Parting Flame", 0x00CC9ED0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Murky Longstaff", 0x00CCC5E0, DS3ItemCategory.WEAPON_UPGRADE_10), + DS3ItemData("Sacred Chime of Filianore", 0x00CCECF0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Preacher's Right Arm", 0x00CD1400, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("White Birch Bow", 0x00D77440, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Millwood Greatbow", 0x00D85EA0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Repeating Crossbow", 0x00D885B0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Giant Door Shield", 0x00F5F8C0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Valorheart", 0x00F646E0, DS3ItemCategory.WEAPON_UPGRADE_5), + DS3ItemData("Crow Quills", 0x00F66DF0, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE), + DS3ItemData("Ringed Knight Paired Greatswords", 0x00F69500, DS3ItemCategory.WEAPON_UPGRADE_5), # Shields - ("Follower Shield", 0x0135C0E0, DS3ItemCategory.SHIELD_INFUSIBLE), - ("Dragonhead Shield", 0x0135E7F0, DS3ItemCategory.SHIELD), - ("Ethereal Oak Shield", 0x01450320, DS3ItemCategory.SHIELD), - ("Dragonhead Greatshield", 0x01452A30, DS3ItemCategory.SHIELD), - ("Follower Torch", 0x015F1AD0, DS3ItemCategory.SHIELD), + DS3ItemData("Follower Shield", 0x0135C0E0, DS3ItemCategory.SHIELD_INFUSIBLE), + DS3ItemData("Dragonhead Shield", 0x0135E7F0, DS3ItemCategory.SHIELD), + DS3ItemData("Ethereal Oak Shield", 0x01450320, DS3ItemCategory.SHIELD), + DS3ItemData("Dragonhead Greatshield", 0x01452A30, DS3ItemCategory.SHIELD), + DS3ItemData("Follower Torch", 0x015F1AD0, DS3ItemCategory.SHIELD), # Armor - ("Vilhelm's Helm", 0x11312D00, DS3ItemCategory.ARMOR), - ("Vilhelm's Armor", 0x113130E8, DS3ItemCategory.ARMOR), - ("Vilhelm's Gauntlets", 0x113134D0, DS3ItemCategory.ARMOR), - ("Vilhelm's Leggings", 0x113138B8, DS3ItemCategory.ARMOR), - ("Antiquated Plain Garb", 0x11B2E408, DS3ItemCategory.ARMOR), - ("Violet Wrappings", 0x11B2E7F0, DS3ItemCategory.ARMOR), - ("Loincloth 2", 0x11B2EBD8, DS3ItemCategory.ARMOR), - ("Shira's Crown", 0x11C22260, DS3ItemCategory.ARMOR), - ("Shira's Armor", 0x11C22648, DS3ItemCategory.ARMOR), - ("Shira's Gloves", 0x11C22A30, DS3ItemCategory.ARMOR), - ("Shira's Trousers", 0x11C22E18, DS3ItemCategory.ARMOR), - ("Lapp's Helm", 0x11E84800, DS3ItemCategory.ARMOR), - ("Lapp's Armor", 0x11E84BE8, DS3ItemCategory.ARMOR), - ("Lapp's Gauntlets", 0x11E84FD0, DS3ItemCategory.ARMOR), - ("Lapp's Leggings", 0x11E853B8, DS3ItemCategory.ARMOR), - ("Slave Knight Hood", 0x134EDCE0, DS3ItemCategory.ARMOR), - ("Slave Knight Armor", 0x134EE0C8, DS3ItemCategory.ARMOR), - ("Slave Knight Gauntlets", 0x134EE4B0, DS3ItemCategory.ARMOR), - ("Slave Knight Leggings", 0x134EE898, DS3ItemCategory.ARMOR), - ("Ordained Hood", 0x135E1F20, DS3ItemCategory.ARMOR), - ("Ordained Dress", 0x135E2308, DS3ItemCategory.ARMOR), - ("Ordained Trousers", 0x135E2AD8, DS3ItemCategory.ARMOR), - ("Follower Helm", 0x137CA3A0, DS3ItemCategory.ARMOR), - ("Follower Armor", 0x137CA788, DS3ItemCategory.ARMOR), - ("Follower Gloves", 0x137CAB70, DS3ItemCategory.ARMOR), - ("Follower Boots", 0x137CAF58, DS3ItemCategory.ARMOR), - ("Millwood Knight Helm", 0x139B2820, DS3ItemCategory.ARMOR), - ("Millwood Knight Armor", 0x139B2C08, DS3ItemCategory.ARMOR), - ("Millwood Knight Gauntlets", 0x139B2FF0, DS3ItemCategory.ARMOR), - ("Millwood Knight Leggings", 0x139B33D8, DS3ItemCategory.ARMOR), - ("Ringed Knight Hood", 0x13C8EEE0, DS3ItemCategory.ARMOR), - ("Ringed Knight Armor", 0x13C8F2C8, DS3ItemCategory.ARMOR), - ("Ringed Knight Gauntlets", 0x13C8F6B0, DS3ItemCategory.ARMOR), - ("Ringed Knight Leggings", 0x13C8FA98, DS3ItemCategory.ARMOR), - ("Harald Legion Armor", 0x13D83508, DS3ItemCategory.ARMOR), - ("Harald Legion Gauntlets", 0x13D838F0, DS3ItemCategory.ARMOR), - ("Harald Legion Leggings", 0x13D83CD8, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Helm", 0x1405F7E0, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Armor", 0x1405FBC8, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Gauntlets", 0x1405FFB0, DS3ItemCategory.ARMOR), - ("Iron Dragonslayer Leggings", 0x14060398, DS3ItemCategory.ARMOR), - ("White Preacher Head", 0x14153A20, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Helm", 0x14CC5520, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Armor", 0x14CC5908, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Gauntlets", 0x14CC5CF0, DS3ItemCategory.ARMOR), - ("Ruin Sentinel Leggings", 0x14CC60D8, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Hood", 0x14DB9760, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Garb", 0x14DB9B48, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Gloves", 0x14DB9F30, DS3ItemCategory.ARMOR), - ("Desert Pyromancer Skirt", 0x14DBA318, DS3ItemCategory.ARMOR), - ("Black Witch Hat", 0x14EAD9A0, DS3ItemCategory.ARMOR), - ("Black Witch Garb", 0x14EADD88, DS3ItemCategory.ARMOR), - ("Black Witch Wrappings", 0x14EAE170, DS3ItemCategory.ARMOR), - ("Black Witch Trousers", 0x14EAE558, DS3ItemCategory.ARMOR), - ("Black Witch Veil", 0x14FA1BE0, DS3ItemCategory.ARMOR), - ("Blindfold Mask", 0x15095E20, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Helm", 0x11312D00, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Armor", 0x113130E8, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Gauntlets", 0x113134D0, DS3ItemCategory.ARMOR), + DS3ItemData("Vilhelm's Leggings", 0x113138B8, DS3ItemCategory.ARMOR), + DS3ItemData("Antiquated Plain Garb", 0x11B2E408, DS3ItemCategory.ARMOR), + DS3ItemData("Violet Wrappings", 0x11B2E7F0, DS3ItemCategory.ARMOR), + DS3ItemData("Loincloth 2", 0x11B2EBD8, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Crown", 0x11C22260, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Armor", 0x11C22648, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Gloves", 0x11C22A30, DS3ItemCategory.ARMOR), + DS3ItemData("Shira's Trousers", 0x11C22E18, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Helm", 0x11E84800, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Armor", 0x11E84BE8, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Gauntlets", 0x11E84FD0, DS3ItemCategory.ARMOR), + DS3ItemData("Lapp's Leggings", 0x11E853B8, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Hood", 0x134EDCE0, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Armor", 0x134EE0C8, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Gauntlets", 0x134EE4B0, DS3ItemCategory.ARMOR), + DS3ItemData("Slave Knight Leggings", 0x134EE898, DS3ItemCategory.ARMOR), + DS3ItemData("Ordained Hood", 0x135E1F20, DS3ItemCategory.ARMOR), + DS3ItemData("Ordained Dress", 0x135E2308, DS3ItemCategory.ARMOR), + DS3ItemData("Ordained Trousers", 0x135E2AD8, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Helm", 0x137CA3A0, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Armor", 0x137CA788, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Gloves", 0x137CAB70, DS3ItemCategory.ARMOR), + DS3ItemData("Follower Boots", 0x137CAF58, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Helm", 0x139B2820, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Armor", 0x139B2C08, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Gauntlets", 0x139B2FF0, DS3ItemCategory.ARMOR), + DS3ItemData("Millwood Knight Leggings", 0x139B33D8, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Hood", 0x13C8EEE0, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Armor", 0x13C8F2C8, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Gauntlets", 0x13C8F6B0, DS3ItemCategory.ARMOR), + DS3ItemData("Ringed Knight Leggings", 0x13C8FA98, DS3ItemCategory.ARMOR), + DS3ItemData("Harald Legion Armor", 0x13D83508, DS3ItemCategory.ARMOR), + DS3ItemData("Harald Legion Gauntlets", 0x13D838F0, DS3ItemCategory.ARMOR), + DS3ItemData("Harald Legion Leggings", 0x13D83CD8, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Helm", 0x1405F7E0, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Armor", 0x1405FBC8, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Gauntlets", 0x1405FFB0, DS3ItemCategory.ARMOR), + DS3ItemData("Iron Dragonslayer Leggings", 0x14060398, DS3ItemCategory.ARMOR), + DS3ItemData("White Preacher Head", 0x14153A20, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Helm", 0x14CC5520, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Armor", 0x14CC5908, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Gauntlets", 0x14CC5CF0, DS3ItemCategory.ARMOR), + DS3ItemData("Ruin Leggings", 0x14CC60D8, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Hood", 0x14DB9760, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Garb", 0x14DB9B48, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Gloves", 0x14DB9F30, DS3ItemCategory.ARMOR), + DS3ItemData("Desert Pyromancer Skirt", 0x14DBA318, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Hat", 0x14EAD9A0, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Garb", 0x14EADD88, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Wrappings", 0x14EAE170, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Trousers", 0x14EAE558, DS3ItemCategory.ARMOR), + DS3ItemData("Black Witch Veil", 0x14FA1BE0, DS3ItemCategory.ARMOR), + DS3ItemData("Blindfold Mask", 0x15095E20, DS3ItemCategory.ARMOR), # Covenants - ("Spear of the Church", 0x2000276A, DS3ItemCategory.SKIP), + DS3ItemData("Spear of the Church", 0x2000276A, DS3ItemCategory.UNIQUE, skip = True), # Rings - ("Chloranthy Ring+3", 0x20004E2D, DS3ItemCategory.RING), - ("Havel's Ring+3", 0x20004E37, DS3ItemCategory.RING), - ("Ring of Favor+3", 0x20004E41, DS3ItemCategory.RING), - ("Ring of Steel Protection+3", 0x20004E4B, DS3ItemCategory.RING), - ("Wolf Ring+3", 0x20004EE1, DS3ItemCategory.RING), - ("Covetous Gold Serpent Ring+3", 0x20004FA9, DS3ItemCategory.RING), - ("Covetous Silver Serpent Ring+3", 0x20004FB3, DS3ItemCategory.RING), - ("Ring of the Evil Eye+3", 0x20005071, DS3ItemCategory.RING), - ("Chillbite Ring", 0x20005208, DS3ItemCategory.RING), + DS3ItemData("Chloranthy Ring+3", 0x20004E2D, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Havel's Ring+3", 0x20004E37, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Ring of Favor+3", 0x20004E41, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Ring of Steel Protection+3", 0x20004E4B, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Wolf Ring+3", 0x20004EE1, DS3ItemCategory.RING), + DS3ItemData("Covetous Gold Serpent Ring+3", 0x20004FA9, DS3ItemCategory.RING), + DS3ItemData("Covetous Silver Serpent Ring+3", 0x20004FB3, DS3ItemCategory.RING, + classification = ItemClassification.useful), + DS3ItemData("Ring of the Evil Eye+3", 0x20005071, DS3ItemCategory.RING), + DS3ItemData("Chillbite Ring", 0x20005208, DS3ItemCategory.RING), # Items - ("Church Guardian Shiv", 0x4000013B, DS3ItemCategory.MISC), - ("Filianore's Spear Ornament", 0x4000017B, DS3ItemCategory.SKIP), - ("Ritual Spear Fragment", 0x4000028A, DS3ItemCategory.MISC), - ("Divine Spear Fragment", 0x4000028B, DS3ItemCategory.SKIP), - ("Soul of Sister Friede", 0x400002E8, DS3ItemCategory.BOSS), - ("Soul of Slave Knight Gael", 0x400002E9, DS3ItemCategory.BOSS), - ("Soul of the Demon Prince", 0x400002EA, DS3ItemCategory.BOSS), - ("Soul of Darkeater Midir", 0x400002EB, DS3ItemCategory.BOSS), - ("Champion's Bones", 0x40000869, DS3ItemCategory.SKIP), - ("Captain's Ashes", 0x4000086A, DS3ItemCategory.MISC), - ("Contraption Key", 0x4000086B, DS3ItemCategory.KEY), - ("Small Envoy Banner", 0x4000086C, DS3ItemCategory.KEY), - ("Old Woman's Ashes", 0x4000086D, DS3ItemCategory.SKIP), - ("Blood of the Dark Soul", 0x4000086E, DS3ItemCategory.SKIP), + DS3ItemData("Church Guardian Shiv", 0x4000013B, DS3ItemCategory.MISC), + DS3ItemData("Filianore's Spear Ornament", 0x4000017B, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Ritual Spear Fragment", 0x4000028A, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Divine Spear Fragment", 0x4000028B, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Soul of Sister Friede", 0x400002E8, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Slave Knight Gael", 0x400002E9, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of the Demon Prince", 0x400002EA, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Soul of Darkeater Midir", 0x400002EB, DS3ItemCategory.BOSS, souls = 20000, + classification = ItemClassification.progression), + DS3ItemData("Champion's Bones", 0x40000869, DS3ItemCategory.UNIQUE, skip = True), + DS3ItemData("Captain's Ashes", 0x4000086A, DS3ItemCategory.MISC, + classification = ItemClassification.progression), + DS3ItemData("Contraption Key", 0x4000086B, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Small Envoy Banner", 0x4000086C, DS3ItemCategory.UNIQUE, + classification = ItemClassification.progression), + DS3ItemData("Old Woman's Ashes", 0x4000086D, DS3ItemCategory.UNIQUE), + DS3ItemData("Blood of the Dark Soul", 0x4000086E, DS3ItemCategory.UNIQUE, skip = True), # Spells - ("Frozen Weapon", 0x401408E8, DS3ItemCategory.SPELL), - ("Old Moonlight", 0x4014FF00, DS3ItemCategory.SPELL), - ("Great Soul Dregs", 0x401879A0, DS3ItemCategory.SPELL), - ("Snap Freeze", 0x401A90C8, DS3ItemCategory.SPELL), - ("Floating Chaos", 0x40257DA8, DS3ItemCategory.SPELL), - ("Flame Fan", 0x40258190, DS3ItemCategory.SPELL), - ("Seething Chaos", 0x402896A0, DS3ItemCategory.SPELL), - ("Lightning Arrow", 0x40358B08, DS3ItemCategory.SPELL), - ("Way of White Corona", 0x403642A0, DS3ItemCategory.SPELL), - ("Projected Heal", 0x40364688, DS3ItemCategory.SPELL), -]] + DS3ItemData("Frozen Weapon", 0x401408E8, DS3ItemCategory.SPELL), + DS3ItemData("Old Moonlight", 0x4014FF00, DS3ItemCategory.SPELL), + DS3ItemData("Great Soul Dregs", 0x401879A0, DS3ItemCategory.SPELL), + DS3ItemData("Snap Freeze", 0x401A90C8, DS3ItemCategory.SPELL), + DS3ItemData("Floating Chaos", 0x40257DA8, DS3ItemCategory.SPELL), + DS3ItemData("Flame Fan", 0x40258190, DS3ItemCategory.SPELL), + DS3ItemData("Seething Chaos", 0x402896A0, DS3ItemCategory.SPELL), + DS3ItemData("Lightning Arrow", 0x40358B08, DS3ItemCategory.SPELL), + DS3ItemData("Way of White Corona", 0x403642A0, DS3ItemCategory.SPELL), + DS3ItemData("Projected Heal", 0x40364688, DS3ItemCategory.SPELL), +] +for item in _dlc_items: + item.is_dlc = True # Unused list for future reference # These items exist to some degree in the code, but aren't accessible # in-game and can't be picked up without modifications -_cut_content_items = [DS3ItemData(row[0], row[1], False, row[2]) for row in [ +_cut_content_items = [ # Weapons - ("Blood-stained Short Sword", 0x00100590, DS3ItemCategory.SKIP), - ("Missionary's Axe", 0x006C2F50, DS3ItemCategory.SKIP), - ("Dragon King Greataxe", 0x006D40C0, DS3ItemCategory.SKIP), - ("Four Knights Hammer", 0x007D4650, DS3ItemCategory.SKIP), - ("Hammer of the Great Tree", 0x007D9470, DS3ItemCategory.SKIP), - ("Lothric's Scythe", 0x009A4430, DS3ItemCategory.SKIP), - ("Ancient Dragon Halberd", 0x009A6B40, DS3ItemCategory.SKIP), - ("Scythe of Want", 0x009A9250, DS3ItemCategory.SKIP), - ("Sacred Beast Catalyst", 0x00C8A730, DS3ItemCategory.SKIP), - ("Deep Pyromancy Flame", 0x00CC9ED0, DS3ItemCategory.SKIP), # Duplicate? - ("Flickering Pyromancy Flame", 0x00CD3B10, DS3ItemCategory.SKIP), - ("Strong Pyromancy Flame", 0x00CD6220, DS3ItemCategory.SKIP), - ("Deep Pyromancy Flame", 0x00CDFE60, DS3ItemCategory.SKIP), # Duplicate? - ("Pitch-Dark Pyromancy Flame", 0x00CE2570, DS3ItemCategory.SKIP), - ("Dancer's Short Bow", 0x00D77440, DS3ItemCategory.SKIP), - ("Shield Crossbow", 0x00D81080, DS3ItemCategory.SKIP), - ("Golden Dual Swords", 0x00F55C80, DS3ItemCategory.SKIP), - ("Channeler's Trident", 0x008C8890, DS3ItemCategory.SKIP), + DS3ItemData("Blood-stained Short Sword", 0x00100590, DS3ItemCategory.UNIQUE), + DS3ItemData("Missionary's Axe", 0x006C2F50, DS3ItemCategory.UNIQUE), + DS3ItemData("Dragon King Greataxe", 0x006D40C0, DS3ItemCategory.UNIQUE), + DS3ItemData("Four Knights Hammer", 0x007D4650, DS3ItemCategory.UNIQUE), + DS3ItemData("Hammer of the Great Tree", 0x007D9470, DS3ItemCategory.UNIQUE), + DS3ItemData("Lothric's Scythe", 0x009A4430, DS3ItemCategory.UNIQUE), + DS3ItemData("Ancient Dragon Halberd", 0x009A6B40, DS3ItemCategory.UNIQUE), + DS3ItemData("Scythe of Want", 0x009A9250, DS3ItemCategory.UNIQUE), + DS3ItemData("Sacred Beast Catalyst", 0x00C8A730, DS3ItemCategory.UNIQUE), + DS3ItemData("Deep Pyromancy Flame", 0x00CC9ED0, DS3ItemCategory.UNIQUE), # Duplicate? + DS3ItemData("Flickering Pyromancy Flame", 0x00CD3B10, DS3ItemCategory.UNIQUE), + DS3ItemData("Strong Pyromancy Flame", 0x00CD6220, DS3ItemCategory.UNIQUE), + DS3ItemData("Deep Pyromancy Flame", 0x00CDFE60, DS3ItemCategory.UNIQUE), # Duplicate? + DS3ItemData("Pitch-Dark Pyromancy Flame", 0x00CE2570, DS3ItemCategory.UNIQUE), + DS3ItemData("Dancer's Short Bow", 0x00D77440, DS3ItemCategory.UNIQUE), + DS3ItemData("Shield Crossbow", 0x00D81080, DS3ItemCategory.UNIQUE), + DS3ItemData("Golden Dual Swords", 0x00F55C80, DS3ItemCategory.UNIQUE), + DS3ItemData("Channeler's Trident", 0x008C8890, DS3ItemCategory.UNIQUE), # Shields - ("Cleric's Parma", 0x013524A0, DS3ItemCategory.SKIP), - ("Prince's Shield", 0x01421CF0, DS3ItemCategory.SKIP), + DS3ItemData("Cleric's Parma", 0x013524A0, DS3ItemCategory.UNIQUE), + DS3ItemData("Prince's Shield", 0x01421CF0, DS3ItemCategory.UNIQUE), # Armor - ("Dingy Maiden's Overcoat", 0x11DA9048, DS3ItemCategory.SKIP), - ("Grotto Hat", 0x11F78A40, DS3ItemCategory.SKIP), - ("Grotto Robe", 0x11F78E28, DS3ItemCategory.SKIP), - ("Grotto Wrap", 0x11F79210, DS3ItemCategory.SKIP), - ("Grotto Trousers", 0x11F795F8, DS3ItemCategory.SKIP), - ("Soldier's Gauntlets", 0x126261D0, DS3ItemCategory.SKIP), - ("Soldier's Hood", 0x1263E0A0, DS3ItemCategory.SKIP), - ("Elder's Robe", 0x129024A8, DS3ItemCategory.SKIP), - ("Saint's Veil", 0x12A70420, DS3ItemCategory.SKIP), - ("Saint's Dress", 0x12A70808, DS3ItemCategory.SKIP), - ("Footman's Hood", 0x12AEA540, DS3ItemCategory.SKIP), - ("Footman's Overcoat", 0x12AEA928, DS3ItemCategory.SKIP), - ("Footman's Bracelets", 0x12AEAD10, DS3ItemCategory.SKIP), - ("Footman's Trousers", 0x12AEB0F8, DS3ItemCategory.SKIP), - ("Scholar's Shed Skin", 0x12E40D20, DS3ItemCategory.SKIP), - ("Man Serpent's Mask", 0x138BE5E0, DS3ItemCategory.SKIP), - ("Man Serpent's Robe", 0x138BE9C8, DS3ItemCategory.SKIP), - ("Old Monarch's Crown", 0x13DFD240, DS3ItemCategory.SKIP), - ("Old Monarch's Robe", 0x13DFD628, DS3ItemCategory.SKIP), - ("Frigid Valley Mask", 0x13FE56C0, DS3ItemCategory.SKIP), - ("Dingy Hood", 0x140D9900, DS3ItemCategory.SKIP), - ("Hexer's Hood", 0x15A995C0, DS3ItemCategory.SKIP), - ("Hexer's Robes", 0x15A999A8, DS3ItemCategory.SKIP), - ("Hexer's Gloves", 0x15A99D90, DS3ItemCategory.SKIP), - ("Hexer's Boots", 0x15A9A178, DS3ItemCategory.SKIP), - ("Varangian Helm", 0x15C81A40, DS3ItemCategory.SKIP), - ("Varangian Armor", 0x15C81E28, DS3ItemCategory.SKIP), - ("Varangian Cuffs", 0x15C82210, DS3ItemCategory.SKIP), - ("Varangian Leggings", 0x15C825F8, DS3ItemCategory.SKIP), + DS3ItemData("Dingy Maiden's Overcoat", 0x11DA9048, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Hat", 0x11F78A40, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Robe", 0x11F78E28, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Wrap", 0x11F79210, DS3ItemCategory.UNIQUE), + DS3ItemData("Grotto Trousers", 0x11F795F8, DS3ItemCategory.UNIQUE), + DS3ItemData("Soldier's Gauntlets", 0x126261D0, DS3ItemCategory.UNIQUE), + DS3ItemData("Soldier's Hood", 0x1263E0A0, DS3ItemCategory.UNIQUE), + DS3ItemData("Elder's Robe", 0x129024A8, DS3ItemCategory.UNIQUE), + DS3ItemData("Saint's Veil", 0x12A70420, DS3ItemCategory.UNIQUE), + DS3ItemData("Saint's Dress", 0x12A70808, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Hood", 0x12AEA540, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Overcoat", 0x12AEA928, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Bracelets", 0x12AEAD10, DS3ItemCategory.UNIQUE), + DS3ItemData("Footman's Trousers", 0x12AEB0F8, DS3ItemCategory.UNIQUE), + DS3ItemData("Scholar's Shed Skin", 0x12E40D20, DS3ItemCategory.UNIQUE), + DS3ItemData("Man Serpent's Mask", 0x138BE5E0, DS3ItemCategory.UNIQUE), + DS3ItemData("Man Serpent's Robe", 0x138BE9C8, DS3ItemCategory.UNIQUE), + DS3ItemData("Old Monarch's Crown", 0x13DFD240, DS3ItemCategory.UNIQUE), + DS3ItemData("Old Monarch's Robe", 0x13DFD628, DS3ItemCategory.UNIQUE), + DS3ItemData("Frigid Valley Mask", 0x13FE56C0, DS3ItemCategory.UNIQUE), + DS3ItemData("Dingy Hood", 0x140D9900, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Hood", 0x15A995C0, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Robes", 0x15A999A8, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Gloves", 0x15A99D90, DS3ItemCategory.UNIQUE), + DS3ItemData("Hexer's Boots", 0x15A9A178, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Helm", 0x15C81A40, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Armor", 0x15C81E28, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Cuffs", 0x15C82210, DS3ItemCategory.UNIQUE), + DS3ItemData("Varangian Leggings", 0x15C825F8, DS3ItemCategory.UNIQUE), # Rings - ("Rare Ring of Sacrifice", 0x20004EFC, DS3ItemCategory.SKIP), - ("Baneful Bird Ring", 0x20005032, DS3ItemCategory.SKIP), - ("Darkmoon Blade Covenant Ring", 0x20004F7E, DS3ItemCategory.SKIP), - ("Yorgh's Ring", 0x2000505A, DS3ItemCategory.SKIP), - ("Ring of Hiding", 0x200050D2, DS3ItemCategory.SKIP), - ("Ring of Sustained Toughness", 0x20005118, DS3ItemCategory.SKIP), - ("Ring of Sustained Energy", 0x20005122, DS3ItemCategory.SKIP), - ("Ring of Sustained Magic", 0x2000512C, DS3ItemCategory.SKIP), - ("Ring of Sustained Essence", 0x20005140, DS3ItemCategory.SKIP), - ("Ring of Sustained Might", 0x2000514A, DS3ItemCategory.SKIP), - ("Ring of Sustained Fortune", 0x20005154, DS3ItemCategory.SKIP), + DS3ItemData("Rare Ring of Sacrifice", 0x20004EFC, DS3ItemCategory.UNIQUE), + DS3ItemData("Baneful Bird Ring", 0x20005032, DS3ItemCategory.UNIQUE), + DS3ItemData("Darkmoon Blade Covenant Ring", 0x20004F7E, DS3ItemCategory.UNIQUE), + DS3ItemData("Yorgh's Ring", 0x2000505A, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Hiding", 0x200050D2, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Toughness", 0x20005118, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Energy", 0x20005122, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Magic", 0x2000512C, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Essence", 0x20005140, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Might", 0x2000514A, DS3ItemCategory.UNIQUE), + DS3ItemData("Ring of Sustained Fortune", 0x20005154, DS3ItemCategory.UNIQUE), # Items - ("Soul of a Wicked Spirit", 0x400002C9, DS3ItemCategory.SKIP), + DS3ItemData("Soul of a Wicked Spirit", 0x400002C9, DS3ItemCategory.UNIQUE), # Spells - ("Dark Orb", 0x4027AC40, DS3ItemCategory.SKIP), - ("Morbid Temptation", 0x40359AA8, DS3ItemCategory.SKIP), - ("Dorris Swarm", 0x40393870, DS3ItemCategory.SKIP), -]] + DS3ItemData("Dark Orb", 0x4027AC40, DS3ItemCategory.UNIQUE), + DS3ItemData("Morbid Temptation", 0x40359AA8, DS3ItemCategory.UNIQUE), + DS3ItemData("Dorris Swarm", 0x40393870, DS3ItemCategory.UNIQUE), +] + + +item_name_groups: Dict[str, Set] = { + "Progression": set(), + "Cinders": set(), + "Weapons": set(), + "Shields": set(), + "Armor": set(), + "Rings": set(), + "Spells": set(), + "Miscellaneous": set(), + "Unique": set(), + "Boss Souls": set(), + "Small Souls": set(), + "Upgrade": set(), + "Healing": set(), +} + item_descriptions = { + "Progression": "Items which unlock locations.", "Cinders": "All four Cinders of a Lord.\n\nOnce you have these four, you can fight Soul of Cinder and win the game.", + "Miscellaneous": "Generic stackable items, such as arrows, firebombs, buffs, and so on.", + "Unique": "Items that are unique per NG cycle, such as scrolls, keys, ashes, and so on. Doesn't include equipment, spells, or souls.", + "Boss Souls": "Souls that can be traded with Ludleth, including Soul of Rosaria.", + "Small Souls": "Soul items, not including boss souls.", + "Upgrade": "Upgrade items, including titanite, gems, and Shriving Stones.", + "Healing": "Undead Bone Shards and Estus Shards.", } + _all_items = _vanilla_items + _dlc_items +for item_data in _all_items: + for group_name in item_data.item_groups(): + item_name_groups[group_name].add(item_data.name) + +filler_item_names = [item_data.name for item_data in _all_items if item_data.filler] item_dictionary = {item_data.name: item_data for item_data in _all_items} diff --git a/worlds/dark_souls_3/Locations.py b/worlds/dark_souls_3/Locations.py index df241a5fd1f..08f4b7cd1a8 100644 --- a/worlds/dark_souls_3/Locations.py +++ b/worlds/dark_souls_3/Locations.py @@ -1,693 +1,3119 @@ -from enum import IntEnum -from typing import Optional, NamedTuple, Dict +from typing import cast, ClassVar, Optional, Dict, List, Set +from dataclasses import dataclass -from BaseClasses import Location, Region +from BaseClasses import ItemClassification, Location, Region +from .Items import DS3ItemCategory, item_dictionary +# Regions in approximate order of reward, mostly measured by how high-quality the upgrade items are +# in each region. +region_order = [ + "Cemetery of Ash", + "Firelink Shrine", + "High Wall of Lothric", + "Greirat's Shop", + "Undead Settlement", + "Road of Sacrifices", + "Farron Keep", + "Cathedral of the Deep", + "Catacombs of Carthus", + "Smouldering Lake", + "Irithyll of the Boreal Valley", + "Irithyll Dungeon", + "Karla's Shop", + # The first half of Painted World has one Titanite Slab but mostly Large Titanite Shards, + # much like Irithyll Dungeon. + "Painted World of Ariandel (Before Contraption)", + "Anor Londo", + "Profaned Capital", + # The second half of Painted World has two Titanite Chunks and two Titanite Slabs, which + # puts it on the low end of the post-Lothric Castle areas in terms of rewards. + "Painted World of Ariandel (After Contraption)", + "Lothric Castle", + "Consumed King's Garden", + "Untended Graves", + # List this late because it contains a Titanite Slab in the base game + "Firelink Shrine Bell Tower", + "Grand Archives", + "Archdragon Peak", + "Kiln of the First Flame", + # Both areas of DLC2 have premium rewards. + "Dreg Heap", + "Ringed City", +] -class DS3LocationCategory(IntEnum): - WEAPON = 0 - SHIELD = 1 - ARMOR = 2 - RING = 3 - SPELL = 4 - NPC = 5 - KEY = 6 - BOSS = 7 - MISC = 8 - HEALTH = 9 - PROGRESSIVE_ITEM = 10 - EVENT = 11 +@dataclass +class DS3LocationData: + __location_id: ClassVar[int] = 100000 + """The next location ID to use when creating location data.""" -class DS3LocationData(NamedTuple): name: str - default_item: str - category: DS3LocationCategory + """The name of this location according to Archipelago. + + This needs to be unique within this world.""" + + default_item_name: Optional[str] + """The name of the item that appears by default in this location. + + If this is None, that indicates that this location is an "event" that's + automatically considered accessed as soon as it's available. Events are used + to indicate major game transitions that aren't otherwise gated by items so + that progression balancing and item smoothing is more accurate for DS3. + """ + + ap_code: Optional[int] = None + """Archipelago's internal ID for this location (also known as its "address").""" + + region_value: int = 0 + """The relative value of items in this location's region. + + This is used to sort locations when placing items like the base game. + """ + + static: Optional[str] = None + """The key in the static randomizer's Slots table that corresponds to this location. + + By default, the static randomizer chooses its location based on the region and the item name. + If the item name is unique across the whole game, it can also look it up based on that alone. If + there are multiple instances of the same item type in the same region, it will assume its order + (in annotations.txt) matches Archipelago's order. + + In cases where this heuristic doesn't work, such as when Archipelago's region categorization or + item name disagrees with the static randomizer's, this field is used to provide an explicit + association instead. + """ + + missable: bool = False + """Whether this item is possible to permanently lose access to. + + This is also used for items that are *technically* possible to get at any time, but are + prohibitively difficult without blocking off other checks (items dropped by NPCs on death + generally fall into this category). + + Missable locations are always marked as excluded, so they will never contain + progression or useful items. + """ + + dlc: bool = False + """Whether this location is only accessible if the DLC is enabled.""" + + ngp: bool = False + """Whether this location only contains an item in NG+ and later. + + By default, these items aren't randomized or included in the randomization pool, but an option + can be set to enable them even for NG runs.""" + + npc: bool = False + """Whether this item is contingent on killing an NPC or following their quest.""" + + prominent: bool = False + """Whether this is one of few particularly prominent places for items to appear. + + This is a small number of locations (boss drops and progression locations) + intended to be set as priority locations for players who don't want a lot of + mandatory checks. + + For bosses with multiple drops, only one should be marked prominent. + """ + + progression: bool = False + """Whether this location normally contains an item that blocks forward progress.""" + + boss: bool = False + """Whether this location is a reward for defeating a full boss.""" + + miniboss: bool = False + """Whether this location is a reward for defeating a miniboss. + + The classification of "miniboss" is a bit fuzzy, but we consider them to be enemies that are + visually distinctive in their locations, usually bigger than normal enemies, with a guaranteed + item drop. NPCs are never considered minibosses, and some normal-looking enemies with guaranteed + drops aren't either (these are instead classified as hidden locations).""" + + drop: bool = False + """Whether this is an item dropped by a (non-boss) enemy. + + This is automatically set to True if miniboss, mimic, lizard, or hostile_npc is True. + """ + + mimic: bool = False + """Whether this location is dropped by a mimic.""" + + hostile_npc: bool = False + """Whether this location is dropped by a hostile NPC. + + An "NPC" is specifically a human (or rather, ash) is built like a player character rather than a + monster. This includes both scripted invaders and NPCs who are always on the overworld. It does + not include initially-friendly NPCs who become hostile as part of a quest or because you attack + them. + """ + + lizard: bool = False + """Whether this location is dropped by a (small) Crystal Lizard.""" + + shop: bool = False + """Whether this location can appear in an NPC's shop. + + Items like Lapp's Set which can appear both in the overworld and in a shop + should still be tagged as shop. + """ + + conditional: bool = False + """Whether this location is conditional on a progression item. + + This is used to track locations that won't become available until an unknown amount of time into + the run, and as such shouldn't have "similar to the base game" items placed in them. + """ + + hidden: bool = False + """Whether this location is particularly tricky to find. + + This is for players without an encyclopedic knowledge of DS3 who don't want to get stuck looking + for an illusory wall or one random mob with a guaranteed drop. + """ + + @property + def is_event(self) -> bool: + """Whether this location represents an event rather than a specific item pickup.""" + return self.default_item_name is None + + def __post_init__(self): + if not self.is_event: + self.ap_code = self.ap_code or DS3LocationData.__location_id + DS3LocationData.__location_id += 1 + if self.miniboss or self.mimic or self.lizard or self.hostile_npc: self.drop = True + + def location_groups(self) -> List[str]: + """The names of location groups this location should appear in. + + This is computed from the properties assigned to this location.""" + names = [] + if self.prominent: names.append("Prominent") + if self.progression: names.append("Progression") + if self.boss: names.append("Boss Rewards") + if self.miniboss: names.append("Miniboss Rewards") + if self.mimic: names.append("Mimic Rewards") + if self.hostile_npc: names.append("Hostile NPC Rewards") + if self.npc: names.append("Friendly NPC Rewards") + if self.lizard: names.append("Small Crystal Lizards") + if self.hidden: names.append("Hidden") + + default_item = item_dictionary[cast(str, self.default_item_name)] + names.append({ + DS3ItemCategory.WEAPON_UPGRADE_5: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10: "Weapons", + DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE: "Weapons", + DS3ItemCategory.SHIELD: "Shields", + DS3ItemCategory.SHIELD_INFUSIBLE: "Shields", + DS3ItemCategory.ARMOR: "Armor", + DS3ItemCategory.RING: "Rings", + DS3ItemCategory.SPELL: "Spells", + DS3ItemCategory.MISC: "Miscellaneous", + DS3ItemCategory.UNIQUE: "Unique", + DS3ItemCategory.BOSS: "Boss Souls", + DS3ItemCategory.SOUL: "Small Souls", + DS3ItemCategory.UPGRADE: "Upgrade", + DS3ItemCategory.HEALING: "Healing", + }[default_item.category]) + if default_item.classification == ItemClassification.progression: + names.append("Progression") + + return names class DarkSouls3Location(Location): game: str = "Dark Souls III" - category: DS3LocationCategory - default_item_name: str + data: DS3LocationData def __init__( self, player: int, - name: str, - category: DS3LocationCategory, - default_item_name: str, - address: Optional[int] = None, - parent: Optional[Region] = None): - super().__init__(player, name, address, parent) - self.default_item_name = default_item_name - self.category = category - - @staticmethod - def get_name_to_id() -> dict: - base_id = 100000 - table_offset = 100 - - table_order = [ - "Firelink Shrine", - "Firelink Shrine Bell Tower", - "High Wall of Lothric", - "Undead Settlement", - "Road of Sacrifices", - "Cathedral of the Deep", - "Farron Keep", - "Catacombs of Carthus", - "Smouldering Lake", - "Irithyll of the Boreal Valley", - "Irithyll Dungeon", - "Profaned Capital", - "Anor Londo", - "Lothric Castle", - "Consumed King's Garden", - "Grand Archives", - "Untended Graves", - "Archdragon Peak", - - "Painted World of Ariandel 1", - "Painted World of Ariandel 2", - "Dreg Heap", - "Ringed City", - - "Progressive Items 1", - "Progressive Items 2", - "Progressive Items 3", - "Progressive Items 4", - "Progressive Items DLC", - "Progressive Items Health", - ] - - output = {} - for i, region_name in enumerate(table_order): - if len(location_tables[region_name]) > table_offset: - raise Exception("A location table has {} entries, that is more than {} entries (table #{})".format(len(location_tables[region_name]), table_offset, i)) - - output.update({location_data.name: id for id, location_data in enumerate(location_tables[region_name], base_id + (table_offset * i))}) - - return output - - -location_tables = { + data: DS3LocationData, + parent: Optional[Region] = None, + event: bool = False): + super().__init__(player, data.name, None if event else data.ap_code, parent) + self.data = data + + +# Naming conventions: +# +# * The regions in item names should match the physical region where the item is +# acquired, even if its logical region is different. For example, Irina's +# inventory appears in the "Undead Settlement" region because she's not +# accessible until there, but it begins with "FS:" because that's where her +# items are purchased. +# +# * Avoid using vanilla enemy placements as landmarks, because these are +# randomized by the enemizer by default. Instead, use generic terms like +# "mob", "boss", and "miniboss". +# +# * Location descriptions don't need to direct the player to the precise spot. +# You can assume the player is broadly familiar with Dark Souls III or willing +# to look at a vanilla guide. Just give a general area to look in or an idea +# of what quest a check is connected to. Terseness is valuable: try to keep +# each location description short enough that the whole line doesn't exceed +# 100 characters. +# +# * Use "[name] drop" for items that require killing an NPC who becomes hostile +# as part of their normal quest, "kill [name]" for items that require killing +# them even when they aren't hostile, and just "[name]" for items that are +# naturally available as part of their quest. +location_tables: Dict[str, List[DS3LocationData]] = { + "Cemetery of Ash": [ + DS3LocationData("CA: Soul of a Deserted Corpse - right of spawn", + "Soul of a Deserted Corpse"), + DS3LocationData("CA: Firebomb - down the cliff edge", "Firebomb x5"), + DS3LocationData("CA: Titanite Shard - jump to coffin", "Titanite Shard"), + DS3LocationData("CA: Soul of an Unknown Traveler - by miniboss", + "Soul of an Unknown Traveler"), + DS3LocationData("CA: Speckled Stoneplate Ring+1 - by miniboss", + "Speckled Stoneplate Ring+1", ngp=True), + DS3LocationData("CA: Titanite Scale - miniboss drop", "Titanite Scale", miniboss=True), + DS3LocationData("CA: Coiled Sword - boss drop", "Coiled Sword", prominent=True, + progression=True, boss=True), + ], "Firelink Shrine": [ - DS3LocationData("FS: Broken Straight Sword", "Broken Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("FS: East-West Shield", "East-West Shield", DS3LocationCategory.SHIELD), - DS3LocationData("FS: Uchigatana", "Uchigatana", DS3LocationCategory.WEAPON), - DS3LocationData("FS: Master's Attire", "Master's Attire", DS3LocationCategory.ARMOR), - DS3LocationData("FS: Master's Gloves", "Master's Gloves", DS3LocationCategory.ARMOR), + # Ludleth drop, does not permanently die + DS3LocationData("FS: Skull Ring - kill Ludleth", "Skull Ring", hidden=True, drop=True, + npc=True), + + # Sword Master drops + DS3LocationData("FS: Uchigatana - NPC drop", "Uchigatana", hostile_npc=True), + DS3LocationData("FS: Master's Attire - NPC drop", "Master's Attire", hostile_npc=True), + DS3LocationData("FS: Master's Gloves - NPC drop", "Master's Gloves", hostile_npc=True), + + DS3LocationData("FS: Broken Straight Sword - gravestone after boss", + "Broken Straight Sword"), + DS3LocationData("FS: Homeward Bone - cliff edge after boss", "Homeward Bone"), + DS3LocationData("FS: Ember - path right of Firelink entrance", "Ember"), + DS3LocationData("FS: Soul of a Deserted Corpse - bell tower door", + "Soul of a Deserted Corpse"), + DS3LocationData("FS: East-West Shield - tree by shrine entrance", "East-West Shield"), + DS3LocationData("FS: Homeward Bone - path above shrine entrance", "Homeward Bone"), + DS3LocationData("FS: Ember - above shrine entrance", "Ember"), + DS3LocationData("FS: Wolf Ring+2 - left of boss room exit", "Wolf Ring+2", ngp=True), + # Leonhard (quest) + DS3LocationData("FS: Cracked Red Eye Orb - Leonhard", "Cracked Red Eye Orb x5", + missable=True, npc=True), + # Leonhard (kill or quest), missable because he can disappear sometimes + DS3LocationData("FS: Lift Chamber Key - Leonhard", "Lift Chamber Key", missable=True, + npc=True, drop=True), + + # Shrine Handmaid shop + DS3LocationData("FS: White Sign Soapstone - shop", "White Sign Soapstone", shop=True), + DS3LocationData("FS: Dried Finger - shop", "Dried Finger", shop=True), + DS3LocationData("FS: Tower Key - shop", "Tower Key", progression=True, shop=True), + DS3LocationData("FS: Ember - shop", "Ember", static='99,0:-1:110000:', shop=True), + DS3LocationData("FS: Farron Dart - shop", "Farron Dart", static='99,0:-1:110000:', + shop=True), + DS3LocationData("FS: Soul Arrow - shop", "Soul Arrow", static='99,0:-1:110000:', + shop=True), + DS3LocationData("FS: Heal Aid - shop", "Heal Aid", shop=True), + DS3LocationData("FS: Alluring Skull - Mortician's Ashes", "Alluring Skull", shop=True, + conditional=True), + DS3LocationData("FS: Ember - Mortician's Ashes", "Ember", + static='99,0:-1:110000,70000100:', shop=True, conditional=True), + DS3LocationData("FS: Grave Key - Mortician's Ashes", "Grave Key", shop=True, + conditional=True), + DS3LocationData("FS: Life Ring - Dreamchaser's Ashes", "Life Ring", shop=True, + conditional=True), + # Only if you say where the ashes were found + DS3LocationData("FS: Hidden Blessing - Dreamchaser's Ashes", "Hidden Blessing", + missable=True, shop=True), + DS3LocationData("FS: Lloyd's Shield Ring - Paladin's Ashes", "Lloyd's Shield Ring", + shop=True, conditional=True), + DS3LocationData("FS: Ember - Grave Warden's Ashes", "Ember", + static='99,0:-1:110000,70000103:', shop=True, conditional=True), + # Prisoner Chief's Ashes + DS3LocationData("FS: Karla's Pointed Hat - Prisoner Chief's Ashes", "Karla's Pointed Hat", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Karla's Coat - Prisoner Chief's Ashes", "Karla's Coat", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Karla's Gloves - Prisoner Chief's Ashes", "Karla's Gloves", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Karla's Trousers - Prisoner Chief's Ashes", "Karla's Trousers", + static='99,0:-1:110000,70000105:', shop=True, conditional=True), + DS3LocationData("FS: Xanthous Overcoat - Xanthous Ashes", "Xanthous Overcoat", shop=True, + conditional=True), + DS3LocationData("FS: Xanthous Gloves - Xanthous Ashes", "Xanthous Gloves", shop=True, + conditional=True), + DS3LocationData("FS: Xanthous Trousers - Xanthous Ashes", "Xanthous Trousers", shop=True, + conditional=True), + DS3LocationData("FS: Ember - Dragon Chaser's Ashes", "Ember", + static='99,0:-1:110000,70000108:', shop=True, conditional=True), + DS3LocationData("FS: Washing Pole - Easterner's Ashes", "Washing Pole", shop=True, + conditional=True), + DS3LocationData("FS: Eastern Helm - Easterner's Ashes", "Eastern Helm", shop=True, + conditional=True), + DS3LocationData("FS: Eastern Armor - Easterner's Ashes", "Eastern Armor", shop=True, + conditional=True), + DS3LocationData("FS: Eastern Gauntlets - Easterner's Ashes", "Eastern Gauntlets", + shop=True, conditional=True), + DS3LocationData("FS: Eastern Leggings - Easterner's Ashes", "Eastern Leggings", shop=True, + conditional=True), + DS3LocationData("FS: Wood Grain Ring - Easterner's Ashes", "Wood Grain Ring", shop=True, + conditional=True), + DS3LocationData("FS: Millwood Knight Helm - Captain's Ashes", "Millwood Knight Helm", + dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Millwood Knight Armor - Captain's Ashes", "Millwood Knight Armor", + dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Millwood Knight Gauntlets - Captain's Ashes", + "Millwood Knight Gauntlets", dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Millwood Knight Leggings - Captain's Ashes", + "Millwood Knight Leggings", dlc=True, shop=True, conditional=True), + DS3LocationData("FS: Refined Gem - Captain's Ashes", "Refined Gem", dlc=True, shop=True, + conditional=True), + + # Ludleth Shop + DS3LocationData("FS: Vordt's Great Hammer - Ludleth for Vordt", "Vordt's Great Hammer", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Pontiff's Left Eye - Ludleth for Vordt", "Pontiff's Left Eye", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Bountiful Sunlight - Ludleth for Rosaria", "Bountiful Sunlight", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Darkmoon Longbow - Ludleth for Aldrich", "Darkmoon Longbow", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Lifehunt Scythe - Ludleth for Aldrich", "Lifehunt Scythe", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Hollowslayer Greatsword - Ludleth for Greatwood", + "Hollowslayer Greatsword", missable=True, boss=True, shop=True), + DS3LocationData("FS: Arstor's Spear - Ludleth for Greatwood", "Arstor's Spear", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Crystal Sage's Rapier - Ludleth for Sage", "Crystal Sage's Rapier", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Crystal Hail - Ludleth for Sage", "Crystal Hail", missable=True, + boss=True, shop=True), + DS3LocationData("FS: Cleric's Candlestick - Ludleth for Deacons", "Cleric's Candlestick", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Deep Soul - Ludleth for Deacons", "Deep Soul", missable=True, + boss=True, shop=True), + DS3LocationData("FS: Havel's Ring - Ludleth for Stray Demon", "Havel's Ring", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Boulder Heave - Ludleth for Stray Demon", "Boulder Heave", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Farron Greatsword - Ludleth for Abyss Watchers", "Farron Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Wolf Knight's Greatsword - Ludleth for Abyss Watchers", + "Wolf Knight's Greatsword", missable=True, boss=True, shop=True), + DS3LocationData("FS: Wolnir's Holy Sword - Ludleth for Wolnir", "Wolnir's Holy Sword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Black Serpent - Ludleth for Wolnir", "Black Serpent", missable=True, + boss=True, shop=True), + DS3LocationData("FS: Demon's Greataxe - Ludleth for Fire Demon", "Demon's Greataxe", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Demon's Fist - Ludleth for Fire Demon", "Demon's Fist", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Old King's Great Hammer - Ludleth for Old Demon King", + "Old King's Great Hammer", missable=True, boss=True, shop=True), + DS3LocationData("FS: Chaos Bed Vestiges - Ludleth for Old Demon King", "Chaos Bed Vestiges", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Greatsword of Judgment - Ludleth for Pontiff", + "Greatsword of Judgment", missable=True, boss=True, shop=True), + DS3LocationData("FS: Profaned Greatsword - Ludleth for Pontiff", "Profaned Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Yhorm's Great Machete - Ludleth for Yhorm", "Yhorm's Great Machete", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Yhorm's Greatshield - Ludleth for Yhorm", "Yhorm's Greatshield", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Dancer's Enchanted Swords - Ludleth for Dancer", + "Dancer's Enchanted Swords", missable=True, boss=True, shop=True), + DS3LocationData("FS: Soothing Sunlight - Ludleth for Dancer", "Soothing Sunlight", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Dragonslayer Greataxe - Ludleth for Dragonslayer", + "Dragonslayer Greataxe", missable=True, boss=True, shop=True), + DS3LocationData("FS: Dragonslayer Greatshield - Ludleth for Dragonslayer", + "Dragonslayer Greatshield", missable=True, boss=True, shop=True), + DS3LocationData("FS: Moonlight Greatsword - Ludleth for Oceiros", "Moonlight Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: White Dragon Breath - Ludleth for Oceiros", "White Dragon Breath", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Lorian's Greatsword - Ludleth for Princes", "Lorian's Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Lothric's Holy Sword - Ludleth for Princes", "Lothric's Holy Sword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Gundyr's Halberd - Ludleth for Champion", "Gundyr's Halberd", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Prisoner's Chain - Ludleth for Champion", "Prisoner's Chain", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Storm Curved Sword - Ludleth for Nameless", "Storm Curved Sword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Dragonslayer Swordspear - Ludleth for Nameless", + "Dragonslayer Swordspear", missable=True, boss=True, shop=True), + DS3LocationData("FS: Lightning Storm - Ludleth for Nameless", "Lightning Storm", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Firelink Greatsword - Ludleth for Cinder", "Firelink Greatsword", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Sunlight Spear - Ludleth for Cinder", "Sunlight Spear", + missable=True, boss=True, shop=True), + DS3LocationData("FS: Friede's Great Scythe - Ludleth for Friede", "Friede's Great Scythe", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Rose of Ariandel - Ludleth for Friede", "Rose of Ariandel", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Demon's Scar - Ludleth for Demon Prince", "Demon's Scar", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Seething Chaos - Ludleth for Demon Prince", "Seething Chaos", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Frayed Blade - Ludleth for Midir", "Frayed Blade", missable=True, + dlc=True, boss=True, shop=True), + DS3LocationData("FS: Old Moonlight - Ludleth for Midir", "Old Moonlight", missable=True, + dlc=True, boss=True, shop=True), + DS3LocationData("FS: Gael's Greatsword - Ludleth for Gael", "Gael's Greatsword", + missable=True, dlc=True, boss=True, shop=True), + DS3LocationData("FS: Repeating Crossbow - Ludleth for Gael", "Repeating Crossbow", + missable=True, dlc=True, boss=True, shop=True), ], "Firelink Shrine Bell Tower": [ - DS3LocationData("FSBT: Covetous Silver Serpent Ring", "Covetous Silver Serpent Ring", DS3LocationCategory.RING), - DS3LocationData("FSBT: Fire Keeper Robe", "Fire Keeper Robe", DS3LocationCategory.ARMOR), - DS3LocationData("FSBT: Fire Keeper Gloves", "Fire Keeper Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("FSBT: Fire Keeper Skirt", "Fire Keeper Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("FSBT: Estus Ring", "Estus Ring", DS3LocationCategory.RING), - DS3LocationData("FSBT: Fire Keeper Soul", "Fire Keeper Soul", DS3LocationCategory.MISC), + # Guarded by Tower Key + DS3LocationData("FSBT: Homeward Bone - roof", "Homeward Bone x3"), + DS3LocationData("FSBT: Estus Ring - tower base", "Estus Ring"), + DS3LocationData("FSBT: Estus Shard - rafters", "Estus Shard"), + DS3LocationData("FSBT: Fire Keeper Soul - tower top", "Fire Keeper Soul"), + DS3LocationData("FSBT: Fire Keeper Robe - partway down tower", "Fire Keeper Robe"), + DS3LocationData("FSBT: Fire Keeper Gloves - partway down tower", "Fire Keeper Gloves"), + DS3LocationData("FSBT: Fire Keeper Skirt - partway down tower", "Fire Keeper Skirt"), + DS3LocationData("FSBT: Covetous Silver Serpent Ring - illusory wall past rafters", + "Covetous Silver Serpent Ring", hidden=True), + DS3LocationData("FSBT: Twinkling Titanite - lizard behind Firelink", + "Twinkling Titanite", lizard=True), + + # Mark all crow trades as missable since no one wants to have to try trading everything just + # in case it gives a progression item. + DS3LocationData("FSBT: Iron Bracelets - crow for Homeward Bone", "Iron Bracelets", + missable=True), + DS3LocationData("FSBT: Ring of Sacrifice - crow for Loretta's Bone", "Ring of Sacrifice", + missable=True), + DS3LocationData("FSBT: Porcine Shield - crow for Undead Bone Shard", "Porcine Shield", + missable=True), + DS3LocationData("FSBT: Lucatiel's Mask - crow for Vertebra Shackle", "Lucatiel's Mask", + missable=True), + DS3LocationData("FSBT: Very good! Carving - crow for Divine Blessing", + "Very good! Carving", missable=True), + DS3LocationData("FSBT: Thank you Carving - crow for Hidden Blessing", "Thank you Carving", + missable=True), + DS3LocationData("FSBT: I'm sorry Carving - crow for Shriving Stone", "I'm sorry Carving", + missable=True), + DS3LocationData("FSBT: Sunlight Shield - crow for Mendicant's Staff", "Sunlight Shield", + missable=True), + DS3LocationData("FSBT: Hollow Gem - crow for Eleonora", "Hollow Gem", + missable=True), + DS3LocationData("FSBT: Titanite Scale - crow for Blacksmith Hammer", "Titanite Scale x3", + static='99,0:50004330::', missable=True), + DS3LocationData("FSBT: Help me! Carving - crow for any sacred chime", "Help me! Carving", + missable=True), + DS3LocationData("FSBT: Titanite Slab - crow for Coiled Sword Fragment", "Titanite Slab", + missable=True), + DS3LocationData("FSBT: Hello Carving - crow for Alluring Skull", "Hello Carving", + missable=True), + DS3LocationData("FSBT: Armor of the Sun - crow for Siegbräu", "Armor of the Sun", + missable=True), + DS3LocationData("FSBT: Large Titanite Shard - crow for Firebomb", "Large Titanite Shard", + missable=True), + DS3LocationData("FSBT: Titanite Chunk - crow for Black Firebomb", "Titanite Chunk", + missable=True), + DS3LocationData("FSBT: Iron Helm - crow for Lightning Urn", "Iron Helm", missable=True), + DS3LocationData("FSBT: Twinkling Titanite - crow for Prism Stone", "Twinkling Titanite", + missable=True), + DS3LocationData("FSBT: Iron Leggings - crow for Seed of a Giant Tree", "Iron Leggings", + missable=True), + DS3LocationData("FSBT: Lightning Gem - crow for Xanthous Crown", "Lightning Gem", + missable=True), + DS3LocationData("FSBT: Twinkling Titanite - crow for Large Leather Shield", + "Twinkling Titanite", missable=True), + DS3LocationData("FSBT: Blessed Gem - crow for Moaning Shield", "Blessed Gem", + missable=True), ], "High Wall of Lothric": [ - DS3LocationData("HWL: Deep Battle Axe", "Deep Battle Axe", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Club", "Club", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Claymore", "Claymore", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Binoculars", "Binoculars", DS3LocationCategory.MISC), - DS3LocationData("HWL: Longbow", "Longbow", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Mail Breaker", "Mail Breaker", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Broadsword", "Broadsword", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Silver Eagle Kite Shield", "Silver Eagle Kite Shield", DS3LocationCategory.SHIELD), - DS3LocationData("HWL: Astora's Straight Sword", "Astora's Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Cell Key", "Cell Key", DS3LocationCategory.KEY), - DS3LocationData("HWL: Rapier", "Rapier", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Lucerne", "Lucerne", DS3LocationCategory.WEAPON), - DS3LocationData("HWL: Small Lothric Banner", "Small Lothric Banner", DS3LocationCategory.KEY), - DS3LocationData("HWL: Basin of Vows", "Basin of Vows", DS3LocationCategory.KEY), - DS3LocationData("HWL: Soul of Boreal Valley Vordt", "Soul of Boreal Valley Vordt", DS3LocationCategory.BOSS), - DS3LocationData("HWL: Soul of the Dancer", "Soul of the Dancer", DS3LocationCategory.BOSS), - DS3LocationData("HWL: Way of Blue", "Way of Blue", DS3LocationCategory.MISC), - DS3LocationData("HWL: Greirat's Ashes", "Greirat's Ashes", DS3LocationCategory.NPC), - DS3LocationData("HWL: Blue Tearstone Ring", "Blue Tearstone Ring", DS3LocationCategory.NPC), + DS3LocationData("HWL: Soul of Boreal Valley Vordt", "Soul of Boreal Valley Vordt", + prominent=True, boss=True), + DS3LocationData("HWL: Soul of the Dancer", "Soul of the Dancer", prominent=True, + boss=True), + DS3LocationData("HWL: Basin of Vows - Emma", "Basin of Vows", prominent=True, + progression=True, conditional=True), + DS3LocationData("HWL: Small Lothric Banner - Emma", "Small Lothric Banner", + prominent=True, progression=True), + DS3LocationData("HWL: Green Blossom - fort walkway, hall behind wheel", "Green Blossom x2", + hidden=True), + DS3LocationData("HWL: Gold Pine Resin - corpse tower, drop", "Gold Pine Resin x2", + hidden=True), + DS3LocationData("HWL: Large Soul of a Deserted Corpse - flame plaza", + "Large Soul of a Deserted Corpse"), + DS3LocationData("HWL: Soul of a Deserted Corpse - by wall tower door", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Standard Arrow - back tower", "Standard Arrow x12"), + DS3LocationData("HWL: Longbow - back tower", "Longbow"), + DS3LocationData("HWL: Firebomb - wall tower, beam", "Firebomb x3"), + DS3LocationData("HWL: Throwing Knife - wall tower, path to Greirat", "Throwing Knife x8"), + DS3LocationData("HWL: Soul of a Deserted Corpse - corpse tower, bottom floor", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Club - flame plaza", "Club"), + DS3LocationData("HWL: Claymore - flame plaza", "Claymore"), + DS3LocationData("HWL: Ember - flame plaza", "Ember"), + DS3LocationData("HWL: Firebomb - corpse tower, under table", "Firebomb x2"), + DS3LocationData("HWL: Titanite Shard - wall tower, corner by bonfire", "Titanite Shard", + hidden=True), + DS3LocationData("HWL: Undead Hunter Charm - fort, room off entry, in pot", + "Undead Hunter Charm x2", hidden=True), + DS3LocationData("HWL: Firebomb - top of ladder to fountain", "Firebomb x3"), + DS3LocationData("HWL: Cell Key - fort ground, down stairs", "Cell Key"), + DS3LocationData("HWL: Ember - fountain #1", "Ember"), + DS3LocationData("HWL: Soul of a Deserted Corpse - fort entry, corner", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Lucerne - promenade, side path", "Lucerne"), + DS3LocationData("HWL: Mail Breaker - wall tower, path to Greirat", "Mail Breaker"), + DS3LocationData("HWL: Titanite Shard - fort ground behind crates", "Titanite Shard", + hidden=True), + DS3LocationData("HWL: Rapier - fountain, corner", "Rapier"), + DS3LocationData("HWL: Titanite Shard - fort, room off entry", "Titanite Shard"), + DS3LocationData("HWL: Large Soul of a Deserted Corpse - fort roof", + "Large Soul of a Deserted Corpse"), + DS3LocationData("HWL: Black Firebomb - small roof over fountain", "Black Firebomb x3"), + DS3LocationData("HWL: Soul of a Deserted Corpse - path to corpse tower", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Ember - fountain #2", "Ember"), + DS3LocationData("HWL: Large Soul of a Deserted Corpse - platform by fountain", + "Large Soul of a Deserted Corpse", hidden=True), # Easily missed turnoff + DS3LocationData("HWL: Binoculars - corpse tower, upper platform", "Binoculars"), + DS3LocationData("HWL: Ring of Sacrifice - awning by fountain", + "Ring of Sacrifice", hidden=True), # Easily missed turnoff + DS3LocationData("HWL: Throwing Knife - shortcut, lift top", "Throwing Knife x6"), + DS3LocationData("HWL: Soul of a Deserted Corpse - path to back tower, by lift door", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Green Blossom - shortcut, lower courtyard", "Green Blossom x3"), + DS3LocationData("HWL: Broadsword - fort, room off walkway", "Broadsword"), + DS3LocationData("HWL: Soul of a Deserted Corpse - fountain, path to promenade", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Firebomb - fort roof", "Firebomb x3"), + DS3LocationData("HWL: Soul of a Deserted Corpse - wall tower, right of exit", + "Soul of a Deserted Corpse"), + DS3LocationData("HWL: Estus Shard - fort ground, on anvil", "Estus Shard"), + DS3LocationData("HWL: Fleshbite Ring+1 - fort roof, jump to other roof", + "Fleshbite Ring+1", ngp=True, hidden=True), # Hidden jump + DS3LocationData("HWL: Ring of the Evil Eye+2 - fort ground, far wall", + "Ring of the Evil Eye+2", ngp=True, hidden=True), # In barrels + DS3LocationData("HWL: Silver Eagle Kite Shield - fort mezzanine", + "Silver Eagle Kite Shield"), + DS3LocationData("HWL: Astora Straight Sword - fort walkway, drop down", + "Astora Straight Sword", hidden=True), # Hidden fall + DS3LocationData("HWL: Battle Axe - flame tower, mimic", "Battle Axe", + static='01,0:53000960::', mimic=True), + + # Only dropped after transformation + DS3LocationData("HWL: Ember - fort roof, transforming hollow", "Ember", hidden=True), + DS3LocationData("HWL: Titanite Shard - fort roof, transforming hollow", "Titanite Shard", + hidden=True), + DS3LocationData("HWL: Ember - back tower, transforming hollow", "Ember", hidden=True), + DS3LocationData("HWL: Titanite Shard - back tower, transforming hollow", "Titanite Shard", + hidden=True), + + DS3LocationData("HWL: Refined Gem - promenade miniboss", "Refined Gem", miniboss=True), + DS3LocationData("HWL: Way of Blue - Emma", "Way of Blue"), + # Categorize this as an NPC item so that it doesn't get randomized if the Lift Chamber Key + # isn't randomized, since in that case it's missable. + DS3LocationData("HWL: Red Eye Orb - wall tower, miniboss", "Red Eye Orb", + conditional=True, miniboss=True, npc=True), + DS3LocationData("HWL: Raw Gem - fort roof, lizard", "Raw Gem", lizard=True), ], "Undead Settlement": [ - DS3LocationData("US: Small Leather Shield", "Small Leather Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Whip", "Whip", DS3LocationCategory.WEAPON), - DS3LocationData("US: Reinforced Club", "Reinforced Club", DS3LocationCategory.WEAPON), - DS3LocationData("US: Blue Wooden Shield", "Blue Wooden Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Cleric Hat", "Cleric Hat", DS3LocationCategory.ARMOR), - DS3LocationData("US: Cleric Blue Robe", "Cleric Blue Robe", DS3LocationCategory.ARMOR), - DS3LocationData("US: Cleric Gloves", "Cleric Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("US: Cleric Trousers", "Cleric Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("US: Mortician's Ashes", "Mortician's Ashes", DS3LocationCategory.KEY), - DS3LocationData("US: Caestus", "Caestus", DS3LocationCategory.WEAPON), - DS3LocationData("US: Plank Shield", "Plank Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Flame Stoneplate Ring", "Flame Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("US: Caduceus Round Shield", "Caduceus Round Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Fire Clutch Ring", "Fire Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("US: Partizan", "Partizan", DS3LocationCategory.WEAPON), - DS3LocationData("US: Bloodbite Ring", "Bloodbite Ring", DS3LocationCategory.RING), - DS3LocationData("US: Red Hilted Halberd", "Red Hilted Halberd", DS3LocationCategory.WEAPON), - DS3LocationData("US: Saint's Talisman", "Saint's Talisman", DS3LocationCategory.WEAPON), - DS3LocationData("US: Irithyll Straight Sword", "Irithyll Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("US: Large Club", "Large Club", DS3LocationCategory.WEAPON), - DS3LocationData("US: Northern Helm", "Northern Helm", DS3LocationCategory.ARMOR), - DS3LocationData("US: Northern Armor", "Northern Armor", DS3LocationCategory.ARMOR), - DS3LocationData("US: Northern Gloves", "Northern Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("US: Northern Trousers", "Northern Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("US: Flynn's Ring", "Flynn's Ring", DS3LocationCategory.RING), - DS3LocationData("US: Mirrah Vest", "Mirrah Vest", DS3LocationCategory.ARMOR), - DS3LocationData("US: Mirrah Gloves", "Mirrah Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("US: Mirrah Trousers", "Mirrah Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("US: Chloranthy Ring", "Chloranthy Ring", DS3LocationCategory.RING), - DS3LocationData("US: Loincloth", "Loincloth", DS3LocationCategory.ARMOR), - DS3LocationData("US: Wargod Wooden Shield", "Wargod Wooden Shield", DS3LocationCategory.SHIELD), - DS3LocationData("US: Loretta's Bone", "Loretta's Bone", DS3LocationCategory.KEY), - DS3LocationData("US: Hand Axe", "Hand Axe", DS3LocationCategory.WEAPON), - DS3LocationData("US: Great Scythe", "Great Scythe", DS3LocationCategory.WEAPON), - DS3LocationData("US: Soul of the Rotted Greatwood", "Soul of the Rotted Greatwood", DS3LocationCategory.BOSS), - DS3LocationData("US: Hawk Ring", "Hawk Ring", DS3LocationCategory.RING), - DS3LocationData("US: Warrior of Sunlight", "Warrior of Sunlight", DS3LocationCategory.MISC), - DS3LocationData("US: Blessed Red and White Shield+1", "Blessed Red and White Shield+1", DS3LocationCategory.SHIELD), - DS3LocationData("US: Irina's Ashes", "Irina's Ashes", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Ashes", "Cornyx's Ashes", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Wrap", "Cornyx's Wrap", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Garb", "Cornyx's Garb", DS3LocationCategory.NPC), - DS3LocationData("US: Cornyx's Skirt", "Cornyx's Skirt", DS3LocationCategory.NPC), - DS3LocationData("US: Pyromancy Flame", "Pyromancy Flame", DS3LocationCategory.NPC), - DS3LocationData("US: Transposing Kiln", "Transposing Kiln", DS3LocationCategory.MISC), - DS3LocationData("US: Tower Key", "Tower Key", DS3LocationCategory.NPC), + DS3LocationData("US: Soul of the Rotted Greatwood", "Soul of the Rotted Greatwood", + prominent=True, boss=True), + DS3LocationData("US: Transposing Kiln - boss drop", "Transposing Kiln", boss=True), + # Missable because it's unavailable if you start as a Pyromancer + DS3LocationData("US: Pyromancy Flame - Cornyx", "Pyromancy Flame", missable=True, + npc=True), + DS3LocationData("US: Old Sage's Blindfold - kill Cornyx", "Old Sage's Blindfold", + npc=True), + DS3LocationData("US: Cornyx's Garb - kill Cornyx", "Cornyx's Garb", + static='02,0:50006141::', npc=True), + DS3LocationData("US: Cornyx's Wrap - kill Cornyx", "Cornyx's Wrap", + static='02,0:50006141::', npc=True), + DS3LocationData("US: Cornyx's Skirt - kill Cornyx", "Cornyx's Skirt", + static='02,0:50006141::', npc=True), + DS3LocationData("US: Tower Key - kill Irina", "Tower Key", missable=True, npc=True), + DS3LocationData("US: Flynn's Ring - tower village, rooftop", "Flynn's Ring"), + DS3LocationData("US: Undead Bone Shard - by white tree", "Undead Bone Shard"), + DS3LocationData("US: Alluring Skull - foot, behind carriage", "Alluring Skull x2"), + DS3LocationData("US: Mortician's Ashes - graveyard by white tree", "Mortician's Ashes", + progression=True), + DS3LocationData("US: Homeward Bone - tower village, jump from roof", "Homeward Bone x2", + static='02,0:53100040::', hidden=True), # Hidden fall + DS3LocationData("US: Caduceus Round Shield - right after stable exit", + "Caduceus Round Shield"), + DS3LocationData("US: Ember - tower basement, miniboss", "Ember"), + DS3LocationData("US: Soul of an Unknown Traveler - chasm crypt", + "Soul of an Unknown Traveler"), + DS3LocationData("US: Repair Powder - first building, balcony", "Repair Powder x2"), + DS3LocationData("US: Homeward Bone - stable roof", "Homeward Bone x2", + static='02,0:53100090::'), + DS3LocationData("US: Titanite Shard - back alley, side path", "Titanite Shard"), + DS3LocationData("US: Wargod Wooden Shield - Pit of Hollows", "Wargod Wooden Shield"), + DS3LocationData("US: Large Soul of a Deserted Corpse - on the way to tower, by well", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Ember - bridge on the way to tower", "Ember"), + DS3LocationData("US: Large Soul of a Deserted Corpse - stable", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Titanite Shard - porch after burning tree", "Titanite Shard"), + DS3LocationData("US: Alluring Skull - tower village building, upstairs", + "Alluring Skull x2"), + DS3LocationData("US: Charcoal Pine Bundle - first building, middle floor", + "Charcoal Pine Bundle x2"), + DS3LocationData("US: Blue Wooden Shield - graveyard by white tree", "Blue Wooden Shield"), + DS3LocationData("US: Cleric Hat - graveyard by white tree", "Cleric Hat"), + DS3LocationData("US: Cleric Blue Robe - graveyard by white tree", "Cleric Blue Robe"), + DS3LocationData("US: Cleric Gloves - graveyard by white tree", "Cleric Gloves"), + DS3LocationData("US: Cleric Trousers - graveyard by white tree", "Cleric Trousers"), + DS3LocationData("US: Soul of an Unknown Traveler - portcullis by burning tree", + "Soul of an Unknown Traveler"), + DS3LocationData("US: Charcoal Pine Resin - hanging corpse room", "Charcoal Pine Resin x2"), + DS3LocationData("US: Loincloth - by Velka statue", "Loincloth"), + DS3LocationData("US: Bloodbite Ring - miniboss in sewer", "Bloodbite Ring", + miniboss=True), # Giant Rat drop + DS3LocationData("US: Charcoal Pine Bundle - first building, bottom floor", + "Charcoal Pine Bundle x2"), + DS3LocationData("US: Soul of an Unknown Traveler - back alley, past crates", + "Soul of an Unknown Traveler", hidden=True), + DS3LocationData("US: Titanite Shard - back alley, up ladder", "Titanite Shard"), + DS3LocationData("US: Red Hilted Halberd - chasm crypt", "Red Hilted Halberd"), + DS3LocationData("US: Rusted Coin - awning above Dilapidated Bridge", "Rusted Coin x2"), + DS3LocationData("US: Caestus - sewer", "Caestus"), + DS3LocationData("US: Saint's Talisman - chasm, by ladder", "Saint's Talisman"), + DS3LocationData("US: Alluring Skull - on the way to tower, behind building", + "Alluring Skull x3"), + DS3LocationData("US: Large Club - tower village, by miniboss", "Large Club"), + DS3LocationData("US: Titanite Shard - chasm #1", "Titanite Shard"), + DS3LocationData("US: Titanite Shard - chasm #2", "Titanite Shard"), + DS3LocationData("US: Fading Soul - outside stable", "Fading Soul"), + DS3LocationData("US: Titanite Shard - lower path to Cliff Underside", "Titanite Shard", + hidden=True), # hidden fall + DS3LocationData("US: Hand Axe - by Cornyx", "Hand Axe"), + DS3LocationData("US: Soul of an Unknown Traveler - pillory past stable", + "Soul of an Unknown Traveler"), + DS3LocationData("US: Ember - by stairs to boss", "Ember"), + DS3LocationData("US: Mirrah Vest - tower village, jump from roof", "Mirrah Vest", + hidden=True), # Hidden fall + DS3LocationData("US: Mirrah Gloves - tower village, jump from roof", "Mirrah Gloves", + hidden=True), # Hidden fall + DS3LocationData("US: Mirrah Trousers - tower village, jump from roof", "Mirrah Trousers", + hidden=True), # Hidden fall + DS3LocationData("US: Plank Shield - outside stable, by NPC", "Plank Shield"), + DS3LocationData("US: Red Bug Pellet - tower village building, basement", + "Red Bug Pellet x2"), + DS3LocationData("US: Chloranthy Ring - tower village, jump from roof", "Chloranthy Ring", + hidden=True), # Hidden fall + DS3LocationData("US: Fire Clutch Ring - wooden walkway past stable", "Fire Clutch Ring"), + DS3LocationData("US: Estus Shard - under burning tree", "Estus Shard"), + DS3LocationData("US: Firebomb - stable roof", "Firebomb x6"), + # In enemy rando, the enemy may not burst through the wall and make this room obvious + DS3LocationData("US: Whip - back alley, behind wooden wall", "Whip", hidden=True), + DS3LocationData("US: Great Scythe - building by white tree, balcony", "Great Scythe"), + DS3LocationData("US: Homeward Bone - foot, drop overlook", "Homeward Bone", + static='02,0:53100540::'), + DS3LocationData("US: Large Soul of a Deserted Corpse - around corner by Cliff Underside", + "Large Soul of a Deserted Corpse", hidden=True), # Hidden corner + DS3LocationData("US: Ember - behind burning tree", "Ember"), + DS3LocationData("US: Large Soul of a Deserted Corpse - across from Foot of the High Wall", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Fading Soul - by white tree", "Fading Soul"), + DS3LocationData("US: Young White Branch - by white tree #1", "Young White Branch"), + DS3LocationData("US: Ember - by white tree", "Ember"), + DS3LocationData("US: Large Soul of a Deserted Corpse - by white tree", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Young White Branch - by white tree #2", "Young White Branch"), + DS3LocationData("US: Reinforced Club - by white tree", "Reinforced Club"), + DS3LocationData("US: Soul of a Nameless Soldier - top of tower", + "Soul of a Nameless Soldier"), + DS3LocationData("US: Loretta's Bone - first building, hanging corpse on balcony", + "Loretta's Bone"), + DS3LocationData("US: Northern Helm - tower village, hanging corpse", "Northern Helm"), + DS3LocationData("US: Northern Armor - tower village, hanging corpse", "Northern Armor"), + DS3LocationData("US: Northern Gloves - tower village, hanging corpse", "Northern Gloves"), + DS3LocationData("US: Northern Trousers - tower village, hanging corpse", + "Northern Trousers"), + DS3LocationData("US: Partizan - hanging corpse above Cliff Underside", "Partizan", + missable=True), # requires projectile + DS3LocationData("US: Flame Stoneplate Ring - hanging corpse by Mound-Maker transport", + "Flame Stoneplate Ring"), + DS3LocationData("US: Red and White Shield - chasm, hanging corpse", "Red and White Shield", + static="02,0:53100740::", missable=True), # requires projectile + DS3LocationData("US: Small Leather Shield - first building, hanging corpse by entrance", + "Small Leather Shield"), + DS3LocationData("US: Pale Tongue - tower village, hanging corpse", "Pale Tongue"), + DS3LocationData("US: Large Soul of a Deserted Corpse - hanging corpse room, over stairs", + "Large Soul of a Deserted Corpse"), + DS3LocationData("US: Kukri - hanging corpse above burning tree", "Kukri x9", + missable=True), # requires projectile + DS3LocationData("US: Life Ring+1 - tower on the way to village", "Life Ring+1", ngp=True), + DS3LocationData("US: Poisonbite Ring+1 - graveyard by white tree, near well", + "Poisonbite Ring+1", ngp=True), + DS3LocationData("US: Covetous Silver Serpent Ring+2 - tower village, drop down from roof", + "Covetous Silver Serpent Ring+2", ngp=True, hidden=True), # Hidden fall + DS3LocationData("US: Human Pine Resin - tower village building, chest upstairs", + "Human Pine Resin x4"), + DS3LocationData("US: Homeward Bone - tower village, right at start", "Homeward Bone", + static='02,0:53100540::'), + DS3LocationData("US: Irithyll Straight Sword - miniboss drop, by Road of Sacrifices", + "Irithyll Straight Sword", miniboss=True), + DS3LocationData("US: Fire Gem - tower village, miniboss drop", "Fire Gem", miniboss=True), + DS3LocationData("US: Warrior of Sunlight - hanging corpse room, drop through hole", + "Warrior of Sunlight", hidden=True), # hidden fall + DS3LocationData("US: Mound-makers - Hodrick", "Mound-makers", missable=True), + DS3LocationData("US: Sharp Gem - lizard by Dilapidated Bridge", "Sharp Gem", lizard=True), + DS3LocationData("US: Heavy Gem - chasm, lizard", "Heavy Gem", lizard=True), + DS3LocationData("US: Siegbräu - Siegward", "Siegbräu", missable=True, npc=True), + DS3LocationData("US: Heavy Gem - Hawkwood", "Heavy Gem", static='00,0:50006070::', + missable=True, npc=True), # Hawkwood (quest, after Greatwood or Sage) + DS3LocationData("US -> RS", None), + + # Yoel/Yuria of Londor + DS3LocationData("FS: Soul Arrow - Yoel/Yuria", "Soul Arrow", + static='99,0:-1:50000,110000,70000116:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Heavy Soul Arrow - Yoel/Yuria", "Heavy Soul Arrow", + static='99,0:-1:50000,110000,70000116:', + missable=True, npc=True, shop=True), + DS3LocationData("FS: Magic Weapon - Yoel/Yuria", "Magic Weapon", + static='99,0:-1:50000,110000,70000116:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Magic Shield - Yoel/Yuria", "Magic Shield", + static='99,0:-1:50000,110000,70000116:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Soul Greatsword - Yoel/Yuria", "Soul Greatsword", + static='99,0:-1:50000,110000,70000450,70000475:', missable=True, + npc=True, shop=True), + DS3LocationData("FS: Dark Hand - Yoel/Yuria", "Dark Hand", missable=True, npc=True), + DS3LocationData("FS: Untrue White Ring - Yoel/Yuria", "Untrue White Ring", missable=True, + npc=True), + DS3LocationData("FS: Untrue Dark Ring - Yoel/Yuria", "Untrue Dark Ring", missable=True, + npc=True), + DS3LocationData("FS: Londor Braille Divine Tome - Yoel/Yuria", "Londor Braille Divine Tome", + static='99,0:-1:40000,110000,70000116:', missable=True, npc=True), + DS3LocationData("FS: Darkdrift - Yoel/Yuria", "Darkdrift", missable=True, drop=True, + npc=True), # kill her or kill Soul of Cinder + + # Cornyx of the Great Swamp + # These aren't missable because the Shrine Handmaid will carry them if you kill Cornyx. + DS3LocationData("FS: Fireball - Cornyx", "Fireball", npc=True, shop=True), + DS3LocationData("FS: Fire Surge - Cornyx", "Fire Surge", npc=True, shop=True), + DS3LocationData("FS: Great Combustion - Cornyx", "Great Combustion", npc=True, + shop=True), + DS3LocationData("FS: Flash Sweat - Cornyx", "Flash Sweat", npc=True, shop=True), + # These are missable if you kill Cornyx before giving him the right tomes. + DS3LocationData("FS: Poison Mist - Cornyx for Great Swamp Tome", "Poison Mist", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Fire Orb - Cornyx for Great Swamp Tome", "Fire Orb", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Profuse Sweat - Cornyx for Great Swamp Tome", "Profuse Sweat", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Bursting Fireball - Cornyx for Great Swamp Tome", "Bursting Fireball", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Acid Surge - Cornyx for Carthus Tome", "Acid Surge", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Carthus Flame Arc - Cornyx for Carthus Tome", "Carthus Flame Arc", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Carthus Beacon - Cornyx for Carthus Tome", "Carthus Beacon", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Great Chaos Fire Orb - Cornyx for Izalith Tome", + "Great Chaos Fire Orb", missable=True, npc=True, shop=True), + DS3LocationData("FS: Chaos Storm - Cornyx for Izalith Tome", "Chaos Storm", missable=True, + npc=True, shop=True), + + # Irina of Carim + # These aren't in their own location because you don't actually need the Grave Key to access + # Irena—you can just fall down the cliff near Eygon. + DS3LocationData("FS: Saint's Ring - Irina", "Saint's Ring", npc=True, shop=True), + DS3LocationData("FS: Heal - Irina", "Heal", npc=True, shop=True), + DS3LocationData("FS: Replenishment - Irina", "Replenishment", npc=True, shop=True), + DS3LocationData("FS: Caressing Tears - Irina", "Caressing Tears", npc=True, shop=True), + DS3LocationData("FS: Homeward - Irina", "Homeward", npc=True, shop=True), + DS3LocationData("FS: Med Heal - Irina for Tome of Carim", "Med Heal", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Tears of Denial - Irina for Tome of Carim", "Tears of Denial", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Force - Irina for Tome of Carim", "Force", missable=True, npc=True, + shop=True), + DS3LocationData("FS: Bountiful Light - Irina for Tome of Lothric", "Bountiful Light", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Magic Barrier - Irina for Tome of Lothric", "Magic Barrier", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Blessed Weapon - Irina for Tome of Lothric", "Blessed Weapon", + missable=True, npc=True, shop=True), ], "Road of Sacrifices": [ - DS3LocationData("RS: Brigand Twindaggers", "Brigand Twindaggers", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Brigand Hood", "Brigand Hood", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Brigand Armor", "Brigand Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Brigand Gauntlets", "Brigand Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Brigand Trousers", "Brigand Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Butcher Knife", "Butcher Knife", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Brigand Axe", "Brigand Axe", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Braille Divine Tome of Carim", "Braille Divine Tome of Carim", DS3LocationCategory.MISC), - DS3LocationData("RS: Morne's Ring", "Morne's Ring", DS3LocationCategory.RING), - DS3LocationData("RS: Twin Dragon Greatshield", "Twin Dragon Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("RS: Heretic's Staff", "Heretic's Staff", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Sorcerer Hood", "Sorcerer Hood", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sorcerer Robe", "Sorcerer Robe", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sorcerer Gloves", "Sorcerer Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sorcerer Trousers", "Sorcerer Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sage Ring", "Sage Ring", DS3LocationCategory.RING), - DS3LocationData("RS: Fallen Knight Helm", "Fallen Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Fallen Knight Armor", "Fallen Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Fallen Knight Gauntlets", "Fallen Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Fallen Knight Trousers", "Fallen Knight Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Hood", "Conjurator Hood", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Robe", "Conjurator Robe", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Manchettes", "Conjurator Manchettes", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Conjurator Boots", "Conjurator Boots", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Great Swamp Pyromancy Tome", "Great Swamp Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("RS: Great Club", "Great Club", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Exile Greatsword", "Exile Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Farron Coal", "Farron Coal", DS3LocationCategory.MISC), - DS3LocationData("RS: Sellsword Twinblades", "Sellsword Twinblades", DS3LocationCategory.WEAPON), - DS3LocationData("RS: Sellsword Helm", "Sellsword Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sellsword Armor", "Sellsword Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sellsword Gauntlet", "Sellsword Gauntlet", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Sellsword Trousers", "Sellsword Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Golden Falcon Shield", "Golden Falcon Shield", DS3LocationCategory.SHIELD), - DS3LocationData("RS: Herald Helm", "Herald Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Herald Armor", "Herald Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Herald Gloves", "Herald Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Herald Trousers", "Herald Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RS: Grass Crest Shield", "Grass Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("RS: Soul of a Crystal Sage", "Soul of a Crystal Sage", DS3LocationCategory.BOSS), - DS3LocationData("RS: Great Swamp Ring", "Great Swamp Ring", DS3LocationCategory.RING), - DS3LocationData("RS: Orbeck's Ashes", "Orbeck's Ashes", DS3LocationCategory.NPC), + DS3LocationData("RS: Soul of a Crystal Sage", "Soul of a Crystal Sage", prominent=True, + boss=True), + DS3LocationData("RS: Exile Greatsword - NPC drop by Farron Keep", "Exile Greatsword", + hostile_npc=True), # Exile Knight #2 drop + DS3LocationData("RS: Great Club - NPC drop by Farron Keep", "Great Club", + hostile_npc=True), # Exile Knight #1 drop + DS3LocationData("RS: Heysel Pick - Heysel drop", "Heysel Pick", missable=True, + hostile_npc=True), + DS3LocationData("RS: Xanthous Crown - Heysel drop", "Xanthous Crown", missable=True, + hostile_npc=True), + DS3LocationData("RS: Butcher Knife - NPC drop beneath road", "Butcher Knife", + hostile_npc=True), # Madwoman + DS3LocationData("RS: Titanite Shard - water by Halfway Fortress", "Titanite Shard"), + DS3LocationData("RS: Titanite Shard - woods, left of path from Halfway Fortress", + "Titanite Shard"), + DS3LocationData("RS: Green Blossom - by deep water", "Green Blossom x4"), + DS3LocationData("RS: Estus Shard - left of fire behind stronghold left room", + "Estus Shard"), + DS3LocationData("RS: Ring of Sacrifice - stronghold, drop from right room balcony", + "Ring of Sacrifice", hidden=True), # hidden fall + DS3LocationData("RS: Soul of an Unknown Traveler - drop along wall from Halfway Fortress", + "Soul of an Unknown Traveler"), + DS3LocationData("RS: Fallen Knight Helm - water's edge by Farron Keep", + "Fallen Knight Helm"), + DS3LocationData("RS: Fallen Knight Armor - water's edge by Farron Keep", + "Fallen Knight Armor"), + DS3LocationData("RS: Fallen Knight Gauntlets - water's edge by Farron Keep", + "Fallen Knight Gauntlets"), + DS3LocationData("RS: Fallen Knight Trousers - water's edge by Farron Keep", + "Fallen Knight Trousers"), + DS3LocationData("RS: Heretic's Staff - stronghold left room", "Heretic's Staff"), + DS3LocationData("RS: Large Soul of an Unknown Traveler - left of stairs to Farron Keep", + "Large Soul of an Unknown Traveler"), + DS3LocationData("RS: Conjurator Hood - deep water", "Conjurator Hood"), + DS3LocationData("RS: Conjurator Robe - deep water", "Conjurator Robe"), + DS3LocationData("RS: Conjurator Manchettes - deep water", "Conjurator Manchettes"), + DS3LocationData("RS: Conjurator Boots - deep water", "Conjurator Boots"), + DS3LocationData("RS: Soul of an Unknown Traveler - right of door to stronghold left", + "Soul of an Unknown Traveler"), + DS3LocationData("RS: Green Blossom - water beneath stronghold", "Green Blossom x2"), + DS3LocationData("RS: Great Swamp Pyromancy Tome - deep water", + "Great Swamp Pyromancy Tome"), + DS3LocationData("RS: Homeward Bone - balcony by Farron Keep", "Homeward Bone x2"), + DS3LocationData("RS: Titanite Shard - woods, surrounded by enemies", "Titanite Shard"), + DS3LocationData("RS: Twin Dragon Greatshield - woods by Crucifixion Woods bonfire", + "Twin Dragon Greatshield"), + DS3LocationData("RS: Sorcerer Hood - water beneath stronghold", "Sorcerer Hood", + hidden=True), # Hidden fall + DS3LocationData("RS: Sorcerer Robe - water beneath stronghold", "Sorcerer Robe", + hidden=True), # Hidden fall + DS3LocationData("RS: Sorcerer Gloves - water beneath stronghold", "Sorcerer Gloves", + hidden=True), # Hidden fall + DS3LocationData("RS: Sorcerer Trousers - water beneath stronghold", "Sorcerer Trousers", + hidden=True), # Hidden fall + DS3LocationData("RS: Sage Ring - water beneath stronghold", "Sage Ring", + hidden=True), # Hidden fall + DS3LocationData("RS: Grass Crest Shield - water by Crucifixion Woods bonfire", + "Grass Crest Shield"), + DS3LocationData("RS: Ember - right of fire behind stronghold left room", "Ember"), + DS3LocationData("RS: Blue Bug Pellet - broken stairs by Orbeck", "Blue Bug Pellet x2"), + DS3LocationData("RS: Soul of an Unknown Traveler - road, by wagon", + "Soul of an Unknown Traveler"), + DS3LocationData("RS: Shriving Stone - road, by start", "Shriving Stone"), + DS3LocationData("RS: Titanite Shard - road, on bridge after you go under", + "Titanite Shard"), + DS3LocationData("RS: Brigand Twindaggers - beneath road", "Brigand Twindaggers"), + DS3LocationData("RS: Braille Divine Tome of Carim - drop from bridge to Halfway Fortress", + "Braille Divine Tome of Carim", hidden=True), # Hidden fall + DS3LocationData("RS: Ember - right of Halfway Fortress entrance", "Ember"), + DS3LocationData("RS: Sellsword Twinblades - keep perimeter", "Sellsword Twinblades"), + DS3LocationData("RS: Golden Falcon Shield - path from stronghold right room to Farron Keep", + "Golden Falcon Shield"), + DS3LocationData("RS: Brigand Axe - beneath road", "Brigand Axe"), + DS3LocationData("RS: Brigand Hood - beneath road", "Brigand Hood"), + DS3LocationData("RS: Brigand Armor - beneath road", "Brigand Armor"), + DS3LocationData("RS: Brigand Gauntlets - beneath road", "Brigand Gauntlets"), + DS3LocationData("RS: Brigand Trousers - beneath road", "Brigand Trousers"), + DS3LocationData("RS: Morne's Ring - drop from bridge to Halfway Fortress", "Morne's Ring", + hidden=True), # Hidden fall + DS3LocationData("RS: Sellsword Helm - keep perimeter balcony", "Sellsword Helm"), + DS3LocationData("RS: Sellsword Armor - keep perimeter balcony", "Sellsword Armor"), + DS3LocationData("RS: Sellsword Gauntlet - keep perimeter balcony", "Sellsword Gauntlet"), + DS3LocationData("RS: Sellsword Trousers - keep perimeter balcony", "Sellsword Trousers"), + DS3LocationData("RS: Farron Coal - keep perimeter", "Farron Coal"), + DS3LocationData("RS: Chloranthy Ring+2 - road, drop across from carriage", + "Chloranthy Ring+2", hidden=True, ngp=True), # Hidden fall + DS3LocationData("RS: Lingering Dragoncrest Ring+1 - water", "Lingering Dragoncrest Ring+1", + ngp=True), + DS3LocationData("RS: Great Swamp Ring - miniboss drop, by Farron Keep", + "Great Swamp Ring", miniboss=True), # Giant Crab drop + DS3LocationData("RS: Blue Sentinels - Horace", "Blue Sentinels", + missable=True, npc=True), # Horace quest + DS3LocationData("RS: Crystal Gem - stronghold, lizard", "Crystal Gem"), + DS3LocationData("RS: Fading Soul - woods by Crucifixion Woods bonfire", "Fading Soul", + static='03,0:53300210::'), + + # Orbeck shop, all missable because he'll disappear if you don't talk to him for too long or + # if you don't give him a scroll. + DS3LocationData("FS: Farron Dart - Orbeck", "Farron Dart", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Soul Arrow - Orbeck", "Soul Arrow", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Great Soul Arrow - Orbeck", "Great Soul Arrow", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Heavy Soul Arrow - Orbeck", "Heavy Soul Arrow", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Great Heavy Soul Arrow - Orbeck", "Great Heavy Soul Arrow", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Magic Weapon - Orbeck", "Magic Weapon", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Magic Shield - Orbeck", "Magic Shield", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True, + shop=True), + DS3LocationData("FS: Spook - Orbeck", "Spook", missable=True, npc=True, shop=True), + DS3LocationData("FS: Aural Decoy - Orbeck", "Aural Decoy", missable=True, npc=True, + shop=True), + DS3LocationData("FS: Soul Greatsword - Orbeck", "Soul Greatsword", + static='99,0:-1:110000,130100,70000111:', missable=True, npc=True), + DS3LocationData("FS: Farron Flashsword - Orbeck", "Farron Flashsword", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Pestilent Mist - Orbeck for any scroll", "Pestilent Mist", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Great Farron Dart - Orbeck for Sage's Scroll", "Great Farron Dart", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Farron Hail - Orbeck for Sage's Scroll", "Farron Hail", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Homing Soulmass - Orbeck for Logan's Scroll", "Homing Soulmass", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Soul Spear - Orbeck for Logan's Scroll", "Soul Spear", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Homing Crystal Soulmass - Orbeck for Crystal Scroll", + "Homing Crystal Soulmass", missable=True, npc=True, shop=True), + DS3LocationData("FS: Crystal Soul Spear - Orbeck for Crystal Scroll", "Crystal Soul Spear", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Crystal Magic Weapon - Orbeck for Crystal Scroll", + "Crystal Magic Weapon", missable=True, npc=True, shop=True), + DS3LocationData("FS: Cast Light - Orbeck for Golden Scroll", "Cast Light", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Twisted Wall of Light - Orbeck for Golden Scroll", + "Twisted Wall of Light", missable=True, npc=True, shop=True), + DS3LocationData("FS: Hidden Weapon - Orbeck for Golden Scroll", "Hidden Weapon", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Hidden Body - Orbeck for Golden Scroll", "Hidden Body", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Repair - Orbeck for Golden Scroll", "Repair", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Clandestine Coat - shop with Orbeck's Ashes", "Clandestine Coat", + missable=True, npc=True, + shop=True), # Shrine Handmaid with Orbeck's Ashes + reload + DS3LocationData("FS: Young Dragon Ring - Orbeck for one scroll and buying three spells", + "Young Dragon Ring", missable=True, npc=True), + DS3LocationData("FS: Slumbering Dragoncrest Ring - Orbeck for buying four specific spells", + "Slumbering Dragoncrest Ring", missable=True, npc=True), + DS3LocationData("RS -> FK", None), + + # Shrine Handmaid after killing exiles + DS3LocationData("FS: Exile Mask - shop after killing NPCs in RS", "Exile Mask", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Exile Armor - shop after killing NPCs in RS", "Exile Armor", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Exile Gauntlets - shop after killing NPCs in RS", "Exile Gauntlets", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Exile Leggings - shop after killing NPCs in RS", "Exile Leggings", + hostile_npc=True, shop=True, hidden=True), + + # Shrine Handmaid after killing Crystal Sage + DS3LocationData("FS: Sage's Big Hat - shop after killing RS boss", "Sage's Big Hat", + boss=True, shop=True), + + # Yuria of Londor for Orbeck's Ashes + DS3LocationData("FS: Morion Blade - Yuria for Orbeck's Ashes", "Morion Blade", + missable=True, npc=True), ], "Cathedral of the Deep": [ - DS3LocationData("CD: Paladin's Ashes", "Paladin's Ashes", DS3LocationCategory.MISC), - DS3LocationData("CD: Spider Shield", "Spider Shield", DS3LocationCategory.SHIELD), - DS3LocationData("CD: Crest Shield", "Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("CD: Notched Whip", "Notched Whip", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Astora Greatsword", "Astora Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Executioner's Greatsword", "Executioner's Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Curse Ward Greatshield", "Curse Ward Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("CD: Saint-tree Bellvine", "Saint-tree Bellvine", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Poisonbite Ring", "Poisonbite Ring", DS3LocationCategory.RING), - DS3LocationData("CD: Lloyd's Sword Ring", "Lloyd's Sword Ring", DS3LocationCategory.RING), - DS3LocationData("CD: Seek Guidance", "Seek Guidance", DS3LocationCategory.SPELL), - DS3LocationData("CD: Aldrich's Sapphire", "Aldrich's Sapphire", DS3LocationCategory.RING), - DS3LocationData("CD: Deep Braille Divine Tome", "Deep Braille Divine Tome", DS3LocationCategory.MISC), - DS3LocationData("CD: Saint Bident", "Saint Bident", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Maiden Hood", "Maiden Hood", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Maiden Robe", "Maiden Robe", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Maiden Gloves", "Maiden Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Maiden Skirt", "Maiden Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Armor", "Drang Armor", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Gauntlets", "Drang Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Shoes", "Drang Shoes", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Drang Hammers", "Drang Hammers", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Deep Ring", "Deep Ring", DS3LocationCategory.RING), - DS3LocationData("CD: Archdeacon White Crown", "Archdeacon White Crown", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Archdeacon Holy Garb", "Archdeacon Holy Garb", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Archdeacon Skirt", "Archdeacon Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("CD: Arbalest", "Arbalest", DS3LocationCategory.WEAPON), - DS3LocationData("CD: Small Doll", "Small Doll", DS3LocationCategory.KEY), - DS3LocationData("CD: Soul of the Deacons of the Deep", "Soul of the Deacons of the Deep", DS3LocationCategory.BOSS), - DS3LocationData("CD: Rosaria's Fingers", "Rosaria's Fingers", DS3LocationCategory.MISC) + DS3LocationData("CD: Herald Helm - path, by fire", "Herald Helm"), + DS3LocationData("CD: Herald Armor - path, by fire", "Herald Armor"), + DS3LocationData("CD: Herald Gloves - path, by fire", "Herald Gloves"), + DS3LocationData("CD: Herald Trousers - path, by fire", "Herald Trousers"), + DS3LocationData("CD: Twinkling Titanite - path, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Twinkling Titanite - path, lizard #2", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Small Doll - boss drop", "Small Doll", prominent=True, + progression=True, boss=True), + DS3LocationData("CD: Soul of the Deacons of the Deep", "Soul of the Deacons of the Deep", + boss=True), + DS3LocationData("CD: Black Eye Orb - Rosaria from Leonhard's quest", "Black Eye Orb", + missable=True, npc=True), + DS3LocationData("CD: Winged Spear - kill Patches", "Winged Spear", drop=True, + missable=True), # Patches (kill) + DS3LocationData("CD: Spider Shield - NPC drop on path", "Spider Shield", + hostile_npc=True), # Brigand + DS3LocationData("CD: Notched Whip - Cleansing Chapel", "Notched Whip"), + DS3LocationData("CD: Titanite Shard - Cleansing Chapel windowsill, by miniboss", + "Titanite Shard"), + DS3LocationData("CD: Astora Greatsword - graveyard, left of entrance", "Astora Greatsword"), + DS3LocationData("CD: Executioner's Greatsword - graveyard, far end", + "Executioner's Greatsword"), + DS3LocationData("CD: Undead Bone Shard - gravestone by white tree", "Undead Bone Shard"), + DS3LocationData("CD: Curse Ward Greatshield - by ladder from white tree to moat", + "Curse Ward Greatshield"), + DS3LocationData("CD: Titanite Shard - moat, far end", "Titanite Shard"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - lower roofs, semicircle balcony", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Paladin's Ashes - path, guarded by lower NPC", "Paladin's Ashes", + progression=True), + DS3LocationData("CD: Arbalest - upper roofs, end of furthest buttress", "Arbalest"), + DS3LocationData("CD: Ember - by back door", "Ember"), + DS3LocationData("CD: Ember - side chapel upstairs, up ladder", "Ember"), + DS3LocationData("CD: Poisonbite Ring - moat, hall past miniboss", "Poisonbite Ring"), + DS3LocationData("CD: Drang Armor - main hall, east", "Drang Armor"), + DS3LocationData("CD: Ember - edge of platform before boss", "Ember"), + DS3LocationData("CD: Duel Charm - next to Patches in onion armor", "Duel Charm x3"), + DS3LocationData("CD: Seek Guidance - side chapel upstairs", "Seek Guidance"), + DS3LocationData("CD: Estus Shard - monument outside Cleansing Chapel", "Estus Shard"), + DS3LocationData("CD: Maiden Hood - main hall south", "Maiden Hood"), + DS3LocationData("CD: Maiden Robe - main hall south", "Maiden Robe"), + DS3LocationData("CD: Maiden Gloves - main hall south", "Maiden Gloves"), + DS3LocationData("CD: Maiden Skirt - main hall south", "Maiden Skirt"), + DS3LocationData("CD: Pale Tongue - upper roofs, outdoors far end", "Pale Tongue"), + DS3LocationData("CD: Fading Soul - graveyard, far end", "Fading Soul"), + DS3LocationData("CD: Blessed Gem - upper roofs, rafters", "Blessed Gem"), + DS3LocationData("CD: Red Bug Pellet - right of cathedral front doors", "Red Bug Pellet"), + DS3LocationData("CD: Soul of a Nameless Soldier - main hall south", + "Soul of a Nameless Soldier"), + DS3LocationData("CD: Duel Charm - by first elevator", "Duel Charm"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - main hall south, side path", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Ember - side chapel, miniboss room", "Ember"), + DS3LocationData("CD: Repair Powder - by white tree", "Repair Powder x3"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - by white tree #1", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - by white tree #2", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Undead Hunter Charm - lower roofs, up stairs between buttresses", + "Undead Hunter Charm x3"), + DS3LocationData("CD: Red Bug Pellet - lower roofs, up stairs between buttresses", + "Red Bug Pellet x3"), + DS3LocationData("CD: Titanite Shard - outside building by white tree", "Titanite Shard", + hidden=True), # Easily missable side path + DS3LocationData("CD: Titanite Shard - moat, up a slope", "Titanite Shard"), + DS3LocationData("CD: Rusted Coin - left of cathedral front doors, behind crates", + "Rusted Coin x2", hidden=True), + DS3LocationData("CD: Drang Hammers - main hall east", "Drang Hammers"), + DS3LocationData("CD: Drang Shoes - main hall east", "Drang Shoes"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - main hall east", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CD: Pale Tongue - main hall east", "Pale Tongue"), + DS3LocationData("CD: Drang Gauntlets - main hall east", "Drang Gauntlets"), + DS3LocationData("CD: Soul of a Nameless Soldier - lower roofs, side room", + "Soul of a Nameless Soldier"), + DS3LocationData("CD: Exploding Bolt - ledge above main hall south", "Exploding Bolt x6"), + DS3LocationData("CD: Lloyd's Sword Ring - ledge above main hall south", + "Lloyd's Sword Ring"), + DS3LocationData("CD: Soul of a Nameless Soldier - ledge above main hall south", + "Soul of a Nameless Soldier"), + DS3LocationData("CD: Homeward Bone - outside main hall south door", "Homeward Bone x2"), + DS3LocationData("CD: Deep Gem - down stairs by first elevator", "Deep Gem"), + DS3LocationData("CD: Titanite Shard - path, side path by Cathedral of the Deep bonfire", + "Titanite Shard"), + DS3LocationData("CD: Large Soul of an Unknown Traveler - path, against outer wall", + "Large Soul of an Unknown Traveler"), + # Before the stairs leading down into the Deacons fight + DS3LocationData("CD: Ring of the Evil Eye+1 - by stairs to boss", "Ring of the Evil Eye+1", + ngp=True), + DS3LocationData("CD: Ring of Favor+2 - upper roofs, on buttress", "Ring of Favor+2", + hidden=True, ngp=True), # Hidden fall + DS3LocationData("CD: Crest Shield - path, drop down by Cathedral of the Deep bonfire", + "Crest Shield", hidden=True), # Hidden fall + DS3LocationData("CD: Young White Branch - by white tree #1", "Young White Branch"), + DS3LocationData("CD: Young White Branch - by white tree #2", "Young White Branch"), + DS3LocationData("CD: Saint-tree Bellvine - moat, by water", "Saint-tree Bellvine"), + DS3LocationData("CD: Saint Bident - outside main hall south door", "Saint Bident"), + # Archdeacon set is hidden because you have to return to a cleared area + DS3LocationData("CD: Archdeacon White Crown - boss room after killing boss", + "Archdeacon White Crown", boss=True, hidden=True), + DS3LocationData("CD: Archdeacon Holy Garb - boss room after killing boss", + "Archdeacon Holy Garb", boss=True, hidden=True), + DS3LocationData("CD: Archdeacon Skirt - boss room after killing boss", "Archdeacon Skirt", + boss=True, hidden=True), + # Heysel items may not be missable, but it's not clear what causes them to trigger + DS3LocationData("CD: Heysel Pick - Heysel Corpse-Grub in Rosaria's Bed Chamber", + "Heysel Pick", missable=True), + DS3LocationData("CD: Xanthous Crown - Heysel Corpse-Grub in Rosaria's Bed Chamber", + "Xanthous Crown", missable=True), + DS3LocationData("CD: Deep Ring - upper roofs, passive mob drop in first tower", "Deep Ring", + drop=True, hidden=True), + DS3LocationData("CD: Deep Braille Divine Tome - mimic by side chapel", + "Deep Braille Divine Tome", mimic=True), + DS3LocationData("CD: Red Sign Soapstone - passive mob drop by Rosaria's Bed Chamber", + "Red Sign Soapstone", drop=True, hidden=True), + DS3LocationData("CD: Aldrich's Sapphire - side chapel, miniboss drop", "Aldrich's Sapphire", + miniboss=True), # Deep Accursed Drop + DS3LocationData("CD: Titanite Scale - moat, miniboss drop", "Titanite Scale", + miniboss=True), # Ravenous Crystal Lizard drop + DS3LocationData("CD: Twinkling Titanite - moat, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Twinkling Titanite - moat, lizard #2", "Twinkling Titanite", + lizard=True), + DS3LocationData("CD: Rosaria's Fingers - Rosaria", "Rosaria's Fingers", + hidden=True), # Hidden fall + DS3LocationData("CD -> PW1", None), + + # Longfinger Kirk drops + DS3LocationData("CD: Barbed Straight Sword - Kirk drop", "Barbed Straight Sword", + missable=True, hostile_npc=True), + DS3LocationData("CD: Spiked Shield - Kirk drop", "Spiked Shield", missable=True, + hostile_npc=True), + # In Rosaria's Bed Chamber + DS3LocationData("CD: Helm of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Helm of Thorns", missable=True, hostile_npc=True), + DS3LocationData("CD: Armor of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Armor of Thorns", missable=True, hostile_npc=True), + DS3LocationData("CD: Gauntlets of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Gauntlets of Thorns", missable=True, hostile_npc=True), + DS3LocationData("CD: Leggings of Thorns - Rosaria's Bed Chamber after killing Kirk", + "Leggings of Thorns", missable=True, hostile_npc=True), + + # Unbreakable Patches + DS3LocationData("CD: Rusted Coin - don't forgive Patches", "Rusted Coin", + missable=True, npc=True), + DS3LocationData("FS: Rusted Gold Coin - don't forgive Patches", "Rusted Gold Coin", + static='99,0:50006201::', missable=True, + npc=True), # Don't forgive Patches + DS3LocationData("CD: Shotel - Patches", "Shotel", missable=True, npc=True, shop=True), + DS3LocationData("CD: Ember - Patches", "Ember", missable=True, npc=True, shop=True), + DS3LocationData("CD: Horsehoof Ring - Patches", "Horsehoof Ring", missable=True, + npc=True, drop=True, shop=True), # (kill or buy) ], "Farron Keep": [ - DS3LocationData("FK: Ragged Mask", "Ragged Mask", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Iron Flesh", "Iron Flesh", DS3LocationCategory.SPELL), - DS3LocationData("FK: Golden Scroll", "Golden Scroll", DS3LocationCategory.MISC), - DS3LocationData("FK: Antiquated Dress", "Antiquated Dress", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Antiquated Gloves", "Antiquated Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Antiquated Skirt", "Antiquated Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Helm", "Nameless Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Armor", "Nameless Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Gauntlets", "Nameless Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Nameless Knight Leggings", "Nameless Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Sunlight Talisman", "Sunlight Talisman", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Wolf's Blood Swordgrass", "Wolf's Blood Swordgrass", DS3LocationCategory.MISC), - DS3LocationData("FK: Greatsword", "Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Sage's Coal", "Sage's Coal", DS3LocationCategory.MISC), - DS3LocationData("FK: Stone Parma", "Stone Parma", DS3LocationCategory.SHIELD), - DS3LocationData("FK: Sage's Scroll", "Sage's Scroll", DS3LocationCategory.MISC), - DS3LocationData("FK: Crown of Dusk", "Crown of Dusk", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Lingering Dragoncrest Ring", "Lingering Dragoncrest Ring", DS3LocationCategory.RING), - DS3LocationData("FK: Pharis's Hat", "Pharis's Hat", DS3LocationCategory.ARMOR), - DS3LocationData("FK: Black Bow of Pharis", "Black Bow of Pharis", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Dreamchaser's Ashes", "Dreamchaser's Ashes", DS3LocationCategory.MISC), - DS3LocationData("FK: Great Axe", "Great Axe", DS3LocationCategory.WEAPON), - DS3LocationData("FK: Dragon Crest Shield", "Dragon Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("FK: Lightning Spear", "Lightning Spear", DS3LocationCategory.SPELL), - DS3LocationData("FK: Atonement", "Atonement", DS3LocationCategory.SPELL), - DS3LocationData("FK: Great Magic Weapon", "Great Magic Weapon", DS3LocationCategory.SPELL), - DS3LocationData("FK: Cinders of a Lord - Abyss Watcher", "Cinders of a Lord - Abyss Watcher", DS3LocationCategory.KEY), - DS3LocationData("FK: Soul of the Blood of the Wolf", "Soul of the Blood of the Wolf", DS3LocationCategory.BOSS), - DS3LocationData("FK: Soul of a Stray Demon", "Soul of a Stray Demon", DS3LocationCategory.BOSS), - DS3LocationData("FK: Watchdogs of Farron", "Watchdogs of Farron", DS3LocationCategory.MISC), + DS3LocationData("FK: Lightning Spear - upper keep, far side of the wall", + "Lightning Spear"), + DS3LocationData("FK: Dragon Crest Shield - upper keep, far side of the wall", + "Dragon Crest Shield"), + DS3LocationData("FK: Soul of the Blood of the Wolf", "Soul of the Blood of the Wolf", + boss=True), + DS3LocationData("FK: Cinders of a Lord - Abyss Watcher", + "Cinders of a Lord - Abyss Watcher", + static="03,0:50002100::", prominent=True, progression=True, + boss=True), + DS3LocationData("FK: Manikin Claws - Londor Pale Shade drop", "Manikin Claws", + missable=True, hostile_npc=True, + npc=True), # Londor Pale Shade (if Yoel/Yuria hostile) + DS3LocationData("FK: Purple Moss Clump - keep ruins, ritual island", + "Purple Moss Clump x2"), + DS3LocationData("FK: Purple Moss Clump - ramp directly in front of Farron Keep bonfire", + "Purple Moss Clump x4"), + DS3LocationData("FK: Greatsword - ramp by keep ruins ritual island", "Greatsword"), + DS3LocationData("FK: Hollow Gem - perimeter, drop down into swamp", "Hollow Gem", + hidden=True), + DS3LocationData("FK: Purple Moss Clump - Farron Keep bonfire, around right corner", + "Purple Moss Clump x3"), + DS3LocationData("FK: Undead Bone Shard - pavilion by keep ruins bonfire island", + "Undead Bone Shard"), + DS3LocationData("FK: Atonement - perimeter, drop down into swamp", "Atonement", + hidden=True), + DS3LocationData("FK: Titanite Shard - by ladder to keep proper", "Titanite Shard"), + DS3LocationData("FK: Iron Flesh - Farron Keep bonfire, right after exit", "Iron Flesh"), + DS3LocationData("FK: Stone Parma - near wall by left island", "Stone Parma"), + DS3LocationData("FK: Rotten Pine Resin - left island, behind fire", "Rotten Pine Resin x2"), + DS3LocationData("FK: Titanite Shard - between left island and keep ruins", "Titanite Shard"), + DS3LocationData("FK: Rusted Gold Coin - right island, behind wall", "Rusted Gold Coin", + hidden=True), + DS3LocationData("FK: Nameless Knight Helm - corner of keep and right island", + "Nameless Knight Helm"), + DS3LocationData("FK: Nameless Knight Armor - corner of keep and right island", + "Nameless Knight Armor"), + DS3LocationData("FK: Nameless Knight Gauntlets - corner of keep and right island", + "Nameless Knight Gauntlets"), + DS3LocationData("FK: Nameless Knight Leggings - corner of keep and right island", + "Nameless Knight Leggings"), + DS3LocationData("FK: Shriving Stone - perimeter, just past stone doors", "Shriving Stone"), + DS3LocationData("FK: Repair Powder - outside hidden cave", "Repair Powder x4", + hidden=True), + DS3LocationData("FK: Golden Scroll - hidden cave", "Golden Scroll", hidden=True), + DS3LocationData("FK: Sage's Scroll - near wall by keep ruins bonfire island", + "Sage's Scroll"), + DS3LocationData("FK: Dreamchaser's Ashes - keep proper, illusory wall", + "Dreamchaser's Ashes", progression=True, hidden=True), + DS3LocationData("FK: Titanite Shard - keep ruins bonfire island, under ramp", + "Titanite Shard"), + DS3LocationData("FK: Wolf's Blood Swordgrass - by ladder to keep proper", + "Wolf's Blood Swordgrass"), + DS3LocationData("FK: Great Magic Weapon - perimeter, by door to Road of Sacrifices", + "Great Magic Weapon"), + DS3LocationData("FK: Ember - perimeter, path to boss", "Ember"), + DS3LocationData("FK: Titanite Shard - swamp by right island", "Titanite Shard x2"), + DS3LocationData("FK: Titanite Shard - by left island stairs", "Titanite Shard"), + DS3LocationData("FK: Titanite Shard - by keep ruins ritual island stairs", "Titanite Shard"), + DS3LocationData("FK: Black Bug Pellet - perimeter, hill by boss door", + "Black Bug Pellet x3"), + DS3LocationData("FK: Rotten Pine Resin - outside pavilion by left island", + "Rotten Pine Resin x4"), + DS3LocationData("FK: Poison Gem - near wall by keep ruins bridge", "Poison Gem"), + DS3LocationData("FK: Ragged Mask - Farron Keep bonfire, around left corner", "Ragged Mask"), + DS3LocationData("FK: Estus Shard - between Farron Keep bonfire and left island", + "Estus Shard"), + DS3LocationData("FK: Homeward Bone - right island, behind fire", "Homeward Bone x2"), + DS3LocationData("FK: Titanite Shard - Farron Keep bonfire, left after exit", + "Titanite Shard"), + DS3LocationData("FK: Large Soul of a Nameless Soldier - corner of keep and right island", + "Large Soul of a Nameless Soldier", hidden=True), # Tricky corner to spot + DS3LocationData("FK: Prism Stone - by left island stairs", "Prism Stone x10"), + DS3LocationData("FK: Large Soul of a Nameless Soldier - near wall by right island", + "Large Soul of a Nameless Soldier"), + DS3LocationData("FK: Sage's Coal - pavilion by left island", "Sage's Coal"), + DS3LocationData("FK: Gold Pine Bundle - by white tree", "Gold Pine Bundle x6"), + DS3LocationData("FK: Ember - by white tree", "Ember"), + DS3LocationData("FK: Soul of a Nameless Soldier - by white tree", "Soul of a Nameless Soldier"), + DS3LocationData("FK: Large Soul of an Unknown Traveler - by white tree", + "Large Soul of an Unknown Traveler"), + DS3LocationData("FK: Greataxe - upper keep, by miniboss", "Greataxe"), + DS3LocationData("FK: Ember - upper keep, by miniboss #1", "Ember"), + DS3LocationData("FK: Ember - upper keep, by miniboss #2", "Ember"), + DS3LocationData("FK: Dark Stoneplate Ring+2 - keep ruins ritual island, behind wall", + "Dark Stoneplate Ring+2", ngp=True, hidden=True), + DS3LocationData("FK: Magic Stoneplate Ring+1 - between right island and wall", + "Magic Stoneplate Ring+1", ngp=True), + DS3LocationData("FK: Wolf Ring+1 - keep ruins bonfire island, outside building", + "Wolf Ring+1", ngp=True), + DS3LocationData("FK: Antiquated Dress - hidden cave", "Antiquated Dress", hidden=True), + DS3LocationData("FK: Antiquated Gloves - hidden cave", "Antiquated Gloves", hidden=True), + DS3LocationData("FK: Antiquated Skirt - hidden cave", "Antiquated Skirt", hidden=True), + DS3LocationData("FK: Sunlight Talisman - estus soup island, by ladder to keep proper", + "Sunlight Talisman"), + DS3LocationData("FK: Young White Branch - by white tree #1", "Young White Branch"), + DS3LocationData("FK: Young White Branch - by white tree #2", "Young White Branch"), + DS3LocationData("FK: Crown of Dusk - by white tree", "Crown of Dusk"), + DS3LocationData("FK: Lingering Dragoncrest Ring - by white tree, miniboss drop", + "Lingering Dragoncrest Ring", miniboss=True), # Great Crab drop + DS3LocationData("FK: Pharis's Hat - miniboss drop, by keep ruins near wall", + "Pharis's Hat", miniboss=True), # Elder Ghru drop + DS3LocationData("FK: Black Bow of Pharis - miniboss drop, by keep ruins near wall", + "Black Bow of Pharis", miniboss=True), # Elder Ghru drop + DS3LocationData("FK: Titanite Scale - perimeter, miniboss drop", "Titanite Scale x2", + miniboss=True), # Ravenous Crystal Lizard drop + DS3LocationData("FK: Large Titanite Shard - upper keep, lizard in open", + "Large Titanite Shard", lizard=True), + DS3LocationData("FK: Large Titanite Shard - upper keep, lizard by wyvern", + "Large Titanite Shard", lizard=True), + DS3LocationData("FK: Heavy Gem - upper keep, lizard on stairs", "Heavy Gem", lizard=True), + DS3LocationData("FK: Twinkling Titanite - keep proper, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("FK: Soul of a Stray Demon - upper keep, miniboss drop", + "Soul of a Stray Demon", miniboss=True), + DS3LocationData("FK: Watchdogs of Farron - Old Wolf", "Watchdogs of Farron"), + DS3LocationData("FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + "Hawkwood's Shield", missable=True, + npc=True), # Hawkwood (quest, after Greatwood, Sage, Watchers, and Deacons) + DS3LocationData("US: Hawk Ring - Giant Archer", "Hawk Ring", drop=True, + npc=True), # Giant archer (kill or quest), here because you need to + # collect all seven White Branch locations to get it peacefully + + # Hawkwood after killing Abyss Watchers + DS3LocationData("FS: Farron Ring - Hawkwood", "Farron Ring", + missable=True, npc=True), + + # Shrine Handmaid after killing Abyss Watchers + DS3LocationData("FS: Undead Legion Helm - shop after killing FK boss", "Undead Legion Helm", + boss=True, shop=True), + DS3LocationData("FS: Undead Legion Armor - shop after killing FK boss", + "Undead Legion Armor", boss=True, shop=True), + DS3LocationData("FS: Undead Legion Gauntlet - shop after killing FK boss", + "Undead Legion Gauntlet", boss=True, shop=True), + DS3LocationData("FS: Undead Legion Leggings - shop after killing FK boss", + "Undead Legion Leggings", boss=True, shop=True), + + # Appears after killing Havel Knight in Archdragon Peak + DS3LocationData("FK: Havel's Helm - upper keep, after killing AP belfry roof NPC", + "Havel's Helm", hidden=True, hostile_npc=True), + DS3LocationData("FK: Havel's Armor - upper keep, after killing AP belfry roof NPC", + "Havel's Armor", hidden=True, hostile_npc=True), + DS3LocationData("FK: Havel's Gauntlets - upper keep, after killing AP belfry roof NPC", + "Havel's Gauntlets", hidden=True, hostile_npc=True), + DS3LocationData("FK: Havel's Leggings - upper keep, after killing AP belfry roof NPC", + "Havel's Leggings", hidden=True, hostile_npc=True), ], "Catacombs of Carthus": [ - DS3LocationData("CC: Carthus Pyromancy Tome", "Carthus Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("CC: Carthus Milkring", "Carthus Milkring", DS3LocationCategory.RING), - DS3LocationData("CC: Grave Warden's Ashes", "Grave Warden's Ashes", DS3LocationCategory.MISC), - DS3LocationData("CC: Carthus Bloodring", "Carthus Bloodring", DS3LocationCategory.RING), - DS3LocationData("CC: Grave Warden Pyromancy Tome", "Grave Warden Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("CC: Old Sage's Blindfold", "Old Sage's Blindfold", DS3LocationCategory.ARMOR), - DS3LocationData("CC: Witch's Ring", "Witch's Ring", DS3LocationCategory.RING), - DS3LocationData("CC: Black Blade", "Black Blade", DS3LocationCategory.WEAPON), - DS3LocationData("CC: Soul of High Lord Wolnir", "Soul of High Lord Wolnir", DS3LocationCategory.BOSS), - DS3LocationData("CC: Soul of a Demon", "Soul of a Demon", DS3LocationCategory.BOSS), + DS3LocationData("CC: Soul of High Lord Wolnir", "Soul of High Lord Wolnir", + prominent=True, boss=True), + DS3LocationData("CC: Carthus Rouge - atrium upper, left after entrance", + "Carthus Rouge x2"), + DS3LocationData("CC: Sharp Gem - atrium lower, right before exit", "Sharp Gem"), + DS3LocationData("CC: Soul of a Nameless Soldier - atrium lower, down hall", + "Soul of a Nameless Soldier"), + DS3LocationData("CC: Titanite Shard - atrium lower, corner by stairs", "Titanite Shard x2"), + DS3LocationData("CC: Bloodred Moss Clump - atrium lower, down more stairs", + "Bloodred Moss Clump x3"), + DS3LocationData("CC: Carthus Milkring - crypt upper, among pots", "Carthus Milkring"), + DS3LocationData("CC: Ember - atrium, on long stairway", "Ember"), + DS3LocationData("CC: Carthus Rouge - crypt across, corner", "Carthus Rouge x3"), + DS3LocationData("CC: Ember - crypt upper, end of hall past hole", "Ember"), + DS3LocationData("CC: Carthus Bloodring - crypt lower, end of side hall", "Carthus Bloodring"), + DS3LocationData("CC: Titanite Shard - crypt lower, left of entrance", "Titanite Shard x2"), + DS3LocationData("CC: Titanite Shard - crypt lower, start of side hall", "Titanite Shard x2"), + DS3LocationData("CC: Ember - crypt lower, shortcut to cavern", "Ember"), + DS3LocationData("CC: Carthus Pyromancy Tome - atrium lower, jump from bridge", + "Carthus Pyromancy Tome", + hidden=True), # Behind illusory wall or hidden drop + DS3LocationData("CC: Large Titanite Shard - crypt upper, skeleton ball hall", + "Large Titanite Shard"), + DS3LocationData("CC: Large Titanite Shard - crypt across, middle hall", + "Large Titanite Shard"), + DS3LocationData("CC: Yellow Bug Pellet - cavern, on overlook", "Yellow Bug Pellet x3"), + DS3LocationData("CC: Large Soul of a Nameless Soldier - cavern, before bridge", + "Large Soul of a Nameless Soldier"), + DS3LocationData("CC: Black Bug Pellet - cavern, before bridge", "Black Bug Pellet x2"), + DS3LocationData("CC: Grave Warden's Ashes - crypt across, corner", "Grave Warden's Ashes", + progression=True), + DS3LocationData("CC: Large Titanite Shard - tomb lower", "Large Titanite Shard"), + DS3LocationData("CC: Large Soul of a Nameless Soldier - tomb lower", + "Large Soul of a Nameless Soldier"), + DS3LocationData("CC: Old Sage's Blindfold - tomb, hall before bonfire", + "Old Sage's Blindfold"), + DS3LocationData("CC: Witch's Ring - tomb, hall before bonfire", "Witch's Ring"), + DS3LocationData("CC: Soul of a Nameless Soldier - atrium upper, up more stairs", + "Soul of a Nameless Soldier"), + DS3LocationData("CC: Grave Warden Pyromancy Tome - boss arena", + "Grave Warden Pyromancy Tome"), + DS3LocationData("CC: Large Soul of an Unknown Traveler - crypt upper, hall middle", + "Large Soul of an Unknown Traveler"), + DS3LocationData("CC: Ring of Steel Protection+2 - atrium upper, drop onto pillar", + "Ring of Steel Protection+2", ngp=True), + DS3LocationData("CC: Thunder Stoneplate Ring+1 - crypt upper, among pots", + "Thunder Stoneplate Ring+1", ngp=True), + DS3LocationData("CC: Undead Bone Shard - crypt upper, skeleton ball drop", + "Undead Bone Shard", hidden=True), # Skeleton Ball puzzle + DS3LocationData("CC: Dark Gem - crypt lower, skeleton ball drop", "Dark Gem", + hidden=True), # Skeleton Ball puzzle + DS3LocationData("CC: Black Blade - tomb, mimic", "Black Blade", mimic=True), + DS3LocationData("CC: Soul of a Demon - tomb, miniboss drop", "Soul of a Demon", + miniboss=True), + DS3LocationData("CC: Twinkling Titanite - atrium lower, lizard down more stairs", + "Twinkling Titanite", lizard=True), + DS3LocationData("CC: Fire Gem - cavern, lizard", "Fire Gem", lizard=True), + DS3LocationData("CC: Homeward Bone - Irithyll bridge", "Homeward Bone"), + DS3LocationData("CC: Pontiff's Right Eye - Irithyll bridge, miniboss drop", + "Pontiff's Right Eye", miniboss=True), # Sulyvahn's Beast drop + + # Shrine Handmaid after killing High Lord Wolnir + DS3LocationData("FS: Wolnir's Crown - shop after killing CC boss", "Wolnir's Crown", + boss=True, shop=True), ], "Smouldering Lake": [ - DS3LocationData("SL: Shield of Want", "Shield of Want", DS3LocationCategory.SHIELD), - DS3LocationData("SL: Speckled Stoneplate Ring", "Speckled Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("SL: Dragonrider Bow", "Dragonrider Bow", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Lightning Stake", "Lightning Stake", DS3LocationCategory.SPELL), - DS3LocationData("SL: Izalith Pyromancy Tome", "Izalith Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("SL: Black Knight Sword", "Black Knight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Quelana Pyromancy Tome", "Quelana Pyromancy Tome", DS3LocationCategory.MISC), - DS3LocationData("SL: Toxic Mist", "Toxic Mist", DS3LocationCategory.SPELL), - DS3LocationData("SL: White Hair Talisman", "White Hair Talisman", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Izalith Staff", "Izalith Staff", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Sacred Flame", "Sacred Flame", DS3LocationCategory.SPELL), - DS3LocationData("SL: Fume Ultra Greatsword", "Fume Ultra Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("SL: Black Iron Greatshield", "Black Iron Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("SL: Soul of the Old Demon King", "Soul of the Old Demon King", DS3LocationCategory.BOSS), - DS3LocationData("SL: Knight Slayer's Ring", "Knight Slayer's Ring", DS3LocationCategory.RING), + DS3LocationData("SL: Soul of the Old Demon King", "Soul of the Old Demon King", + prominent=True, boss=True), + DS3LocationData("SL: Fume Ultra Greatsword - ruins basement, NPC drop", + "Fume Ultra Greatsword", hostile_npc=True), # Knight Slayer Tsorig drop + DS3LocationData("SL: Black Iron Greatshield - ruins basement, NPC drop", + "Black Iron Greatshield", hostile_npc=True), # Knight Slayer Tsorig drop + DS3LocationData("SL: Large Titanite Shard - ledge by Demon Ruins bonfire", + "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by entrance", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, straight from entrance", + "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by tree #1", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by miniboss", "Large Titanite Shard"), + DS3LocationData("SL: Yellow Bug Pellet - side lake", "Yellow Bug Pellet x2"), + DS3LocationData("SL: Large Titanite Shard - side lake #1", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - side lake #2", "Large Titanite Shard"), + DS3LocationData("SL: Large Titanite Shard - lake, by tree #2", "Large Titanite Shard"), + DS3LocationData("SL: Speckled Stoneplate Ring - lake, ballista breaks bricks", + "Speckled Stoneplate Ring", hidden=True), # Requires careful ballista shot + DS3LocationData("SL: Homeward Bone - path to ballista", "Homeward Bone x2"), + DS3LocationData("SL: Ember - ruins main upper, hall end by hole", "Ember"), + DS3LocationData("SL: Chaos Gem - lake, far end by mob", "Chaos Gem"), + DS3LocationData("SL: Ember - ruins main lower, path to antechamber", "Ember"), + DS3LocationData("SL: Izalith Pyromancy Tome - antechamber, room near bonfire", + "Izalith Pyromancy Tome"), + DS3LocationData("SL: Black Knight Sword - ruins main lower, illusory wall in far hall", + "Black Knight Sword", hidden=True), + DS3LocationData("SL: Ember - ruins main upper, just after entrance", "Ember"), + DS3LocationData("SL: Quelana Pyromancy Tome - ruins main lower, illusory wall in grey room", + "Quelana Pyromancy Tome", hidden=True), + DS3LocationData("SL: Izalith Staff - ruins basement, second illusory wall behind chest", + "Izalith Staff", hidden=True), + DS3LocationData("SL: White Hair Talisman - ruins main lower, in lava", + "White Hair Talisman", + missable=True), # This may not even be possible to get without enough fire + # protection gear which the player may not have + DS3LocationData("SL: Toxic Mist - ruins main lower, in lava", "Toxic Mist", + missable=True), # This is _probably_ reachable with normal gear, but it + # still sucks and will probably force a death. + DS3LocationData("SL: Undead Bone Shard - ruins main lower, left after stairs", + "Undead Bone Shard"), + DS3LocationData("SL: Titanite Scale - ruins basement, path to lava", "Titanite Scale"), + DS3LocationData("SL: Shield of Want - lake, by miniboss", "Shield of Want"), + DS3LocationData("SL: Soul of a Crestfallen Knight - ruins basement, above lava", + "Soul of a Crestfallen Knight"), + + # Lava items are missable because they require a complex set of armor, rings, spells, and + # undead bone shards to reliably access without dying. + DS3LocationData("SL: Ember - ruins basement, in lava", "Ember", missable=True), # In lava + DS3LocationData("SL: Sacred Flame - ruins basement, in lava", "Sacred Flame", + missable=True), # In lava + + DS3LocationData("SL: Dragonrider Bow - by ladder from ruins basement to ballista", + "Dragonrider Bow", hidden=True), # Hidden fall + DS3LocationData("SL: Estus Shard - antechamber, illusory wall", "Estus Shard", + hidden=True), + DS3LocationData("SL: Bloodbite Ring+1 - behind ballista", "Bloodbite Ring+1", ngp=True), + DS3LocationData("SL: Flame Stoneplate Ring+2 - ruins main lower, illusory wall in far hall", + "Flame Stoneplate Ring+2", ngp=True, hidden=True), + DS3LocationData("SL: Large Titanite Shard - ruins basement, illusory wall in upper hall", + "Large Titanite Shard x3", hidden=True), + DS3LocationData("SL: Undead Bone Shard - lake, miniboss drop", "Undead Bone Shard", + miniboss=True), # Sand Worm drop + DS3LocationData("SL: Lightning Stake - lake, miniboss drop", "Lightning Stake", + miniboss=True), # Sand Worm drop + DS3LocationData("SL: Twinkling Titanite - path to side lake, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("SL: Titanite Chunk - path to side lake, lizard", "Titanite Chunk", + lizard=True), + DS3LocationData("SL: Chaos Gem - antechamber, lizard at end of long hall", "Chaos Gem", + lizard=True), + DS3LocationData("SL: Knight Slayer's Ring - ruins basement, NPC drop", + "Knight Slayer's Ring", hostile_npc=True), # Knight Slayer Tsorig drop + + # Horace the Hushed + # These are listed here even though you can kill Horace in the Road of Sacrifices because + # the player may want to complete his and Anri's quest first. + DS3LocationData("SL: Llewellyn Shield - Horace drop", "Llewellyn Shield", npc=True, + hostile_npc=True), + DS3LocationData("FS: Executioner Helm - shop after killing Horace", "Executioner Helm", + npc=True, hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Executioner Armor - shop after killing Horace", "Executioner Armor", + npc=True, hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Executioner Gauntlets - shop after killing Horace", + "Executioner Gauntlets", hostile_npc=True, npc=True, shop=True, + hidden=True), + DS3LocationData("FS: Executioner Leggings - shop after killing Horace", + "Executioner Leggings", hostile_npc=True, npc=True, shop=True, + hidden=True), + + # Shrine Handmaid after killing Knight Slayer Tsorig + DS3LocationData("FS: Black Iron Helm - shop after killing Tsorig", "Black Iron Helm", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Black Iron Armor - shop after killing Tsorig", "Black Iron Armor", + hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Black Iron Gauntlets - shop after killing Tsorig", + "Black Iron Gauntlets", hostile_npc=True, shop=True, hidden=True), + DS3LocationData("FS: Black Iron Leggings - shop after killing Tsorig", + "Black Iron Leggings", hostile_npc=True, shop=True, hidden=True), + + # Near Cornyx's cage after killing Old Demon King with Cuculus + DS3LocationData("US: Spotted Whip - by Cornyx's cage after Cuculus quest", "Spotted Whip", + missable=True, boss=True, npc=True), + DS3LocationData("US: Cornyx's Garb - by Cornyx's cage after Cuculus quest", + "Cornyx's Garb", static='02,0:53100100::', missable=True, boss=True, + npc=True), + DS3LocationData("US: Cornyx's Wrap - by Cornyx's cage after Cuculus quest", "Cornyx's Wrap", + static='02,0:53100100::', missable=True, boss=True, npc=True), + DS3LocationData("US: Cornyx's Skirt - by Cornyx's cage after Cuculus quest", + "Cornyx's Skirt", static='02,0:53100100::', missable=True, boss=True, + npc=True), ], "Irithyll of the Boreal Valley": [ - DS3LocationData("IBV: Dorhys' Gnawing", "Dorhys' Gnawing", DS3LocationCategory.SPELL), - DS3LocationData("IBV: Witchtree Branch", "Witchtree Branch", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Magic Clutch Ring", "Magic Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Ring of the Sun's First Born", "Ring of the Sun's First Born", DS3LocationCategory.RING), - DS3LocationData("IBV: Roster of Knights", "Roster of Knights", DS3LocationCategory.MISC), - DS3LocationData("IBV: Pontiff's Right Eye", "Pontiff's Right Eye", DS3LocationCategory.RING), - DS3LocationData("IBV: Yorshka's Spear", "Yorshka's Spear", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Great Heal", "Great Heal", DS3LocationCategory.SPELL), - DS3LocationData("IBV: Smough's Great Hammer", "Smough's Great Hammer", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Leo Ring", "Leo Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Excrement-covered Ashes", "Excrement-covered Ashes", DS3LocationCategory.MISC), - DS3LocationData("IBV: Dark Stoneplate Ring", "Dark Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Easterner's Ashes", "Easterner's Ashes", DS3LocationCategory.MISC), - DS3LocationData("IBV: Painting Guardian's Curved Sword", "Painting Guardian's Curved Sword", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Painting Guardian Hood", "Painting Guardian Hood", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Painting Guardian Gown", "Painting Guardian Gown", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Painting Guardian Gloves", "Painting Guardian Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Painting Guardian Waistcloth", "Painting Guardian Waistcloth", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Dragonslayer Greatbow", "Dragonslayer Greatbow", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Reversal Ring", "Reversal Ring", DS3LocationCategory.RING), - DS3LocationData("IBV: Brass Helm", "Brass Helm", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Brass Armor", "Brass Armor", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Brass Gauntlets", "Brass Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Brass Leggings", "Brass Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("IBV: Ring of Favor", "Ring of Favor", DS3LocationCategory.RING), - DS3LocationData("IBV: Golden Ritual Spear", "Golden Ritual Spear", DS3LocationCategory.WEAPON), - DS3LocationData("IBV: Soul of Pontiff Sulyvahn", "Soul of Pontiff Sulyvahn", DS3LocationCategory.BOSS), - DS3LocationData("IBV: Aldrich Faithful", "Aldrich Faithful", DS3LocationCategory.MISC), - DS3LocationData("IBV: Drang Twinspears", "Drang Twinspears", DS3LocationCategory.WEAPON), + DS3LocationData("IBV: Soul of Pontiff Sulyvahn", "Soul of Pontiff Sulyvahn", + prominent=True, boss=True), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - central, by bonfire", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Large Titanite Shard - ascent, down ladder in last building", + "Large Titanite Shard"), + DS3LocationData("IBV: Soul of a Weary Warrior - central, by first fountain", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Soul of a Weary Warrior - central, railing by first fountain", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Rime-blue Moss Clump - central, by bonfire", "Rime-blue Moss Clump"), + DS3LocationData("IBV: Witchtree Branch - by Dorhys", "Witchtree Branch", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Large Titanite Shard - central, side path after first fountain", + "Large Titanite Shard"), + DS3LocationData("IBV: Budding Green Blossom - central, by second fountain", + "Budding Green Blossom"), + DS3LocationData("IBV: Rime-blue Moss Clump - central, past second fountain", + "Rime-blue Moss Clump x2"), + DS3LocationData("IBV: Large Titanite Shard - central, balcony just before plaza", + "Large Titanite Shard"), + DS3LocationData("IBV: Large Titanite Shard - path to Dorhys", "Large Titanite Shard", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Ring of the Sun's First Born - fall from in front of cathedral", + "Ring of the Sun's First Born", + hidden=True), # Hidden fall + DS3LocationData("IBV: Large Soul of a Nameless Soldier - path to plaza", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Large Titanite Shard - plaza, balcony overlooking ascent", + "Large Titanite Shard"), + DS3LocationData("IBV: Large Titanite Shard - plaza, by stairs to church", + "Large Titanite Shard"), + DS3LocationData("IBV: Soul of a Weary Warrior - plaza, side room lower", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Magic Clutch Ring - plaza, illusory wall", "Magic Clutch Ring", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Fading Soul - descent, cliff edge #1", "Fading Soul"), + DS3LocationData("IBV: Fading Soul - descent, cliff edge #2", "Fading Soul"), + DS3LocationData("IBV: Homeward Bone - descent, before gravestone", "Homeward Bone x3"), + DS3LocationData("IBV: Undead Bone Shard - descent, behind gravestone", "Undead Bone Shard", + hidden=True), # Hidden behind gravestone + DS3LocationData("IBV: Kukri - descent, side path", "Kukri x8"), + DS3LocationData("IBV: Rusted Gold Coin - descent, side path", "Rusted Gold Coin"), + DS3LocationData("IBV: Blue Bug Pellet - descent, dark room", "Blue Bug Pellet x2"), + DS3LocationData("IBV: Shriving Stone - descent, dark room rafters", "Shriving Stone"), + DS3LocationData("IBV: Blood Gem - descent, platform before lake", "Blood Gem"), + DS3LocationData("IBV: Green Blossom - lake, by stairs from descent", "Green Blossom x3"), + DS3LocationData("IBV: Ring of Sacrifice - lake, right of stairs from descent", + "Ring of Sacrifice"), + DS3LocationData("IBV: Great Heal - lake, dead Corpse-Grub", "Great Heal"), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - lake island", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Green Blossom - lake wall", "Green Blossom x3"), + DS3LocationData("IBV: Dung Pie - sewer #1", "Dung Pie x3"), + DS3LocationData("IBV: Dung Pie - sewer #2", "Dung Pie x3"), + # These don't actually guard any single item sales. Maybe we can inject one manually? + DS3LocationData("IBV: Excrement-covered Ashes - sewer, by stairs", + "Excrement-covered Ashes"), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - ascent, after great hall", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Soul of a Weary Warrior - ascent, by final staircase", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Large Titanite Shard - ascent, by elevator door", + "Large Titanite Shard"), + DS3LocationData("IBV: Blue Bug Pellet - ascent, in last building", "Blue Bug Pellet x2"), + DS3LocationData("IBV: Ember - shortcut from church to cathedral", "Ember"), + DS3LocationData("IBV: Green Blossom - lake, by Distant Manor", "Green Blossom"), + DS3LocationData("IBV: Lightning Gem - plaza center", "Lightning Gem"), + DS3LocationData("IBV: Large Soul of a Nameless Soldier - central, by second fountain", + "Large Soul of a Nameless Soldier"), + DS3LocationData("IBV: Soul of a Weary Warrior - plaza, side room upper", + "Soul of a Weary Warrior"), + DS3LocationData("IBV: Proof of a Concord Kept - Church of Yorshka altar", + "Proof of a Concord Kept"), + DS3LocationData("IBV: Rusted Gold Coin - Distant Manor, drop after stairs", + "Rusted Gold Coin"), + DS3LocationData("IBV: Chloranthy Ring+1 - plaza, behind altar", "Chloranthy Ring+1", + ngp=True), + DS3LocationData("IBV: Covetous Gold Serpent Ring+1 - descent, drop after dark room", + "Covetous Gold Serpent Ring+1", ngp=True, hidden=True), # Hidden fall + DS3LocationData("IBV: Wood Grain Ring+2 - ascent, right after great hall", "Wood Grain Ring+2", + ngp=True), + DS3LocationData("IBV: Divine Blessing - great hall, chest", "Divine Blessing"), + DS3LocationData("IBV: Smough's Great Hammer - great hall, chest", + "Smough's Great Hammer"), + DS3LocationData("IBV: Yorshka's Spear - descent, dark room rafters chest", "Yorshka's Spear"), + DS3LocationData("IBV: Leo Ring - great hall, chest", "Leo Ring"), + DS3LocationData("IBV: Dorhys' Gnawing - Dorhys drop", "Dorhys' Gnawing", + hidden=True), # Behind illusory wall + DS3LocationData("IBV: Divine Blessing - great hall, mob drop", + "Divine Blessing", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Large Titanite Shard - great hall, main floor mob drop", + "Large Titanite Shard", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Large Titanite Shard - great hall, upstairs mob drop #1", + "Large Titanite Shard x2", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Large Titanite Shard - great hall, upstairs mob drop #2", + "Large Titanite Shard x2", drop=True, + hidden=True), # Guaranteed drop from normal-looking Silver Knight + DS3LocationData("IBV: Roster of Knights - descent, first landing", "Roster of Knights"), + DS3LocationData("IBV: Twinkling Titanite - descent, lizard behind illusory wall", + "Twinkling Titanite", lizard=True, hidden=True), # Behind illusory wall + DS3LocationData("IBV: Twinkling Titanite - central, lizard before plaza", + "Twinkling Titanite", lizard=True), + DS3LocationData("IBV: Large Titanite Shard - Distant Manor, under overhang", + "Large Titanite Shard"), + DS3LocationData("IBV: Siegbräu - Siegward", "Siegbräu", missable=True, npc=True), + DS3LocationData("IBV: Emit Force - Siegward", "Emit Force", missable=True, npc=True), + DS3LocationData("IBV -> ID", None), + + # After winning both Londor Pale Shade invasions + DS3LocationData("FS: Sneering Mask - Yoel's room, kill Londor Pale Shade twice", + "Sneering Mask", missable=True, hostile_npc=True), + DS3LocationData("FS: Pale Shade Robe - Yoel's room, kill Londor Pale Shade twice", + "Pale Shade Robe", missable=True, hostile_npc=True), + DS3LocationData("FS: Pale Shade Gloves - Yoel's room, kill Londor Pale Shade twice", + "Pale Shade Gloves", missable=True, hostile_npc=True), + DS3LocationData("FS: Pale Shade Trousers - Yoel's room, kill Londor Pale Shade twice", + "Pale Shade Trousers", missable=True, hostile_npc=True), + + # Anri of Astora + DS3LocationData("IBV: Ring of the Evil Eye - Anri", "Ring of the Evil Eye", missable=True, + npc=True), + + # Sirris quest after killing Creighton + DS3LocationData("FS: Mail Breaker - Sirris for killing Creighton", "Mail Breaker", + static='99,0:50006080::', missable=True, hostile_npc=True, + npc=True), + DS3LocationData("FS: Silvercat Ring - Sirris for killing Creighton", "Silvercat Ring", + missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Dragonslayer's Axe - Creighton drop", "Dragonslayer's Axe", + missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Creighton's Steel Mask - bridge after killing Creighton", + "Creighton's Steel Mask", missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Mirrah Chain Mail - bridge after killing Creighton", + "Mirrah Chain Mail", missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Mirrah Chain Gloves - bridge after killing Creighton", + "Mirrah Chain Gloves", missable=True, hostile_npc=True, npc=True), + DS3LocationData("IBV: Mirrah Chain Leggings - bridge after killing Creighton", + "Mirrah Chain Leggings", missable=True, hostile_npc=True, npc=True), ], "Irithyll Dungeon": [ - DS3LocationData("ID: Bellowing Dragoncrest Ring", "Bellowing Dragoncrest Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Jailbreaker's Key", "Jailbreaker's Key", DS3LocationCategory.KEY), - DS3LocationData("ID: Prisoner Chief's Ashes", "Prisoner Chief's Ashes", DS3LocationCategory.KEY), - DS3LocationData("ID: Old Sorcerer Hat", "Old Sorcerer Hat", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Old Sorcerer Coat", "Old Sorcerer Coat", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Old Sorcerer Gauntlets", "Old Sorcerer Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Old Sorcerer Boots", "Old Sorcerer Boots", DS3LocationCategory.ARMOR), - DS3LocationData("ID: Great Magic Shield", "Great Magic Shield", DS3LocationCategory.SPELL), - DS3LocationData("ID: Dragon Torso Stone", "Dragon Torso Stone", DS3LocationCategory.MISC), - DS3LocationData("ID: Lightning Blade", "Lightning Blade", DS3LocationCategory.SPELL), - DS3LocationData("ID: Profaned Coal", "Profaned Coal", DS3LocationCategory.MISC), - DS3LocationData("ID: Xanthous Ashes", "Xanthous Ashes", DS3LocationCategory.MISC), - DS3LocationData("ID: Old Cell Key", "Old Cell Key", DS3LocationCategory.KEY), - DS3LocationData("ID: Pickaxe", "Pickaxe", DS3LocationCategory.WEAPON), - DS3LocationData("ID: Profaned Flame", "Profaned Flame", DS3LocationCategory.SPELL), - DS3LocationData("ID: Covetous Gold Serpent Ring", "Covetous Gold Serpent Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Jailer's Key Ring", "Jailer's Key Ring", DS3LocationCategory.KEY), - DS3LocationData("ID: Dusk Crown Ring", "Dusk Crown Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Dark Clutch Ring", "Dark Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("ID: Karla's Ashes", "Karla's Ashes", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Pointed Hat", "Karla's Pointed Hat", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Coat", "Karla's Coat", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Gloves", "Karla's Gloves", DS3LocationCategory.NPC), - DS3LocationData("ID: Karla's Trousers", "Karla's Trousers", DS3LocationCategory.NPC), + DS3LocationData("ID: Titanite Slab - Siegward", "Titanite Slab", missable=True, + npc=True), + DS3LocationData("ID: Murakumo - Alva drop", "Murakumo", missable=True, + hostile_npc=True), + DS3LocationData("ID: Large Titanite Shard - after bonfire, second cell on left", + "Large Titanite Shard"), + DS3LocationData("ID: Fading Soul - B1 near, main hall", "Fading Soul"), + DS3LocationData("ID: Large Soul of a Nameless Soldier - B2, hall by stairs", + "Large Soul of a Nameless Soldier"), + DS3LocationData("ID: Jailbreaker's Key - B1 far, cell after gate", "Jailbreaker's Key"), + DS3LocationData("ID: Pale Pine Resin - B1 far, cell with broken wall", + "Pale Pine Resin x2"), + DS3LocationData("ID: Simple Gem - B2 far, cell by stairs", "Simple Gem"), + DS3LocationData("ID: Large Soul of a Nameless Soldier - B2 far, by lift", + "Large Soul of a Nameless Soldier"), + DS3LocationData("ID: Large Titanite Shard - B1 far, rightmost cell", + "Large Titanite Shard"), + DS3LocationData("ID: Homeward Bone - path from B2 to pit", "Homeward Bone x2"), + DS3LocationData("ID: Bellowing Dragoncrest Ring - drop from B1 towards pit", + "Bellowing Dragoncrest Ring", conditional=True), + DS3LocationData("ID: Soul of a Weary Warrior - by drop to pit", "Soul of a Weary Warrior"), + DS3LocationData("ID: Soul of a Crestfallen Knight - balcony above pit", + "Soul of a Crestfallen Knight"), + DS3LocationData("ID: Lightning Bolt - awning over pit", "Lightning Bolt x9"), + DS3LocationData("ID: Large Titanite Shard - pit #1", "Large Titanite Shard"), + DS3LocationData("ID: Profaned Flame - pit", "Profaned Flame"), + DS3LocationData("ID: Large Titanite Shard - pit #2", "Large Titanite Shard"), + DS3LocationData("ID: Soul of a Weary Warrior - stairs between pit and B3", + "Soul of a Weary Warrior"), + DS3LocationData("ID: Dung Pie - B3, by path from pit", "Dung Pie x4"), + DS3LocationData("ID: Ember - B3 center", "Ember"), + DS3LocationData("ID: Ember - B3 far right", "Ember"), + DS3LocationData("ID: Profaned Coal - B3 far, left cell", "Profaned Coal"), + DS3LocationData("ID: Large Titanite Shard - B3 near, right corner", "Large Titanite Shard"), + DS3LocationData("ID: Old Sorcerer Hat - B2 near, middle cell", "Old Sorcerer Hat"), + DS3LocationData("ID: Old Sorcerer Coat - B2 near, middle cell", "Old Sorcerer Coat"), + DS3LocationData("ID: Old Sorcerer Gauntlets - B2 near, middle cell", + "Old Sorcerer Gauntlets"), + DS3LocationData("ID: Old Sorcerer Boots - B2 near, middle cell", "Old Sorcerer Boots"), + DS3LocationData("ID: Large Soul of a Weary Warrior - just before Profaned Capital", + "Large Soul of a Weary Warrior"), + DS3LocationData("ID: Covetous Gold Serpent Ring - Siegward's cell", + "Covetous Gold Serpent Ring", conditional=True), + DS3LocationData("ID: Lightning Blade - B3 lift, middle platform", "Lightning Blade"), + DS3LocationData("ID: Rusted Coin - after bonfire, first cell on left", "Rusted Coin"), + DS3LocationData("ID: Dusk Crown Ring - B3 far, right cell", "Dusk Crown Ring"), + DS3LocationData("ID: Pickaxe - path from pit to B3", "Pickaxe"), + DS3LocationData("ID: Xanthous Ashes - B3 far, right cell", "Xanthous Ashes", + progression=True), + DS3LocationData("ID: Large Titanite Shard - B1 near, by door", "Large Titanite Shard"), + DS3LocationData("ID: Rusted Gold Coin - after bonfire, last cell on right", + "Rusted Gold Coin"), + DS3LocationData("ID: Old Cell Key - stairs between pit and B3", "Old Cell Key"), + DS3LocationData("ID: Covetous Silver Serpent Ring+1 - pit lift, middle platform", + "Covetous Silver Serpent Ring+1", ngp=True), + DS3LocationData("ID: Dragon Torso Stone - B3, outside lift", "Dragon Torso Stone"), + DS3LocationData("ID: Prisoner Chief's Ashes - B2 near, locked cell by stairs", + "Prisoner Chief's Ashes", progression=True), + DS3LocationData("ID: Great Magic Shield - B2 near, mob drop in far left cell", + "Great Magic Shield", drop=True, + hidden=True), # Guaranteed drop from a normal-looking Corpse-Grub + DS3LocationData("ID: Dragonslayer Lightning Arrow - pit, mimic in hall", + "Dragonslayer Lightning Arrow x10", mimic=True), + DS3LocationData("ID: Titanite Scale - B3 far, mimic in hall", "Titanite Scale x2", + mimic=True), + DS3LocationData("ID: Dark Clutch Ring - stairs between pit and B3, mimic", + "Dark Clutch Ring", mimic=True), + DS3LocationData("ID: Estus Shard - mimic on path from B2 to pit", "Estus Shard", + mimic=True), + DS3LocationData("ID: Titanite Chunk - balcony above pit, lizard", "Titanite Chunk", + lizard=True), + DS3LocationData("ID: Titanite Scale - B2 far, lizard", "Titanite Scale", lizard=True), + + # These are missable because of a bug that causes them to be dropped wherever the giant is + # randomized to, instead of where the miniboss is in vanilla. + DS3LocationData("ID: Dung Pie - pit, miniboss drop", "Dung Pie x4", + miniboss=True, missable=True), # Giant slave drop + DS3LocationData("ID: Titanite Chunk - pit, miniboss drop", "Titanite Chunk", + miniboss=True, missable=True), # Giant Slave Drop + + # Alva (requires ember) + DS3LocationData("ID: Alva Helm - B3 near, by Karla's cell, after killing Alva", "Alva Helm", + missable=True, npc=True), + DS3LocationData("ID: Alva Armor - B3 near, by Karla's cell, after killing Alva", + "Alva Armor", missable=True, npc=True), + DS3LocationData("ID: Alva Gauntlets - B3 near, by Karla's cell, after killing Alva", + "Alva Gauntlets", missable=True, npc=True), + DS3LocationData("ID: Alva Leggings - B3 near, by Karla's cell, after killing Alva", + "Alva Leggings", missable=True, npc=True), ], "Profaned Capital": [ - DS3LocationData("PC: Cursebite Ring", "Cursebite Ring", DS3LocationCategory.RING), - DS3LocationData("PC: Court Sorcerer Hood", "Court Sorcerer Hood", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Court Sorcerer Robe", "Court Sorcerer Robe", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Court Sorcerer Gloves", "Court Sorcerer Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Court Sorcerer Trousers", "Court Sorcerer Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("PC: Wrath of the Gods", "Wrath of the Gods", DS3LocationCategory.SPELL), - DS3LocationData("PC: Logan's Scroll", "Logan's Scroll", DS3LocationCategory.MISC), - DS3LocationData("PC: Eleonora", "Eleonora", DS3LocationCategory.WEAPON), - DS3LocationData("PC: Court Sorcerer's Staff", "Court Sorcerer's Staff", DS3LocationCategory.WEAPON), - DS3LocationData("PC: Greatshield of Glory", "Greatshield of Glory", DS3LocationCategory.SHIELD), - DS3LocationData("PC: Storm Ruler", "Storm Ruler", DS3LocationCategory.KEY), - DS3LocationData("PC: Cinders of a Lord - Yhorm the Giant", "Cinders of a Lord - Yhorm the Giant", DS3LocationCategory.KEY), - DS3LocationData("PC: Soul of Yhorm the Giant", "Soul of Yhorm the Giant", DS3LocationCategory.BOSS), + DS3LocationData("PC: Soul of Yhorm the Giant", "Soul of Yhorm the Giant", boss=True), + DS3LocationData("PC: Cinders of a Lord - Yhorm the Giant", + "Cinders of a Lord - Yhorm the Giant", static="07,0:50002170::", + prominent=True, progression=True, boss=True), + DS3LocationData("PC: Logan's Scroll - chapel roof, NPC drop", "Logan's Scroll", + hostile_npc=True), # Sorcerer + DS3LocationData("PC: Purging Stone - chapel ground floor", "Purging Stone x3"), + DS3LocationData("PC: Rusted Coin - tower exterior", "Rusted Coin x2"), + DS3LocationData("PC: Rusted Gold Coin - halls above swamp", "Rusted Gold Coin"), + DS3LocationData("PC: Purging Stone - swamp, by chapel ladder", "Purging Stone"), + DS3LocationData("PC: Cursebite Ring - swamp, below halls", "Cursebite Ring"), + DS3LocationData("PC: Poison Gem - swamp, below halls", "Poison Gem"), + DS3LocationData("PC: Shriving Stone - swamp, by chapel door", "Shriving Stone"), + DS3LocationData("PC: Poison Arrow - chapel roof", "Poison Arrow x18"), + DS3LocationData("PC: Rubbish - chapel, down stairs from second floor", "Rubbish"), + DS3LocationData("PC: Onislayer Greatarrow - bridge", "Onislayer Greatarrow x8"), + DS3LocationData("PC: Large Soul of a Weary Warrior - bridge, far end", + "Large Soul of a Weary Warrior"), + DS3LocationData("PC: Rusted Coin - below bridge #1", "Rusted Coin"), + DS3LocationData("PC: Rusted Coin - below bridge #2", "Rusted Coin"), + DS3LocationData("PC: Blooming Purple Moss Clump - walkway above swamp", + "Blooming Purple Moss Clump x3"), + DS3LocationData("PC: Wrath of the Gods - chapel, drop from roof", "Wrath of the Gods"), + DS3LocationData("PC: Onislayer Greatbow - drop from bridge", "Onislayer Greatbow", + hidden=True), # Hidden fall + DS3LocationData("PC: Jailer's Key Ring - hall past chapel", "Jailer's Key Ring", + progression=True), + DS3LocationData("PC: Ember - palace, far room", "Ember"), + DS3LocationData("PC: Flame Stoneplate Ring+1 - chapel, drop from roof towards entrance", + "Flame Stoneplate Ring+1", ngp=True, hidden=True), # Hidden fall + DS3LocationData("PC: Magic Stoneplate Ring+2 - tower base", "Magic Stoneplate Ring+2", + ngp=True), + DS3LocationData("PC: Court Sorcerer Hood - chapel, second floor", "Court Sorcerer Hood"), + DS3LocationData("PC: Court Sorcerer Robe - chapel, second floor", "Court Sorcerer Robe"), + DS3LocationData("PC: Court Sorcerer Gloves - chapel, second floor", "Court Sorcerer Gloves"), + DS3LocationData("PC: Court Sorcerer Trousers - chapel, second floor", + "Court Sorcerer Trousers"), + DS3LocationData("PC: Storm Ruler - boss room", "Storm Ruler"), + DS3LocationData("PC: Undead Bone Shard - by bonfire", "Undead Bone Shard"), + DS3LocationData("PC: Eleonora - chapel ground floor, kill mob", "Eleonora", + drop=True, + hidden=True), # Guaranteed drop from a normal-looking Monstrosity of Sin + DS3LocationData("PC: Rusted Gold Coin - palace, mimic in far room", "Rusted Gold Coin x2", + mimic=True), + DS3LocationData("PC: Court Sorcerer's Staff - chapel, mimic on second floor", + "Court Sorcerer's Staff", mimic=True), + DS3LocationData("PC: Greatshield of Glory - palace, mimic in far room", + "Greatshield of Glory", mimic=True), + DS3LocationData("PC: Twinkling Titanite - halls above swamp, lizard #1", + "Twinkling Titanite", lizard=True), + DS3LocationData("PC: Twinkling Titanite - halls above swamp, lizard #2", + "Twinkling Titanite", lizard=True), + DS3LocationData("PC: Siegbräu - Siegward after killing boss", "Siegbräu", + missable=True, npc=True), + + # Siegward drops (kill or quest) + DS3LocationData("PC: Storm Ruler - Siegward", "Storm Ruler", static='02,0:50006218::', + missable=True, drop=True, npc=True), + DS3LocationData("PC: Pierce Shield - Siegward", "Pierce Shield", missable=True, + drop=True, npc=True), ], + # We consider "Anor Londo" to be everything accessible only after killing Pontiff. This doesn't + # match up one-to-one with where the game pops up the region name, but it balances items better + # and covers the region that's full of DS1 Anor Londo references. "Anor Londo": [ - DS3LocationData("AL: Giant's Coal", "Giant's Coal", DS3LocationCategory.MISC), - DS3LocationData("AL: Sun Princess Ring", "Sun Princess Ring", DS3LocationCategory.RING), - DS3LocationData("AL: Aldrich's Ruby", "Aldrich's Ruby", DS3LocationCategory.RING), - DS3LocationData("AL: Cinders of a Lord - Aldrich", "Cinders of a Lord - Aldrich", DS3LocationCategory.KEY), - DS3LocationData("AL: Soul of Aldrich", "Soul of Aldrich", DS3LocationCategory.BOSS), + DS3LocationData("AL: Soul of Aldrich", "Soul of Aldrich", boss=True), + DS3LocationData("AL: Cinders of a Lord - Aldrich", "Cinders of a Lord - Aldrich", + static='06,0:50002130::', prominent=True, progression=True, + boss=True), + DS3LocationData("AL: Yorshka's Chime - kill Yorshka", "Yorshka's Chime", missable=True, + drop=True, + npc=True), # Hidden walkway, missable because it will break Sirris's quest + DS3LocationData("AL: Drang Twinspears - plaza, NPC drop", "Drang Twinspears", drop=True, + hidden=True), + DS3LocationData("AL: Estus Shard - dark cathedral, by left stairs", "Estus Shard"), + DS3LocationData("AL: Painting Guardian's Curved Sword - prison tower rafters", + "Painting Guardian's Curved Sword", hidden=True), # Invisible walkway + DS3LocationData("AL: Brass Helm - tomb", "Brass Helm", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Brass Armor - tomb", "Brass Armor", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Brass Gauntlets - tomb", "Brass Gauntlets", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Brass Leggings - tomb", "Brass Leggings", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Human Dregs - water reserves", "Human Dregs", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Ember - spiral staircase, bottom", "Ember"), + DS3LocationData("AL: Large Titanite Shard - bottom of the furthest buttress", + "Large Titanite Shard"), + DS3LocationData("AL: Large Titanite Shard - right after light cathedral", + "Large Titanite Shard"), + DS3LocationData("AL: Large Titanite Shard - walkway, side path by cathedral", + "Large Titanite Shard"), + DS3LocationData("AL: Soul of a Weary Warrior - plaza, nearer", "Soul of a Weary Warrior"), + DS3LocationData("AL: Ember - plaza, right side", "Ember"), + DS3LocationData("AL: Ember - plaza, further", "Ember"), + DS3LocationData("AL: Large Titanite Shard - balcony by dead giants", + "Large Titanite Shard"), + DS3LocationData("AL: Dark Stoneplate Ring - by dark stairs up from plaza", + "Dark Stoneplate Ring"), + DS3LocationData("AL: Large Titanite Shard - bottom of the nearest buttress", + "Large Titanite Shard"), + DS3LocationData("AL: Deep Gem - water reserves", "Deep Gem"), + DS3LocationData("AL: Titanite Scale - top of ladder up to buttresses", "Titanite Scale"), + DS3LocationData("AL: Dragonslayer Greatarrow - drop from nearest buttress", + "Dragonslayer Greatarrow x5", static='06,0:53700620::', + hidden=True), # Hidden fall + DS3LocationData("AL: Dragonslayer Greatbow - drop from nearest buttress", + "Dragonslayer Greatbow", static='06,0:53700620::', + hidden=True), # Hidden fall + DS3LocationData("AL: Easterner's Ashes - below top of furthest buttress", + "Easterner's Ashes", progression=True), + DS3LocationData("AL: Painting Guardian Hood - prison tower, rafters", + "Painting Guardian Hood", hidden=True), # Invisible walkway + DS3LocationData("AL: Painting Guardian Gown - prison tower, rafters", + "Painting Guardian Gown", hidden=True), # Invisible walkway + DS3LocationData("AL: Painting Guardian Gloves - prison tower, rafters", + "Painting Guardian Gloves", hidden=True), # Invisible walkway + DS3LocationData("AL: Painting Guardian Waistcloth - prison tower, rafters", + "Painting Guardian Waistcloth", hidden=True), # Invisible walkway + DS3LocationData("AL: Soul of a Crestfallen Knight - right of dark cathedral entrance", + "Soul of a Crestfallen Knight"), + DS3LocationData("AL: Moonlight Arrow - dark cathedral, up right stairs", + "Moonlight Arrow x6"), + DS3LocationData("AL: Proof of a Concord Kept - dark cathedral, up left stairs", + "Proof of a Concord Kept"), + DS3LocationData("AL: Large Soul of a Weary Warrior - left of dark cathedral entrance", + "Large Soul of a Weary Warrior"), + DS3LocationData("AL: Giant's Coal - by giant near dark cathedral", "Giant's Coal"), + DS3LocationData("AL: Havel's Ring+2 - prison tower, rafters", "Havel's Ring+2", ngp=True, + hidden=True), # Invisible walkway + DS3LocationData("AL: Ring of Favor+1 - light cathedral, upstairs", "Ring of Favor+1", + ngp=True), + DS3LocationData("AL: Sun Princess Ring - dark cathedral, after boss", "Sun Princess Ring"), + DS3LocationData("AL: Reversal Ring - tomb, chest in corner", "Reversal Ring", + hidden=True), # Behind illusory wall + DS3LocationData("AL: Golden Ritual Spear - light cathedral, mimic upstairs", + "Golden Ritual Spear", mimic=True), + DS3LocationData("AL: Ring of Favor - water reserves, both minibosses", "Ring of Favor", + miniboss=True, + hidden=True), # Sulyvahn's Beast Duo drop, behind illusory wall + DS3LocationData("AL: Blade of the Darkmoon - Yorshka with Darkmoon Loyalty", + "Blade of the Darkmoon", missable=True, drop=True, + npc=True), # Hidden walkway, missable because it will break Sirris's quest + DS3LocationData("AL: Simple Gem - light cathedral, lizard upstairs", "Simple Gem", + lizard=True), + DS3LocationData("AL: Twinkling Titanite - lizard after light cathedral #1", + "Twinkling Titanite", lizard=True), + DS3LocationData("AL: Twinkling Titanite - lizard after light cathedral #2", + "Twinkling Titanite", lizard=True), + DS3LocationData("AL: Aldrich's Ruby - dark cathedral, miniboss", "Aldrich's Ruby", + miniboss=True), # Deep Accursed drop + DS3LocationData("AL: Aldrich Faithful - water reserves, talk to McDonnel", "Aldrich Faithful", + hidden=True), # Behind illusory wall + + DS3LocationData("FS: Budding Green Blossom - shop after killing Creighton and AL boss", + "Budding Green Blossom", static='99,0:-1:110000,70000118:', + missable=True, npc=True, + shop=True), # sold by Shrine Maiden after killing Aldrich and helping + # Sirris defeat Creighton + + # Sirris (quest completion) + DS3LocationData("FS: Sunset Shield - by grave after killing Hodrick w/Sirris", + "Sunset Shield", missable=True, hostile_npc=True, npc=True), + # In Pit of Hollows after killing Hodrick + DS3LocationData("US: Sunset Helm - Pit of Hollows after killing Hodrick w/Sirris", + "Sunset Helm", missable=True, hostile_npc=True, npc=True), + DS3LocationData("US: Sunset Armor - pit of hollows after killing Hodrick w/Sirris", + "Sunset Armor", missable=True, hostile_npc=True, npc=True), + DS3LocationData("US: Sunset Gauntlets - pit of hollows after killing Hodrick w/Sirris", + "Sunset Gauntlets", missable=True, hostile_npc=True, npc=True), + DS3LocationData("US: Sunset Leggings - pit of hollows after killing Hodrick w/Sirris", + "Sunset Leggings", missable=True, hostile_npc=True, npc=True), + + # Shrine Handmaid after killing Sulyvahn's Beast Duo + DS3LocationData("FS: Helm of Favor - shop after killing water reserve minibosses", + "Helm of Favor", hidden=True, miniboss=True, shop=True), + DS3LocationData("FS: Embraced Armor of Favor - shop after killing water reserve minibosses", + "Embraced Armor of Favor", hidden=True, miniboss=True, shop=True), + DS3LocationData("FS: Gauntlets of Favor - shop after killing water reserve minibosses", + "Gauntlets of Favor", hidden=True, miniboss=True, shop=True), + DS3LocationData("FS: Leggings of Favor - shop after killing water reserve minibosses", + "Leggings of Favor", hidden=True, miniboss=True, shop=True), + + # Anri of Astora + DS3LocationData("AL: Chameleon - tomb after marrying Anri", "Chameleon", missable=True, + npc=True), + DS3LocationData("AL: Anri's Straight Sword - Anri quest", "Anri's Straight Sword", + missable=True, npc=True), + + # Shrine Handmaid after killing Ringfinger Leonhard + # This is listed here even though you can kill Leonhard immediately because we want the + # logic to assume people will do his full quest. Missable because he can disappear forever + # if you use up all your Pale Tongues. + DS3LocationData("FS: Leonhard's Garb - shop after killing Leonhard", + "Leonhard's Garb", hidden=True, npc=True, shop=True, missable=True), + DS3LocationData("FS: Leonhard's Gauntlets - shop after killing Leonhard", + "Leonhard's Gauntlets", hidden=True, npc=True, shop=True, + missable=True), + DS3LocationData("FS: Leonhard's Trousers - shop after killing Leonhard", + "Leonhard's Trousers", hidden=True, npc=True, shop=True, + missable=True), + + # Shrine Handmaid after killing Alrich, Devourer of Gods + DS3LocationData("FS: Smough's Helm - shop after killing AL boss", "Smough's Helm", + boss=True, shop=True), + DS3LocationData("FS: Smough's Armor - shop after killing AL boss", "Smough's Armor", + boss=True, shop=True), + DS3LocationData("FS: Smough's Gauntlets - shop after killing AL boss", "Smough's Gauntlets", + boss=True, shop=True), + DS3LocationData("FS: Smough's Leggings - shop after killing AL boss", "Smough's Leggings", + boss=True, shop=True), + + # Ringfinger Leonhard (quest or kill) + DS3LocationData("AL: Crescent Moon Sword - Leonhard drop", "Crescent Moon Sword", + missable=True, npc=True), + DS3LocationData("AL: Silver Mask - Leonhard drop", "Silver Mask", missable=True, + npc=True), + DS3LocationData("AL: Soul of Rosaria - Leonhard drop", "Soul of Rosaria", missable=True, + npc=True), + + # Shrine Handmaid after killing Anri or completing their quest + DS3LocationData("FS: Elite Knight Helm - shop after Anri quest", "Elite Knight Helm", + npc=True, shop=True), + DS3LocationData("FS: Elite Knight Armor - shop after Anri quest", "Elite Knight Armor", + npc=True, shop=True), + DS3LocationData("FS: Elite Knight Gauntlets - shop after Anri quest", + "Elite Knight Gauntlets", npc=True, shop=True), + DS3LocationData("FS: Elite Knight Leggings - shop after Anri quest", + "Elite Knight Leggings", npc=True, shop=True), ], "Lothric Castle": [ - DS3LocationData("LC: Hood of Prayer", "Hood of Prayer", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Robe of Prayer", "Robe of Prayer", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Skirt of Prayer", "Skirt of Prayer", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Sacred Bloom Shield", "Sacred Bloom Shield", DS3LocationCategory.SHIELD), - DS3LocationData("LC: Winged Knight Helm", "Winged Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Winged Knight Armor", "Winged Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Winged Knight Gauntlets", "Winged Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Winged Knight Leggings", "Winged Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("LC: Greatlance", "Greatlance", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Sniper Crossbow", "Sniper Crossbow", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Spirit Tree Crest Shield", "Spirit Tree Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("LC: Red Tearstone Ring", "Red Tearstone Ring", DS3LocationCategory.RING), - DS3LocationData("LC: Caitha's Chime", "Caitha's Chime", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Braille Divine Tome of Lothric", "Braille Divine Tome of Lothric", DS3LocationCategory.MISC), - DS3LocationData("LC: Knight's Ring", "Knight's Ring", DS3LocationCategory.RING), - DS3LocationData("LC: Irithyll Rapier", "Irithyll Rapier", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Sunlight Straight Sword", "Sunlight Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("LC: Soul of Dragonslayer Armour", "Soul of Dragonslayer Armour", DS3LocationCategory.BOSS), - DS3LocationData("LC: Grand Archives Key", "Grand Archives Key", DS3LocationCategory.KEY), - DS3LocationData("LC: Gotthard Twinswords", "Gotthard Twinswords", DS3LocationCategory.WEAPON), + DS3LocationData("LC: Soul of Dragonslayer Armour", "Soul of Dragonslayer Armour", + prominent=True, boss=True), + DS3LocationData("LC: Sniper Bolt - moat, right path end", "Sniper Bolt x11"), + DS3LocationData("LC: Sniper Crossbow - moat, right path end", "Sniper Crossbow"), + DS3LocationData("LC: Titanite Scale - dark room, upper balcony", "Titanite Scale"), + DS3LocationData("LC: Titanite Chunk - dark room mid, out door opposite wyvern", + "Titanite Chunk"), + DS3LocationData("LC: Greatlance - overlooking Dragon Barracks bonfire", "Greatlance"), + DS3LocationData("LC: Titanite Chunk - ascent, first balcony", "Titanite Chunk"), + DS3LocationData("LC: Titanite Chunk - ascent, turret before barricades", "Titanite Chunk"), + DS3LocationData("LC: Sacred Bloom Shield - ascent, behind illusory wall", + "Sacred Bloom Shield", hidden=True), # Behind illusory wall + DS3LocationData("LC: Titanite Chunk - ascent, final turret", "Titanite Chunk x2"), + DS3LocationData("LC: Refined Gem - plaza", "Refined Gem"), + DS3LocationData("LC: Soul of a Crestfallen Knight - by lift bottom", + "Soul of a Crestfallen Knight"), + DS3LocationData("LC: Undead Bone Shard - moat, far ledge", "Undead Bone Shard"), + DS3LocationData("LC: Lightning Urn - moat, right path, first room", "Lightning Urn x3"), + DS3LocationData("LC: Titanite Chunk - moat #1", "Titanite Chunk"), + DS3LocationData("LC: Titanite Chunk - moat #2", "Titanite Chunk"), + DS3LocationData("LC: Titanite Chunk - moat, near ledge", "Titanite Chunk"), + DS3LocationData("LC: Caitha's Chime - chapel, drop onto roof", "Caitha's Chime"), + DS3LocationData("LC: Lightning Urn - plaza", "Lightning Urn x6"), + DS3LocationData("LC: Ember - plaza, by gate", "Ember"), + DS3LocationData("LC: Raw Gem - plaza left", "Raw Gem"), + DS3LocationData("LC: Black Firebomb - dark room lower", "Black Firebomb x3"), + DS3LocationData("LC: Pale Pine Resin - dark room upper, by mimic", "Pale Pine Resin"), + DS3LocationData("LC: Large Soul of a Weary Warrior - main hall, by lever", + "Large Soul of a Weary Warrior"), + DS3LocationData("LC: Sunlight Medal - by lift top", "Sunlight Medal"), + DS3LocationData("LC: Soul of a Crestfallen Knight - wyvern room, balcony", + "Soul of a Crestfallen Knight", hidden=True), # Hidden fall + DS3LocationData("LC: Titanite Chunk - altar roof", "Titanite Chunk"), + DS3LocationData("LC: Titanite Scale - dark room mid, out door opposite wyvern", + "Titanite Scale"), + DS3LocationData("LC: Large Soul of a Nameless Soldier - moat, right path", + "Large Soul of a Nameless Soldier"), + DS3LocationData("LC: Knight's Ring - altar", "Knight's Ring"), + DS3LocationData("LC: Ember - main hall, left of stairs", "Ember"), + DS3LocationData("LC: Large Soul of a Weary Warrior - ascent, last turret", + "Large Soul of a Weary Warrior"), + DS3LocationData("LC: Ember - by Dragon Barracks bonfire", "Ember"), + DS3LocationData("LC: Twinkling Titanite - ascent, side room", "Twinkling Titanite"), + DS3LocationData("LC: Large Soul of a Nameless Soldier - dark room mid", + "Large Soul of a Nameless Soldier"), + DS3LocationData("LC: Ember - plaza center", "Ember"), + DS3LocationData("LC: Winged Knight Helm - ascent, behind illusory wall", + "Winged Knight Helm", hidden=True), + DS3LocationData("LC: Winged Knight Armor - ascent, behind illusory wall", + "Winged Knight Armor", hidden=True), + DS3LocationData("LC: Winged Knight Gauntlets - ascent, behind illusory wall", + "Winged Knight Gauntlets", hidden=True), + DS3LocationData("LC: Winged Knight Leggings - ascent, behind illusory wall", + "Winged Knight Leggings", hidden=True), + DS3LocationData("LC: Rusted Coin - chapel", "Rusted Coin x2"), + DS3LocationData("LC: Braille Divine Tome of Lothric - wyvern room", + "Braille Divine Tome of Lothric", hidden=True), # Hidden fall + DS3LocationData("LC: Red Tearstone Ring - chapel, drop onto roof", "Red Tearstone Ring"), + DS3LocationData("LC: Twinkling Titanite - moat, left side", "Twinkling Titanite x2"), + DS3LocationData("LC: Large Soul of a Nameless Soldier - plaza left, by pillar", + "Large Soul of a Nameless Soldier"), + DS3LocationData("LC: Titanite Scale - altar", "Titanite Scale x3"), + DS3LocationData("LC: Titanite Scale - chapel, chest", "Titanite Scale"), + DS3LocationData("LC: Hood of Prayer", "Hood of Prayer"), + DS3LocationData("LC: Robe of Prayer - ascent, chest at beginning", "Robe of Prayer"), + DS3LocationData("LC: Skirt of Prayer - ascent, chest at beginning", "Skirt of Prayer"), + DS3LocationData("LC: Spirit Tree Crest Shield - basement, chest", + "Spirit Tree Crest Shield"), + DS3LocationData("LC: Titanite Scale - basement, chest", "Titanite Scale"), + DS3LocationData("LC: Twinkling Titanite - basement, chest #1", "Twinkling Titanite"), + DS3LocationData("LC: Twinkling Titanite - basement, chest #2", "Twinkling Titanite x2"), + DS3LocationData("LC: Life Ring+2 - dark room mid, out door opposite wyvern, drop down", + "Life Ring+2", ngp=True, hidden=True), # Hidden fall + DS3LocationData("LC: Dark Stoneplate Ring+1 - wyvern room, balcony", + "Dark Stoneplate Ring+1", ngp=True, hidden=True), # Hidden fall + DS3LocationData("LC: Thunder Stoneplate Ring+2 - chapel, drop onto roof", + "Thunder Stoneplate Ring+2", ngp=True), + DS3LocationData("LC: Sunlight Straight Sword - wyvern room, mimic", + "Sunlight Straight Sword", mimic=True, hidden=True), # Hidden fall + DS3LocationData("LC: Titanite Scale - dark room, upper, mimic", "Titanite Scale x3", + mimic=True), + DS3LocationData("LC: Ember - wyvern room, wyvern foot mob drop", "Ember x2", + drop=True, hidden=True), # Hidden fall, Pus of Man Wyvern drop + DS3LocationData("LC: Titanite Chunk - wyvern room, wyvern foot mob drop", "Titanite Chunk x2", + drop=True, hidden=True), # Hidden fall, Pus of Man Wyvern drop + DS3LocationData("LC: Ember - dark room mid, pus of man mob drop", "Ember x2", + drop=True), # Pus of Man Wyvern drop + DS3LocationData("LC: Titanite Chunk - dark room mid, pus of man mob drop", + "Titanite Chunk x2"), + DS3LocationData("LC: Irithyll Rapier - basement, miniboss drop", "Irithyll Rapier", + miniboss=True), # Boreal Outrider drop + DS3LocationData("LC: Twinkling Titanite - dark room mid, out door opposite wyvern, lizard", + "Twinkling Titanite x2", lizard=True, missable=True), + DS3LocationData("LC: Twinkling Titanite - moat, right path, lizard", + "Twinkling Titanite x2", lizard=True, missable=True), + DS3LocationData("LC: Gotthard Twinswords - by Grand Archives door, after PC and AL bosses", + "Gotthard Twinswords", conditional=True), + DS3LocationData("LC: Grand Archives Key - by Grand Archives door, after PC and AL bosses", + "Grand Archives Key", prominent=True, progression=True, + conditional=True), + DS3LocationData("LC: Titanite Chunk - down stairs after boss", "Titanite Chunk"), + + # Eygon of Carim (kill or quest) + DS3LocationData("FS: Morne's Great Hammer - Eygon", "Morne's Great Hammer", npc=True), + DS3LocationData("FS: Moaning Shield - Eygon", "Moaning Shield", npc=True), + + # Shrine Handmaid after killing Dragonslayer Armour (or Eygon of Carim) + DS3LocationData("FS: Dancer's Crown - shop after killing LC entry boss", "Dancer's Crown", + boss=True, shop=True), + DS3LocationData("FS: Dancer's Armor - shop after killing LC entry boss", "Dancer's Armor", + boss=True, shop=True), + DS3LocationData("FS: Dancer's Gauntlets - shop after killing LC entry boss", + "Dancer's Gauntlets", boss=True, shop=True), + DS3LocationData("FS: Dancer's Leggings - shop after killing LC entry boss", + "Dancer's Leggings", boss=True, shop=True), + + # Shrine Handmaid after killing Dragonslayer Armour (or Eygon of Carim) + DS3LocationData("FS: Morne's Helm - shop after killing Eygon or LC boss", "Morne's Helm", + boss=True, shop=True), + DS3LocationData("FS: Morne's Armor - shop after killing Eygon or LC boss", "Morne's Armor", + boss=True, shop=True), + DS3LocationData("FS: Morne's Gauntlets - shop after killing Eygon or LC boss", + "Morne's Gauntlets", boss=True, shop=True), + DS3LocationData("FS: Morne's Leggings - shop after killing Eygon or LC boss", + "Morne's Leggings", boss=True, shop=True), ], "Consumed King's Garden": [ - DS3LocationData("CKG: Dragonscale Ring", "Dragonscale Ring", DS3LocationCategory.RING), - DS3LocationData("CKG: Shadow Mask", "Shadow Mask", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Shadow Garb", "Shadow Garb", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Shadow Gauntlets", "Shadow Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Shadow Leggings", "Shadow Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("CKG: Claw", "Claw", DS3LocationCategory.WEAPON), - DS3LocationData("CKG: Soul of Consumed Oceiros", "Soul of Consumed Oceiros", DS3LocationCategory.BOSS), - DS3LocationData("CKG: Magic Stoneplate Ring", "Magic Stoneplate Ring", DS3LocationCategory.RING), + DS3LocationData("CKG: Soul of Consumed Oceiros", "Soul of Consumed Oceiros", + prominent=True, boss=True), + # Could classify this as "hidden" because it's midway down an elevator, but the elevator is + # so slow and the midway point is so obvious that it's not actually hard to find. + DS3LocationData("CKG: Estus Shard - balcony", "Estus Shard"), + DS3LocationData("CKG: Shadow Mask - under center platform", "Shadow Mask"), + DS3LocationData("CKG: Shadow Garb - under rotunda", "Shadow Garb"), + DS3LocationData("CKG: Shadow Gauntlets - under rotunda", "Shadow Gauntlets"), + DS3LocationData("CKG: Shadow Leggings - under rotunda", "Shadow Leggings"), + DS3LocationData("CKG: Black Firebomb - under rotunda", "Black Firebomb x2"), + DS3LocationData("CKG: Claw - under rotunda", "Claw"), + DS3LocationData("CKG: Titanite Chunk - up lone stairway", "Titanite Chunk"), + DS3LocationData("CKG: Dragonscale Ring - shortcut, leave halfway down lift", + "Dragonscale Ring"), + DS3LocationData("CKG: Human Pine Resin - toxic pool, past rotunda", "Human Pine Resin"), + DS3LocationData("CKG: Titanite Chunk - shortcut", "Titanite Chunk"), + DS3LocationData("CKG: Titanite Chunk - balcony, drop onto rubble", "Titanite Chunk"), + DS3LocationData("CKG: Soul of a Weary Warrior - before first lift", + "Soul of a Weary Warrior"), + DS3LocationData("CKG: Dark Gem - under lone stairway", "Dark Gem"), + DS3LocationData("CKG: Titanite Scale - shortcut", "Titanite Scale"), + DS3LocationData("CKG: Human Pine Resin - pool by lift", "Human Pine Resin x2"), + DS3LocationData("CKG: Titanite Chunk - right of shortcut lift bottom", "Titanite Chunk"), + DS3LocationData("CKG: Ring of Sacrifice - under balcony", "Ring of Sacrifice"), + DS3LocationData("CKG: Wood Grain Ring+1 - by first elevator bottom", "Wood Grain Ring+1", + ngp=True), + DS3LocationData("CKG: Sage Ring+2 - balcony, drop onto rubble, jump back", "Sage Ring+2", + ngp=True, hidden=True), + DS3LocationData("CKG: Titanite Scale - tomb, chest #1", "Titanite Scale"), + DS3LocationData("CKG: Titanite Scale - tomb, chest #2", "Titanite Scale"), + DS3LocationData("CKG: Magic Stoneplate Ring - mob drop before boss", + "Magic Stoneplate Ring", drop=True, + hidden=True), # Guaranteed drop from a normal-looking Cathedral Knight + + # After Oceiros's boss room, only once the Drakeblood summon in AP has been killed + DS3LocationData("CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC", + "Drakeblood Helm", hostile_npc=True, hidden=True), + DS3LocationData("CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPC", + "Drakeblood Armor", hostile_npc=True, hidden=True), + DS3LocationData("CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPC", + "Drakeblood Gauntlets", hostile_npc=True, hidden=True), + DS3LocationData("CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC", + "Drakeblood Leggings", hostile_npc=True, hidden=True), ], "Grand Archives": [ - DS3LocationData("GA: Avelyn", "Avelyn", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Witch's Locks", "Witch's Locks", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Power Within", "Power Within", DS3LocationCategory.SPELL), - DS3LocationData("GA: Scholar Ring", "Scholar Ring", DS3LocationCategory.RING), - DS3LocationData("GA: Soul Stream", "Soul Stream", DS3LocationCategory.SPELL), - DS3LocationData("GA: Fleshbite Ring", "Fleshbite Ring", DS3LocationCategory.RING), - DS3LocationData("GA: Crystal Chime", "Crystal Chime", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Golden Wing Crest Shield", "Golden Wing Crest Shield", DS3LocationCategory.SHIELD), - DS3LocationData("GA: Onikiri and Ubadachi", "Onikiri and Ubadachi", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Hunter's Ring", "Hunter's Ring", DS3LocationCategory.RING), - DS3LocationData("GA: Divine Pillars of Light", "Divine Pillars of Light", DS3LocationCategory.SPELL), - DS3LocationData("GA: Cinders of a Lord - Lothric Prince", "Cinders of a Lord - Lothric Prince", DS3LocationCategory.KEY), - DS3LocationData("GA: Soul of the Twin Princes", "Soul of the Twin Princes", DS3LocationCategory.BOSS), - DS3LocationData("GA: Sage's Crystal Staff", "Sage's Crystal Staff", DS3LocationCategory.WEAPON), - DS3LocationData("GA: Outrider Knight Helm", "Outrider Knight Helm", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Outrider Knight Armor", "Outrider Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Outrider Knight Gauntlets", "Outrider Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Outrider Knight Leggings", "Outrider Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("GA: Crystal Scroll", "Crystal Scroll", DS3LocationCategory.MISC), + DS3LocationData("GA: Titanite Slab - final elevator secret", "Titanite Slab", + hidden=True), + DS3LocationData("GA: Soul of the Twin Princes", "Soul of the Twin Princes", boss=True), + DS3LocationData("GA: Cinders of a Lord - Lothric Prince", + "Cinders of a Lord - Lothric Prince", + static="09,0:50002040::", prominent=True, progression=True, + boss=True), + DS3LocationData("GA: Onikiri and Ubadachi - outside 5F, NPC drop", "Onikiri and Ubadachi", + hostile_npc=True, # Black Hand Kamui drop + missable=True), # This is placed at the location the NPC gets randomized + # to, which makes it hard to include in logic. + DS3LocationData("GA: Golden Wing Crest Shield - outside 5F, NPC drop", + "Golden Wing Crest Shield", + hostile_npc=True), # Lion Knight Albert drop + DS3LocationData("GA: Sage's Crystal Staff - outside 5F, NPC drop", + "Sage's Crystal Staff", + hostile_npc=True), # Daughter of Crystal Kriemhild drop + DS3LocationData("GA: Titanite Chunk - 1F, up right stairs", "Titanite Chunk"), + DS3LocationData("GA: Titanite Chunk - 1F, path from wax pool", "Titanite Chunk"), + DS3LocationData("GA: Soul of a Crestfallen Knight - 1F, loop left after drop", + "Soul of a Crestfallen Knight"), + DS3LocationData("GA: Titanite Chunk - 1F, balcony", "Titanite Chunk"), + DS3LocationData("GA: Fleshbite Ring - up stairs from 4F", "Fleshbite Ring"), + DS3LocationData("GA: Soul of a Crestfallen Knight - path to dome", + "Soul of a Crestfallen Knight"), + DS3LocationData("GA: Soul of a Nameless Soldier - dark room", "Soul of a Nameless Soldier"), + DS3LocationData("GA: Crystal Chime - 1F, path from wax pool", "Crystal Chime"), + DS3LocationData("GA: Titanite Scale - dark room, upstairs", "Titanite Scale"), + DS3LocationData("GA: Estus Shard - dome, far balcony", "Estus Shard"), + DS3LocationData("GA: Homeward Bone - 2F early balcony", "Homeward Bone x3"), + DS3LocationData("GA: Titanite Scale - 2F, titanite scale atop bookshelf", "Titanite Scale", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Chunk - 2F, by wax pool", "Titanite Chunk"), + DS3LocationData("GA: Hollow Gem - rooftops lower, in hall", "Hollow Gem", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Scale - 3F, corner up stairs", "Titanite Scale"), + DS3LocationData("GA: Titanite Scale - 1F, up stairs on bookshelf", "Titanite Scale"), + DS3LocationData("GA: Titanite Scale - 3F, by ladder to 2F late", "Titanite Scale", + hidden=True), # Hidden by a table + DS3LocationData("GA: Shriving Stone - 2F late, by ladder from 3F", "Shriving Stone"), + DS3LocationData("GA: Large Soul of a Crestfallen Knight - 4F, back", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("GA: Titanite Chunk - rooftops, balcony", "Titanite Chunk"), + DS3LocationData("GA: Titanite Scale - rooftops lower, path to 2F", "Titanite Scale x3", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Chunk - rooftops lower, ledge by buttress", "Titanite Chunk", + hidden=True), # Hidden fall + DS3LocationData("GA: Soul of a Weary Warrior - rooftops, by lizards", + "Soul of a Weary Warrior"), + DS3LocationData("GA: Titanite Chunk - rooftops, just before 5F", "Titanite Chunk"), + DS3LocationData("GA: Ember - 5F, by entrance", "Ember"), + DS3LocationData("GA: Blessed Gem - rafters", "Blessed Gem"), + DS3LocationData("GA: Titanite Chunk - 5F, far balcony", "Titanite Chunk x2"), + DS3LocationData("GA: Large Soul of a Crestfallen Knight - outside 5F", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("GA: Avelyn - 1F, drop from 3F onto bookshelves", "Avelyn", + hidden=True), # Hidden fall + DS3LocationData("GA: Titanite Chunk - 2F, right after dark room", "Titanite Chunk"), + DS3LocationData("GA: Hunter's Ring - dome, very top", "Hunter's Ring"), + DS3LocationData("GA: Divine Pillars of Light - cage above rafters", + "Divine Pillars of Light"), + DS3LocationData("GA: Power Within - dark room, behind retractable bookshelf", + "Power Within", hidden=True), # Switch in darkened room + DS3LocationData("GA: Sage Ring+1 - rafters, second level down", "Sage Ring+1", ngp=True), + DS3LocationData("GA: Lingering Dragoncrest Ring+2 - dome, room behind spire", + "Lingering Dragoncrest Ring+2", ngp=True), + DS3LocationData("GA: Divine Blessing - rafters, down lower level ladder", + "Divine Blessing"), + DS3LocationData("GA: Twinkling Titanite - rafters, down lower level ladder", + "Twinkling Titanite x3"), + DS3LocationData("GA: Witch's Locks - dark room, behind retractable bookshelf", + "Witch's Locks", hidden=True), # Switch in darkened room + DS3LocationData("GA: Titanite Slab - 1F, after pulling 2F switch", "Titanite Slab", + hidden=True), + DS3LocationData("GA: Titanite Scale - 4F, chest by exit", "Titanite Scale x3"), + DS3LocationData("GA: Soul Stream - 3F, behind illusory wall", "Soul Stream", + hidden=True), # Behind illusory wall + DS3LocationData("GA: Scholar Ring - 2F, between late and early", "Scholar Ring"), + DS3LocationData("GA: Undead Bone Shard - 5F, by entrance", "Undead Bone Shard"), + DS3LocationData("GA: Titanite Slab - dome, kill all mobs", "Titanite Slab", + drop=True, + hidden=True), # Guaranteed drop from killing all Winged Knights + DS3LocationData("GA: Outrider Knight Helm - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Helm", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Outrider Knight Armor - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Armor", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Outrider Knight Gauntlets - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Gauntlets", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Outrider Knight Leggings - 3F, behind illusory wall, miniboss drop", + "Outrider Knight Leggings", miniboss=True, + hidden=True), # Behind illusory wall, Outrider Knight drop + DS3LocationData("GA: Crystal Scroll - 2F late, miniboss drop", "Crystal Scroll", + miniboss=True), # Crystal Sage drop + DS3LocationData("GA: Twinkling Titanite - dark room, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Chaos Gem - dark room, lizard", "Chaos Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - 1F, lizard by drop", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Crystal Gem - 1F, lizard by drop", "Crystal Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - 2F, lizard by entrance", "Twinkling Titanite x2", + lizard=True), + DS3LocationData("GA: Titanite Scale - 1F, drop from 2F late onto bookshelves, lizard", + "Titanite Scale x2", lizard=True, hidden=True), # Hidden fall + DS3LocationData("GA: Twinkling Titanite - rooftops, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Heavy Gem - rooftops, lizard", "Heavy Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - rooftops, lizard #2", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Sharp Gem - rooftops, lizard", "Sharp Gem", lizard=True), + DS3LocationData("GA: Twinkling Titanite - up stairs from 4F, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("GA: Refined Gem - up stairs from 4F, lizard", "Refined Gem", + lizard=True), + DS3LocationData("GA: Twinkling Titanite - dark room, lizard #2", "Twinkling Titanite x2", + lizard=True), + + # Shrine Handmaid after killing NPCs + DS3LocationData("FS: Faraam Helm - shop after killing GA NPC", "Faraam Helm", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Faraam Armor - shop after killing GA NPC", "Faraam Armor", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Faraam Gauntlets - shop after killing GA NPC", "Faraam Gauntlets", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Faraam Boots - shop after killing GA NPC", "Faraam Boots", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Black Hand Hat - shop after killing GA NPC", "Black Hand Hat", + hidden=True, hostile_npc=True, shop=True), + DS3LocationData("FS: Black Hand Armor - shop after killing GA NPC", "Black Hand Armor", + hidden=True, hostile_npc=True, shop=True), + + # Shrine Handmaid after killing Lothric, Younger Prince + DS3LocationData("FS: Lorian's Helm - shop after killing GA boss", "Lorian's Helm", + boss=True, shop=True), + DS3LocationData("FS: Lorian's Armor - shop after killing GA boss", "Lorian's Armor", + boss=True, shop=True), + DS3LocationData("FS: Lorian's Gauntlets - shop after killing GA boss", "Lorian's Gauntlets", + boss=True, shop=True), + DS3LocationData("FS: Lorian's Leggings - shop after killing GA boss", "Lorian's Leggings", + boss=True, shop=True), + + # Sirris quest completion + beat Twin Princes + DS3LocationData("FS: Sunless Talisman - Sirris, kill GA boss", "Sunless Talisman", + missable=True, npc=True), + DS3LocationData("FS: Sunless Veil - shop, Sirris quest, kill GA boss", "Sunless Veil", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Sunless Armor - shop, Sirris quest, kill GA boss", "Sunless Armor", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Sunless Gauntlets - shop, Sirris quest, kill GA boss", + "Sunless Gauntlets", missable=True, npc=True, shop=True), + DS3LocationData("FS: Sunless Leggings - shop, Sirris quest, kill GA boss", + "Sunless Leggings", missable=True, npc=True, shop=True), + + # Unbreakable Patches + DS3LocationData("FS: Hidden Blessing - Patches after searching GA", "Hidden Blessing", + missable=True, npc=True, shop=True), ], "Untended Graves": [ - DS3LocationData("UG: Ashen Estus Ring", "Ashen Estus Ring", DS3LocationCategory.RING), - DS3LocationData("UG: Black Knight Glaive", "Black Knight Glaive", DS3LocationCategory.WEAPON), - DS3LocationData("UG: Hornet Ring", "Hornet Ring", DS3LocationCategory.RING), - DS3LocationData("UG: Chaos Blade", "Chaos Blade", DS3LocationCategory.WEAPON), - DS3LocationData("UG: Blacksmith Hammer", "Blacksmith Hammer", DS3LocationCategory.WEAPON), - DS3LocationData("UG: Eyes of a Fire Keeper", "Eyes of a Fire Keeper", DS3LocationCategory.KEY), - DS3LocationData("UG: Coiled Sword Fragment", "Coiled Sword Fragment", DS3LocationCategory.MISC), - DS3LocationData("UG: Soul of Champion Gundyr", "Soul of Champion Gundyr", DS3LocationCategory.BOSS), + DS3LocationData("UG: Soul of Champion Gundyr", "Soul of Champion Gundyr", prominent=True, + boss=True), + DS3LocationData("UG: Priestess Ring - shop", "Priestess Ring", shop=True), + DS3LocationData("UG: Shriving Stone - swamp, by bonfire", "Shriving Stone"), + DS3LocationData("UG: Titanite Chunk - swamp, left path by fountain", "Titanite Chunk"), + DS3LocationData("UG: Soul of a Crestfallen Knight - swamp, center", + "Soul of a Crestfallen Knight"), + DS3LocationData("UG: Titanite Chunk - swamp, right path by fountain", "Titanite Chunk"), + DS3LocationData("UG: Ashen Estus Ring - swamp, path opposite bonfire", "Ashen Estus Ring"), + DS3LocationData("UG: Black Knight Glaive - boss arena", "Black Knight Glaive"), + DS3LocationData("UG: Hidden Blessing - cemetery, behind coffin", "Hidden Blessing"), + DS3LocationData("UG: Eyes of a Fire Keeper - shrine, Irina's room", "Eyes of a Fire Keeper", + hidden=True), # Illusory wall + DS3LocationData("UG: Soul of a Crestfallen Knight - environs, above shrine entrance", + "Soul of a Crestfallen Knight"), + DS3LocationData("UG: Blacksmith Hammer - shrine, Andre's room", "Blacksmith Hammer"), + DS3LocationData("UG: Chaos Blade - environs, left of shrine", "Chaos Blade"), + DS3LocationData("UG: Hornet Ring - environs, right of main path after killing FK boss", + "Hornet Ring", conditional=True), + DS3LocationData("UG: Coiled Sword Fragment - shrine, dead bonfire", "Coiled Sword Fragment", + boss=True), + DS3LocationData("UG: Life Ring+3 - shrine, behind big throne", "Life Ring+3", ngp=True), + DS3LocationData("UG: Ring of Steel Protection+1 - environs, behind bell tower", + "Ring of Steel Protection+1", ngp=True), + + # Yuria shop, or Shrine Handmaiden with Hollow's Ashes + # This is here because this is where the ashes end up if you kill Yoel or Yuria + DS3LocationData("FS: Ring of Sacrifice - Yuria shop", "Ring of Sacrifice", + static='99,0:-1:40000,110000,70000107,70000116:', npc=True, + shop=True), + + # Untended Graves Handmaid + # All shop items are missable because she can be killed, except Priestess ring because she + # drops it on death anyway. + DS3LocationData("UG: Ember - shop", "Ember", shop=True, missable=True), + # Untended Graves Handmaid after killing Abyss Watchers + DS3LocationData("UG: Wolf Knight Helm - shop after killing FK boss", "Wolf Knight Helm", + boss=True, shop=True, conditional=True, + missable=True), + DS3LocationData("UG: Wolf Knight Armor - shop after killing FK boss", + "Wolf Knight Armor", boss=True, shop=True, missable=True), + DS3LocationData("UG: Wolf Knight Gauntlets - shop after killing FK boss", + "Wolf Knight Gauntlets", boss=True, shop=True, missable=True), + DS3LocationData("UG: Wolf Knight Leggings - shop after killing FK boss", + "Wolf Knight Leggings", boss=True, shop=True, missable=True), + + # Shrine Handmaid after killing Champion Gundyr + DS3LocationData("FS: Gundyr's Helm - shop after killing UG boss", "Gundyr's Helm", + boss=True, shop=True), + DS3LocationData("FS: Gundyr's Armor - shop after killing UG boss", "Gundyr's Armor", + boss=True, shop=True), + DS3LocationData("FS: Gundyr's Gauntlets - shop after killing UG boss", "Gundyr's Gauntlets", + boss=True, shop=True), + DS3LocationData("FS: Gundyr's Leggings - shop after killing UG boss", "Gundyr's Leggings", + boss=True, shop=True), ], "Archdragon Peak": [ - DS3LocationData("AP: Lightning Clutch Ring", "Lightning Clutch Ring", DS3LocationCategory.RING), - DS3LocationData("AP: Ancient Dragon Greatshield", "Ancient Dragon Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("AP: Ring of Steel Protection", "Ring of Steel Protection", DS3LocationCategory.RING), - DS3LocationData("AP: Calamity Ring", "Calamity Ring", DS3LocationCategory.RING), - DS3LocationData("AP: Drakeblood Greatsword", "Drakeblood Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Dragonslayer Spear", "Dragonslayer Spear", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Thunder Stoneplate Ring", "Thunder Stoneplate Ring", DS3LocationCategory.RING), - DS3LocationData("AP: Great Magic Barrier", "Great Magic Barrier", DS3LocationCategory.SPELL), - DS3LocationData("AP: Dragon Chaser's Ashes", "Dragon Chaser's Ashes", DS3LocationCategory.MISC), - DS3LocationData("AP: Twinkling Dragon Torso Stone", "Twinkling Dragon Torso Stone", DS3LocationCategory.MISC), - DS3LocationData("AP: Dragonslayer Helm", "Dragonslayer Helm", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Dragonslayer Armor", "Dragonslayer Armor", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Dragonslayer Gauntlets", "Dragonslayer Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Dragonslayer Leggings", "Dragonslayer Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("AP: Ricard's Rapier", "Ricard's Rapier", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Soul of the Nameless King", "Soul of the Nameless King", DS3LocationCategory.BOSS), - DS3LocationData("AP: Dragon Tooth", "Dragon Tooth", DS3LocationCategory.WEAPON), - DS3LocationData("AP: Havel's Greatshield", "Havel's Greatshield", DS3LocationCategory.SHIELD), + DS3LocationData("AP: Dragon Head Stone - fort, boss drop", "Dragon Head Stone", + prominent=True, boss=True), + DS3LocationData("AP: Soul of the Nameless King", "Soul of the Nameless King", + prominent=True, boss=True), + DS3LocationData("AP: Dragon Tooth - belfry roof, NPC drop", "Dragon Tooth", + hostile_npc=True), # Havel Knight drop + DS3LocationData("AP: Havel's Greatshield - belfry roof, NPC drop", "Havel's Greatshield", + hostile_npc=True), # Havel Knight drop + DS3LocationData("AP: Drakeblood Greatsword - mausoleum, NPC drop", "Drakeblood Greatsword", + hostile_npc=True), + DS3LocationData("AP: Ricard's Rapier - belfry, NPC drop", "Ricard's Rapier", + hostile_npc=True), + DS3LocationData("AP: Lightning Clutch Ring - intro, left of boss door", + "Lightning Clutch Ring"), + DS3LocationData("AP: Stalk Dung Pie - fort overlook", "Stalk Dung Pie x6"), + DS3LocationData("AP: Titanite Chunk - fort, second room balcony", "Titanite Chunk"), + DS3LocationData("AP: Titanite Scale - mausoleum, downstairs balcony #1", + "Titanite Scale"), + DS3LocationData("AP: Soul of a Weary Warrior - intro, first cliff edge", + "Soul of a Weary Warrior"), + DS3LocationData("AP: Titanite Chunk - intro, left before archway", "Titanite Chunk"), + DS3LocationData("AP: Lightning Gem - intro, side rise", "Lightning Gem"), + DS3LocationData("AP: Homeward Bone - intro, path to bonfire", "Homeward Bone x2"), + DS3LocationData("AP: Soul of a Nameless Soldier - intro, right before archway", + "Soul of a Nameless Soldier"), + DS3LocationData("AP: Titanite Chunk - intro, archway corner", "Titanite Chunk"), + DS3LocationData("AP: Ember - fort overlook #1", "Ember"), + DS3LocationData("AP: Large Soul of a Weary Warrior - fort, center", + "Large Soul of a Weary Warrior"), + DS3LocationData("AP: Large Soul of a Nameless Soldier - fort, by stairs to first room", + "Large Soul of a Nameless Soldier"), + DS3LocationData("AP: Lightning Urn - fort, left of first room entrance", + "Lightning Urn x4"), + DS3LocationData("AP: Lightning Bolt - rotunda", "Lightning Bolt x12"), + DS3LocationData("AP: Titanite Chunk - rotunda", "Titanite Chunk x2"), + # Not 100% sure about this location name, can't find this on any maps + DS3LocationData("AP: Dung Pie - fort, landing after second room", "Dung Pie x3"), + DS3LocationData("AP: Titanite Scale - mausoleum, downstairs balcony #2", "Titanite Scale"), + DS3LocationData("AP: Soul of a Weary Warrior - walkway, building window", + "Soul of a Weary Warrior"), + DS3LocationData("AP: Soul of a Crestfallen Knight - mausoleum, upstairs", + "Soul of a Crestfallen Knight"), + DS3LocationData("AP: Titanite Chunk - intro, behind rock", "Titanite Chunk"), + DS3LocationData("AP: Ember - fort overlook #2", "Ember"), + DS3LocationData("AP: Thunder Stoneplate Ring - walkway, up ladder", + "Thunder Stoneplate Ring"), + DS3LocationData("AP: Titanite Scale - mausoleum, upstairs balcony", "Titanite Scale"), + DS3LocationData("AP: Ember - belfry, below bell", "Ember"), + DS3LocationData("AP: Ancient Dragon Greatshield - intro, on archway", + "Ancient Dragon Greatshield"), + DS3LocationData("AP: Large Soul of a Crestfallen Knight - summit, by fountain", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("AP: Dragon Chaser's Ashes - summit, side path", "Dragon Chaser's Ashes", + progression=True), + DS3LocationData("AP: Ember - intro, by bonfire", "Ember"), + DS3LocationData("AP: Dragonslayer Spear - gate after mausoleum", "Dragonslayer Spear"), + DS3LocationData("AP: Dragonslayer Helm - plaza", "Dragonslayer Helm"), + DS3LocationData("AP: Dragonslayer Armor - plaza", "Dragonslayer Armor"), + DS3LocationData("AP: Dragonslayer Gauntlets - plaza", "Dragonslayer Gauntlets"), + DS3LocationData("AP: Dragonslayer Leggings - plaza", "Dragonslayer Leggings"), + DS3LocationData("AP: Twinkling Titanite - fort, end of rafters", "Twinkling Titanite x2"), + DS3LocationData("AP: Twinkling Titanite - fort, down second room balcony ladder", + "Twinkling Titanite x2"), + DS3LocationData("AP: Titanite Slab - belfry roof", "Titanite Slab"), + DS3LocationData("AP: Great Magic Barrier - drop off belfry roof", "Great Magic Barrier", + hidden=True), # Hidden fall + DS3LocationData("AP: Titanite Slab - plaza", "Titanite Slab"), + DS3LocationData("AP: Ring of Steel Protection - fort overlook, beside stairs", + "Ring of Steel Protection"), + DS3LocationData("AP: Havel's Ring+1 - summit, after building", "Havel's Ring+1", + ngp=True), + DS3LocationData("AP: Covetous Gold Serpent Ring+2 - plaza", "Covetous Gold Serpent Ring+2", + ngp=True), + DS3LocationData("AP: Titanite Scale - walkway building", "Titanite Scale x3"), + DS3LocationData("AP: Twinkling Titanite - belfry, by ladder to roof", + "Twinkling Titanite x3"), + DS3LocationData("AP: Twinkling Dragon Torso Stone - summit, gesture at altar", + "Twinkling Dragon Torso Stone", hidden=True), # Requires gesture + DS3LocationData("AP: Calamity Ring - mausoleum, gesture at altar", "Calamity Ring", + hidden=True), # Requires gesture + DS3LocationData("AP: Twinkling Titanite - walkway building, lizard", + "Twinkling Titanite x3", lizard=True), + DS3LocationData("AP: Titanite Chunk - walkway, miniboss drop", "Titanite Chunk x6", + miniboss=True), # Wyvern miniboss drop + DS3LocationData("AP: Titanite Scale - walkway, miniboss drop", "Titanite Scale x3", + miniboss=True), # Wyvern miniboss drop + DS3LocationData("AP: Twinkling Titanite - walkway, miniboss drop", "Twinkling Titanite x3", + miniboss=True), # Wyvern miniboss drop + DS3LocationData("FS: Hawkwood's Swordgrass - Andre after gesture in AP summit", + "Hawkwood's Swordgrass", conditional=True, hidden=True), + + # Shrine Handmaid after killing Nameless King + DS3LocationData("FS: Golden Crown - shop after killing AP boss", "Golden Crown", + boss=True, shop=True), + DS3LocationData("FS: Dragonscale Armor - shop after killing AP boss", "Dragonscale Armor", + boss=True, shop=True), + DS3LocationData("FS: Golden Bracelets - shop after killing AP boss", "Golden Bracelets", + boss=True, shop=True), + DS3LocationData("FS: Dragonscale Waistcloth - shop after killing AP boss", + "Dragonscale Waistcloth", boss=True, shop=True), + DS3LocationData("FK: Twinkling Dragon Head Stone - Hawkwood drop", + "Twinkling Dragon Head Stone", missable=True, + npc=True), # Hawkwood (quest) + ], + "Kiln of the First Flame": [ + DS3LocationData("KFF: Soul of the Lords", "Soul of the Lords", boss=True), + + # Shrine Handmaid after placing all Cinders of a Lord + DS3LocationData("FS: Titanite Slab - shop after placing all Cinders", "Titanite Slab", + static='99,0:-1:9210,110000:', hidden=True), + DS3LocationData("FS: Firelink Helm - shop after placing all Cinders", "Firelink Helm", + boss=True, shop=True), + DS3LocationData("FS: Firelink Armor - shop after placing all Cinders", "Firelink Armor", + boss=True, shop=True), + DS3LocationData("FS: Firelink Gauntlets - shop after placing all Cinders", + "Firelink Gauntlets", boss=True, shop=True), + DS3LocationData("FS: Firelink Leggings - shop after placing all Cinders", + "Firelink Leggings", boss=True, shop=True), + + # Yuria (quest, after Soul of Cinder) + DS3LocationData("FS: Billed Mask - Yuria after killing KFF boss", "Billed Mask", + missable=True, npc=True), + DS3LocationData("FS: Black Dress - Yuria after killing KFF boss", "Black Dress", + missable=True, npc=True), + DS3LocationData("FS: Black Gauntlets - Yuria after killing KFF boss", "Black Gauntlets", + missable=True, npc=True), + DS3LocationData("FS: Black Leggings - Yuria after killing KFF boss", "Black Leggings", + missable=True, npc=True), ], - "Kiln of the First Flame": [], # DLC - "Painted World of Ariandel 1": [ - DS3LocationData("PW: Follower Javelin", "Follower Javelin", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Frozen Weapon", "Frozen Weapon", DS3LocationCategory.SPELL), - DS3LocationData("PW: Millwood Greatbow", "Millwood Greatbow", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Captain's Ashes", "Captain's Ashes", DS3LocationCategory.MISC), - DS3LocationData("PW: Millwood Battle Axe", "Millwood Battle Axe", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Ethereal Oak Shield", "Ethereal Oak Shield", DS3LocationCategory.SHIELD), - DS3LocationData("PW: Crow Quills", "Crow Quills", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Slave Knight Hood", "Slave Knight Hood", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Slave Knight Armor", "Slave Knight Armor", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Slave Knight Gauntlets", "Slave Knight Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Slave Knight Leggings", "Slave Knight Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Way of White Corona", "Way of White Corona", DS3LocationCategory.SPELL), - DS3LocationData("PW: Crow Talons", "Crow Talons", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Onyx Blade", "Onyx Blade", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Contraption Key", "Contraption Key", DS3LocationCategory.KEY), + "Painted World of Ariandel (Before Contraption)": [ + DS3LocationData("PW1: Valorheart - boss drop", "Valorheart", prominent=True, boss=True), + DS3LocationData("PW1: Contraption Key - library, NPC drop", "Contraption Key", + prominent=True, progression=True, + hostile_npc=True), # Sir Vilhelm drop + DS3LocationData("PW1: Onyx Blade - library, NPC drop", "Onyx Blade", + hostile_npc=True), # Sir Vilhelm drop + DS3LocationData("PW1: Chillbite Ring - Friede", "Chillbite Ring", + npc=True), # Friede conversation + DS3LocationData("PW1: Rime-blue Moss Clump - snowfield upper, starting cave", + "Rime-blue Moss Clump x2"), + DS3LocationData("PW1: Poison Gem - snowfield upper, forward from bonfire", "Poison Gem"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield lower, path back up", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Follower Javelin - snowfield lower, path back up", "Follower Javelin"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield lower, path to village", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Homeward Bone - snowfield village, outcropping", "Homeward Bone x6"), + DS3LocationData("PW1: Blessed Gem - snowfield, behind tower", "Blessed Gem", + hidden=True), # Hidden behind a tower + DS3LocationData("PW1: Captain's Ashes - snowfield tower, 6F", "Captain's Ashes", + progression=True), + DS3LocationData("PW1: Black Firebomb - snowfield lower, path to bonfire", + "Black Firebomb x2"), + DS3LocationData("PW1: Shriving Stone - below bridge near", "Shriving Stone"), + DS3LocationData("PW1: Millwood Greatarrow - snowfield village, loop back to lower", + "Millwood Greatarrow x5"), + DS3LocationData("PW1: Millwood Greatbow - snowfield village, loop back to lower", + "Millwood Greatbow"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield upper", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Rusted Coin - snowfield lower, straight from fall", "Rusted Coin"), + DS3LocationData("PW1: Large Titanite Shard - snowfield lower, left from fall", + "Large Titanite Shard"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement courtyard, cliff", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Crow Quills - settlement loop, jump into courtyard", "Crow Quills", + hidden=True), # Hidden fall + DS3LocationData("PW1: Simple Gem - settlement, lowest level, behind gate", "Simple Gem"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement, by ladder to bonfire", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Slave Knight Hood - settlement roofs, drop by ladder", + "Slave Knight Hood"), + DS3LocationData("PW1: Slave Knight Armor - settlement roofs, drop by ladder", + "Slave Knight Armor"), + DS3LocationData("PW1: Slave Knight Gauntlets - settlement roofs, drop by ladder", + "Slave Knight Gauntlets"), + DS3LocationData("PW1: Slave Knight Leggings - settlement roofs, drop by ladder", + "Slave Knight Leggings"), + DS3LocationData("PW1: Ember - settlement main, left building after bridge", "Ember"), + DS3LocationData("PW1: Dark Gem - settlement back, egg building", "Dark Gem"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement roofs, balcony", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement loop, by bonfire", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Rusted Gold Coin - settlement roofs, roof near second ladder", + "Rusted Gold Coin x3"), + DS3LocationData("PW1: Soul of a Crestfallen Knight - settlement hall, rafters", + "Soul of a Crestfallen Knight"), + DS3LocationData("PW1: Way of White Corona - settlement hall, by altar", + "Way of White Corona"), + DS3LocationData("PW1: Rusted Coin - right of library", "Rusted Coin x2"), + DS3LocationData("PW1: Young White Branch - right of library", "Young White Branch"), + DS3LocationData("PW1: Budding Green Blossom - settlement courtyard, ledge", + "Budding Green Blossom x3"), + DS3LocationData("PW1: Crow Talons - settlement roofs, near bonfire", "Crow Talons"), + DS3LocationData("PW1: Hollow Gem - beside chapel", "Hollow Gem"), + DS3LocationData("PW1: Rime-blue Moss Clump - below bridge far", "Rime-blue Moss Clump x4"), + DS3LocationData("PW1: Follower Sabre - roots above depths", "Follower Sabre"), + DS3LocationData("PW1: Ember - roots above depths", "Ember"), + DS3LocationData("PW1: Snap Freeze - depths, far end, mob drop", "Snap Freeze", drop=True, + hidden=True), # Guaranteed drop from normal-looking Tree Woman + DS3LocationData("PW1: Rime-blue Moss Clump - snowfield upper, overhang", + "Rime-blue Moss Clump"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - snowfield lower, by cliff", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Ember - settlement, building near bonfire", "Ember"), + DS3LocationData("PW1: Frozen Weapon - snowfield lower, egg zone", "Frozen Weapon"), + DS3LocationData("PW1: Titanite Slab - depths, up secret ladder", "Titanite Slab", + static='11,0:54500640::', + hidden=True), # Must kill normal-looking Tree Woman + DS3LocationData("PW1: Homeward Bone - depths, up hill", "Homeward Bone x2"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - below snowfield village overhang", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Large Soul of a Weary Warrior - settlement hall roof", + "Large Soul of a Weary Warrior"), + DS3LocationData("PW1: Large Soul of an Unknown Traveler - settlement back", + "Large Soul of an Unknown Traveler"), + DS3LocationData("PW1: Heavy Gem - snowfield village", "Heavy Gem"), + DS3LocationData("PW1: Large Soul of a Weary Warrior - snowfield tower, 6F", + "Large Soul of a Weary Warrior"), + DS3LocationData("PW1: Millwood Battle Axe - snowfield tower, 5F", "Millwood Battle Axe"), + DS3LocationData("PW1: Ethereal Oak Shield - snowfield tower, 3F", "Ethereal Oak Shield"), + DS3LocationData("PW1: Soul of a Weary Warrior - snowfield tower, 1F", + "Soul of a Weary Warrior"), + DS3LocationData("PW1: Twinkling Titanite - snowfield tower, 3F lizard", + "Twinkling Titanite", lizard=True), + DS3LocationData("PW1: Large Titanite Shard - lizard under bridge near", + "Large Titanite Shard", lizard=True), + DS3LocationData("PW1: Twinkling Titanite - roots, lizard", "Twinkling Titanite", + lizard=True), + DS3LocationData("PW1: Twinkling Titanite - settlement roofs, lizard before hall", + "Twinkling Titanite", lizard=True), + DS3LocationData("PW1: Large Titanite Shard - settlement loop, lizard", + "Large Titanite Shard x2", lizard=True), ], - "Painted World of Ariandel 2": [ - DS3LocationData("PW: Quakestone Hammer", "Quakestone Hammer", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Earth Seeker", "Earth Seeker", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Follower Torch", "Follower Torch", DS3LocationCategory.SHIELD), - DS3LocationData("PW: Follower Shield", "Follower Shield", DS3LocationCategory.SHIELD), - DS3LocationData("PW: Follower Sabre", "Follower Sabre", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Snap Freeze", "Snap Freeze", DS3LocationCategory.SPELL), - DS3LocationData("PW: Floating Chaos", "Floating Chaos", DS3LocationCategory.SPELL), - DS3LocationData("PW: Pyromancer's Parting Flame", "Pyromancer's Parting Flame", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Vilhelm's Helm", "Vilhelm's Helm", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Vilhelm's Armor", "Vilhelm's Armor", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Vilhelm's Gauntlets", "Vilhelm's Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Vilhelm's Leggings", "Vilhelm's Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("PW: Valorheart", "Valorheart", DS3LocationCategory.WEAPON), - DS3LocationData("PW: Champion's Bones", "Champion's Bones", DS3LocationCategory.MISC), - DS3LocationData("PW: Soul of Sister Friede", "Soul of Sister Friede", DS3LocationCategory.BOSS), - DS3LocationData("PW: Chillbite Ring", "Chillbite Ring", DS3LocationCategory.RING), + "Painted World of Ariandel (After Contraption)": [ + DS3LocationData("PW2: Soul of Sister Friede", "Soul of Sister Friede", prominent=True, + boss=True), + DS3LocationData("PW2: Titanite Slab - boss drop", "Titanite Slab", + static='11,0:50004700::', + boss=True), # One-time drop after Friede Phase 2 + DS3LocationData("PW2: Floating Chaos - NPC drop", "Floating Chaos", hostile_npc=True, + hidden=True), # Livid Pyromancer Dunnel drop (requires ember) + DS3LocationData("PW2: Prism Stone - pass, tree by beginning", "Prism Stone x10"), + DS3LocationData("PW2: Titanite Chunk - pass, cliff overlooking bonfire", "Titanite Chunk"), + DS3LocationData("PW2: Titanite Chunk - pass, by kickable tree", "Titanite Chunk"), + DS3LocationData("PW2: Follower Shield - pass, far cliffside", "Follower Shield"), + DS3LocationData("PW2: Large Titanite Shard - pass, just before B1", + "Large Titanite Shard x2"), + DS3LocationData("PW2: Quakestone Hammer - pass, side path near B1", "Quakestone Hammer"), + DS3LocationData("PW2: Ember - pass, central alcove", "Ember"), + DS3LocationData("PW2: Large Titanite Shard - pass, far side path", + "Large Titanite Shard x2"), + DS3LocationData("PW2: Soul of a Crestfallen Knight - pit edge #1", + "Soul of a Crestfallen Knight"), + DS3LocationData("PW2: Soul of a Crestfallen Knight - pit edge #2", + "Soul of a Crestfallen Knight"), + DS3LocationData("PW2: Large Soul of a Crestfallen Knight - pit, by tree", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("PW2: Earth Seeker - pit cave", "Earth Seeker"), + DS3LocationData("PW2: Follower Torch - pass, far side path", "Follower Torch"), + DS3LocationData("PW2: Dung Pie - B1", "Dung Pie x2"), + DS3LocationData("PW2: Vilhelm's Helm", "Vilhelm's Helm"), + DS3LocationData("PW2: Vilhelm's Armor - B2, along wall", "Vilhelm's Armor"), + DS3LocationData("PW2: Vilhelm's Gauntlets - B2, along wall", "Vilhelm's Gauntlets"), + DS3LocationData("PW2: Vilhelm's Leggings - B2, along wall", "Vilhelm's Leggings"), + DS3LocationData("PW2: Blood Gem - B2, center", "Blood Gem"), + DS3LocationData("PW2: Pyromancer's Parting Flame - rotunda", + "Pyromancer's Parting Flame", hidden=True), # Behind illusory wall + DS3LocationData("PW2: Homeward Bone - rotunda", "Homeward Bone x2", + hidden=True), # Behind illusory wall + DS3LocationData("PW2: Twinkling Titanite - B3, lizard #1", "Twinkling Titanite", + lizard=True), + DS3LocationData("PW2: Twinkling Titanite - B3, lizard #2", "Twinkling Titanite", + lizard=True), + + # Corvian Settler after killing Friede + DS3LocationData("PW1: Titanite Slab - Corvian", "Titanite Slab", npc=True), + + # Shrine Handmaid after killing Sister Friede + DS3LocationData("FS: Ordained Hood - shop after killing PW2 boss", "Ordained Hood", + boss=True, shop=True), + DS3LocationData("FS: Ordained Dress - shop after killing PW2 boss", "Ordained Dress", + boss=True, shop=True), + DS3LocationData("FS: Ordained Trousers - shop after killing PW2 boss", "Ordained Trousers", + boss=True, shop=True), ], "Dreg Heap": [ - DS3LocationData("DH: Loincloth", "Loincloth", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Aquamarine Dagger", "Aquamarine Dagger", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Murky Hand Scythe", "Murky Hand Scythe", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Murky Longstaff", "Murky Longstaff", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Great Soul Dregs", "Great Soul Dregs", DS3LocationCategory.SPELL), - DS3LocationData("DH: Lothric War Banner", "Lothric War Banner", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Projected Heal", "Projected Heal", DS3LocationCategory.SPELL), - DS3LocationData("DH: Desert Pyromancer Hood", "Desert Pyromancer Hood", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Desert Pyromancer Garb", "Desert Pyromancer Garb", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Desert Pyromancer Gloves", "Desert Pyromancer Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Desert Pyromancer Skirt", "Desert Pyromancer Skirt", DS3LocationCategory.ARMOR), - DS3LocationData("DH: Giant Door Shield", "Giant Door Shield", DS3LocationCategory.SHIELD), - DS3LocationData("DH: Herald Curved Greatsword", "Herald Curved Greatsword", DS3LocationCategory.WEAPON), - DS3LocationData("DH: Flame Fan", "Flame Fan", DS3LocationCategory.SPELL), - DS3LocationData("DH: Soul of the Demon Prince", "Soul of the Demon Prince", DS3LocationCategory.BOSS), - DS3LocationData("DH: Small Envoy Banner", "Small Envoy Banner", DS3LocationCategory.KEY), - DS3LocationData("DH: Ring of Favor+3", "Ring of Favor+3", DS3LocationCategory.RING), - DS3LocationData("DH: Covetous Silver Serpent Ring+3", "Covetous Silver Serpent Ring+3", DS3LocationCategory.RING), - DS3LocationData("DH: Ring of Steel Protection+3", "Ring of Steel Protection+3", DS3LocationCategory.RING), + DS3LocationData("DH: Soul of the Demon Prince", "Soul of the Demon Prince", + prominent=True, boss=True), + DS3LocationData("DH: Siegbräu - Lapp", "Siegbräu", missable=True, drop=True, + npc=True), # Lapp (quest or kill) + DS3LocationData("DH: Flame Fan - swamp upper, NPC drop", "Flame Fan", + hostile_npc=True), # Desert Pyromancer Zoey drop + DS3LocationData("DH: Ember - castle, behind spire", "Ember"), + DS3LocationData("DH: Soul of a Weary Warrior - castle overhang", "Soul of a Weary Warrior"), + DS3LocationData("DH: Titanite Chunk - castle, up stairs", "Titanite Chunk"), + DS3LocationData("DH: Aquamarine Dagger - castle, up stairs", "Aquamarine Dagger"), + DS3LocationData("DH: Twinkling Titanite - library, chandelier", "Twinkling Titanite"), + DS3LocationData("DH: Murky Hand Scythe - library, behind bookshelves", "Murky Hand Scythe"), + DS3LocationData("DH: Divine Blessing - library, after drop", "Divine Blessing"), + DS3LocationData("DH: Ring of Steel Protection+3 - ledge before church", + "Ring of Steel Protection+3"), + DS3LocationData("DH: Soul of a Crestfallen Knight - church, altar", + "Soul of a Crestfallen Knight"), + DS3LocationData("DH: Rusted Coin - behind fountain after church", "Rusted Coin x2"), + DS3LocationData("DH: Titanite Chunk - pantry, first room", "Titanite Chunk"), + DS3LocationData("DH: Murky Longstaff - pantry, last room", "Murky Longstaff"), + DS3LocationData("DH: Ember - pantry, behind crates just before upstairs", "Ember", + hidden=True), # Behind illusory wall + DS3LocationData("DH: Great Soul Dregs - pantry upstairs", "Great Soul Dregs", + hidden=True), # Behind illusory wall + DS3LocationData("DH: Covetous Silver Serpent Ring+3 - pantry upstairs, drop down", + "Covetous Silver Serpent Ring+3", hidden=True), # Behind illusory wall + DS3LocationData("DH: Titanite Chunk - path from church, by pillar", "Titanite Chunk"), + DS3LocationData("DH: Homeward Bone - end of path from church", "Homeward Bone x3"), + DS3LocationData("DH: Lightning Urn - wall outside church", "Lightning Urn x4"), + DS3LocationData("DH: Projected Heal - parapets balcony", "Projected Heal"), + DS3LocationData("DH: Large Soul of a Weary Warrior - parapets, hall", + "Large Soul of a Weary Warrior"), + DS3LocationData("DH: Lothric War Banner - parapets, end of hall", "Lothric War Banner"), + DS3LocationData("DH: Titanite Scale - library, back of room", "Titanite Scale"), + DS3LocationData("DH: Black Firebomb - ruins, up windmill from bonfire", "Black Firebomb x4"), + DS3LocationData("DH: Titanite Chunk - ruins, path from bonfire", "Titanite Chunk"), + DS3LocationData("DH: Twinkling Titanite - ruins, root near bonfire", "Twinkling Titanite"), + DS3LocationData("DH: Desert Pyromancer Garb - ruins, by shack near cliff", + "Desert Pyromancer Garb"), + DS3LocationData("DH: Titanite Chunk - ruins, by far shack", "Titanite Chunk x2"), + DS3LocationData("DH: Giant Door Shield - ruins, path below far shack", "Giant Door Shield"), + DS3LocationData("DH: Ember - ruins, alcove before swamp", "Ember"), + DS3LocationData("DH: Desert Pyromancer Gloves - swamp, far right", + "Desert Pyromancer Gloves"), + DS3LocationData("DH: Desert Pyromancer Skirt - swamp right, by roots", + "Desert Pyromancer Skirt"), + DS3LocationData("DH: Titanite Scale - swamp upper, drop and jump into tower", + "Titanite Scale"), + DS3LocationData("DH: Purple Moss Clump - swamp shack", "Purple Moss Clump x4"), + DS3LocationData("DH: Ring of Favor+3 - swamp right, up root", "Ring of Favor+3"), + DS3LocationData("DH: Titanite Chunk - swamp right, drop partway up root", "Titanite Chunk"), + DS3LocationData("DH: Large Soul of a Weary Warrior - swamp, under overhang", + "Large Soul of a Weary Warrior"), + DS3LocationData("DH: Titanite Slab - swamp, path under overhang", "Titanite Slab"), + DS3LocationData("DH: Titanite Chunk - swamp, along buildings", "Titanite Chunk"), + DS3LocationData("DH: Loincloth - swamp, left edge", "Loincloth"), + DS3LocationData("DH: Titanite Chunk - swamp, path to upper", "Titanite Chunk"), + DS3LocationData("DH: Large Soul of a Weary Warrior - swamp center", + "Large Soul of a Weary Warrior"), + DS3LocationData("DH: Harald Curved Greatsword - swamp left, under root", + "Harald Curved Greatsword"), + DS3LocationData("DH: Homeward Bone - swamp left, on root", "Homeward Bone"), + DS3LocationData("DH: Prism Stone - swamp upper, tunnel start", "Prism Stone x6"), + DS3LocationData("DH: Desert Pyromancer Hood - swamp upper, tunnel end", + "Desert Pyromancer Hood"), + DS3LocationData("DH: Twinkling Titanite - swamp upper, drop onto root", + "Twinkling Titanite", hidden=True), # Hidden fall + DS3LocationData("DH: Divine Blessing - swamp upper, building roof", "Divine Blessing"), + DS3LocationData("DH: Ember - ruins, alcove on cliff", "Ember", hidden=True), # Hidden fall + DS3LocationData("DH: Small Envoy Banner - boss drop", "Small Envoy Banner", + progression=True, boss=True), + DS3LocationData("DH: Twinkling Titanite - ruins, alcove on cliff, mob drop", + "Twinkling Titanite x2", drop=True, + hidden=True), # Hidden fall, also guaranteed drop from killing normal-looking pilgrim + DS3LocationData("DH: Twinkling Titanite - swamp upper, mob drop on roof", + "Twinkling Titanite x2", drop=True, + hidden=True), # Hidden fall, also guaranteed drop from killing normal-looking pilgrim + DS3LocationData("DH: Twinkling Titanite - path after church, mob drop", + "Twinkling Titanite x2", drop=True, + hidden=True), # Guaranteed drop from killing normal-looking pilgrim + + # Stone-humped Hag's shop + DS3LocationData("DH: Splitleaf Greatsword - shop", "Splitleaf Greatsword", shop=True), + DS3LocationData("DH: Divine Blessing - shop", "Divine Blessing", shop=True), + DS3LocationData("DH: Hidden Blessing - shop", "Hidden Blessing", shop=True), + DS3LocationData("DH: Rusted Gold Coin - shop", "Rusted Gold Coin", shop=True), + DS3LocationData("DH: Ember - shop", "Ember", shop=True), ], "Ringed City": [ - DS3LocationData("RC: Ruin Sentinel Helm", "Ruin Sentinel Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Ruin Sentinel Armor", "Ruin Sentinel Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Ruin Sentinel Gauntlets", "Ruin Sentinel Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Ruin Sentinel Leggings", "Ruin Sentinel Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Veil", "Black Witch Veil", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Hat", "Black Witch Hat", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Garb", "Black Witch Garb", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Wrappings", "Black Witch Wrappings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Black Witch Trousers", "Black Witch Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RC: White Preacher Head", "White Preacher Head", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Havel's Ring+3", "Havel's Ring+3", DS3LocationCategory.RING), - DS3LocationData("RC: Ringed Knight Spear", "Ringed Knight Spear", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Dragonhead Shield", "Dragonhead Shield", DS3LocationCategory.SHIELD), - DS3LocationData("RC: Ringed Knight Straight Sword", "Ringed Knight Straight Sword", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Preacher's Right Arm", "Preacher's Right Arm", DS3LocationCategory.WEAPON), - DS3LocationData("RC: White Birch Bow", "White Birch Bow", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Church Guardian Shiv", "Church Guardian Shiv", DS3LocationCategory.MISC), - DS3LocationData("RC: Dragonhead Greatshield", "Dragonhead Greatshield", DS3LocationCategory.SHIELD), - DS3LocationData("RC: Ringed Knight Paired Greatswords", "Ringed Knight Paired Greatswords", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Shira's Crown", "Shira's Crown", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Shira's Armor", "Shira's Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Shira's Gloves", "Shira's Gloves", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Shira's Trousers", "Shira's Trousers", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Crucifix of the Mad King", "Crucifix of the Mad King", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Sacred Chime of Filianore", "Sacred Chime of Filianore", DS3LocationCategory.WEAPON), - DS3LocationData("RC: Iron Dragonslayer Helm", "Iron Dragonslayer Helm", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Iron Dragonslayer Armor", "Iron Dragonslayer Armor", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Iron Dragonslayer Gauntlets", "Iron Dragonslayer Gauntlets", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Iron Dragonslayer Leggings", "Iron Dragonslayer Leggings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Lightning Arrow", "Lightning Arrow", DS3LocationCategory.SPELL), - DS3LocationData("RC: Ritual Spear Fragment", "Ritual Spear Fragment", DS3LocationCategory.MISC), - DS3LocationData("RC: Antiquated Plain Garb", "Antiquated Plain Garb", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Violet Wrappings", "Violet Wrappings", DS3LocationCategory.ARMOR), - DS3LocationData("RC: Soul of Darkeater Midir", "Soul of Darkeater Midir", DS3LocationCategory.BOSS), - DS3LocationData("RC: Soul of Slave Knight Gael", "Soul of Slave Knight Gael", DS3LocationCategory.BOSS), - DS3LocationData("RC: Blood of the Dark Soul", "Blood of the Dark Soul", DS3LocationCategory.KEY), - DS3LocationData("RC: Chloranthy Ring+3", "Chloranthy Ring+3", DS3LocationCategory.RING), - DS3LocationData("RC: Covetous Gold Serpent Ring+3", "Covetous Gold Serpent Ring+3", DS3LocationCategory.RING), - DS3LocationData("RC: Ring of the Evil Eye+3", "Ring of the Evil Eye+3", DS3LocationCategory.RING), - DS3LocationData("RC: Wolf Ring+3", "Wolf Ring+3", DS3LocationCategory.RING), + DS3LocationData("RC: Titanite Slab - mid boss drop", "Titanite Slab", + prominent=True, boss=True), # Halflight drop, only once + DS3LocationData("RC: Filianore's Spear Ornament - mid boss drop", + "Filianore's Spear Ornament"), + DS3LocationData("RC: Soul of Darkeater Midir", "Soul of Darkeater Midir", prominent=True, + boss=True), + DS3LocationData("RC: Sacred Chime of Filianore - ashes, NPC drop", + "Sacred Chime of Filianore", + hostile_npc=True), # Shira (kill or quest) + DS3LocationData("RC: Titanite Slab - ashes, NPC drop", "Titanite Slab", + hostile_npc=True), # Shira (kill or quest) + DS3LocationData("RC: Crucifix of the Mad King - ashes, NPC drop", + "Crucifix of the Mad King", hostile_npc=True), # Shira drop + DS3LocationData("RC: Ledo's Great Hammer - streets high, opposite building, NPC drop", + "Ledo's Great Hammer", hostile_npc=True, + missable=True), # Silver Knight Ledo drop, doesn't invade once Halflight + # is defeated + DS3LocationData("RC: Wolf Ring+3 - street gardens, NPC drop", "Wolf Ring+3", + hostile_npc=True, + missable=True), # Alva drop, doesn't invade once Halflight is defeated + DS3LocationData("RC: Blindfold Mask - grave, NPC drop", "Blindfold Mask", + hostile_npc=True), # Moaning Knight drop + DS3LocationData("RC: Titanite Scale - wall top, behind spawn", "Titanite Scale"), # wrong + DS3LocationData("RC: Ruin Helm - wall top, under stairs to bonfire", "Ruin Helm"), + DS3LocationData("RC: Ruin Armor - wall top, under stairs to bonfire", "Ruin Armor"), + DS3LocationData("RC: Ruin Gauntlets - wall top, under stairs to bonfire", "Ruin Gauntlets"), + DS3LocationData("RC: Ruin Leggings - wall top, under stairs to bonfire", "Ruin Leggings"), + DS3LocationData("RC: Budding Green Blossom - wall top, in flower cluster", + "Budding Green Blossom x2"), + DS3LocationData("RC: Titanite Chunk - wall top, among graves", "Titanite Chunk x2"), + DS3LocationData("RC: Ember - wall top, by statue", "Ember"), + DS3LocationData("RC: Budding Green Blossom - wall top, flowers by stairs", + "Budding Green Blossom x2"), + DS3LocationData("RC: Hidden Blessing - wall top, tomb under platform", "Hidden Blessing", + hidden=True), # hidden fall + DS3LocationData("RC: Soul of a Crestfallen Knight - wall top, under drop", + "Soul of a Crestfallen Knight", hidden=True), # hidden fall + DS3LocationData("RC: Large Soul of a Weary Warrior - wall top, right of small tomb", + "Large Soul of a Weary Warrior"), + DS3LocationData("RC: Ember - wall upper, balcony", "Ember"), + DS3LocationData("RC: Purging Stone - wall top, by door to upper", "Purging Stone x2"), + DS3LocationData("RC: Hollow Gem - wall upper, path to tower", "Hollow Gem"), + DS3LocationData("RC: Titanite Chunk - wall upper, courtyard alcove", "Titanite Chunk"), + DS3LocationData("RC: Twinkling Titanite - wall tower, jump from chandelier", + "Twinkling Titanite", hidden=True), # Hidden fall + DS3LocationData("RC: Shriving Stone - wall tower, bottom floor center", "Shriving Stone"), + DS3LocationData("RC: Shira's Crown - Shira's room after killing ashes NPC", "Shira's Crown", + hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Shira's Armor - Shira's room after killing ashes NPC", "Shira's Armor", + hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Shira's Gloves - Shira's room after killing ashes NPC", + "Shira's Gloves", hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Shira's Trousers - Shira's room after killing ashes NPC", + "Shira's Trousers", hidden=True), # Have to return to a cleared area + DS3LocationData("RC: Mossfruit - streets near left, path to garden", "Mossfruit x2"), + DS3LocationData("RC: Large Soul of a Crestfallen Knight - streets, far stairs", + "Large Soul of a Crestfallen Knight"), + DS3LocationData("RC: Ringed Knight Spear - streets, down far right hall", + "Ringed Knight Spear"), + DS3LocationData("RC: Black Witch Hat - streets garden", "Black Witch Hat", + hostile_npc=True), # Alva + DS3LocationData("RC: Black Witch Garb - streets garden", "Black Witch Garb", + hostile_npc=True), # Alva + DS3LocationData("RC: Black Witch Wrappings - streets garden", "Black Witch Wrappings", + hostile_npc=True), # Alva + DS3LocationData("RC: Black Witch Trousers - streets garden", "Black Witch Trousers", + hostile_npc=True), # Alva + DS3LocationData("RC: Dragonhead Shield - streets monument, across bridge", + "Dragonhead Shield", hidden=True), # "Show Your Humanity" puzzle + DS3LocationData("RC: Titanite Chunk - streets, near left drop", "Titanite Chunk", + hidden=True), # Hidden fall + DS3LocationData("RC: Mossfruit - streets, far left alcove", "Mossfruit x2"), + DS3LocationData("RC: Large Soul of a Crestfallen Knight - streets monument, across bridge", + "Large Soul of a Crestfallen Knight", + hidden=True), # "Show Your Humanity" puzzle + DS3LocationData("RC: Covetous Gold Serpent Ring+3 - streets, by Lapp", + "Covetous Gold Serpent Ring+3"), + DS3LocationData("RC: Titanite Chunk - streets high, building opposite", "Titanite Chunk x2"), + DS3LocationData("RC: Dark Gem - swamp near, by stairs", "Dark Gem"), + DS3LocationData("RC: Prism Stone - swamp near, railing by bonfire", "Prism Stone x4"), + DS3LocationData("RC: Ringed Knight Straight Sword - swamp near, tower on peninsula", + "Ringed Knight Straight Sword"), + DS3LocationData("RC: Havel's Ring+3 - streets high, drop from building opposite", + "Havel's Ring+3", hidden=True), # Hidden fall + DS3LocationData("RC: Titanite Chunk - swamp near left, by spire top", "Titanite Chunk"), + DS3LocationData("RC: Twinkling Titanite - swamp near left", "Twinkling Titanite"), + DS3LocationData("RC: Soul of a Weary Warrior - swamp center", "Soul of a Weary Warrior"), + DS3LocationData("RC: Preacher's Right Arm - swamp near right, by tower", + "Preacher's Right Arm"), + DS3LocationData("RC: Rubbish - swamp far, by crystal", "Rubbish"), + DS3LocationData("RC: Titanite Chunk - swamp near right, behind rock", + "Titanite Chunk"), + DS3LocationData("RC: Black Witch Veil - swamp near right, by sunken church", + "Black Witch Veil"), + DS3LocationData("RC: Twinkling Titanite - swamp near right, on sunken church", + "Twinkling Titanite"), + DS3LocationData("RC: Soul of a Crestfallen Knight - swamp near left, nook", + "Soul of a Crestfallen Knight"), + DS3LocationData("RC: White Preacher Head - swamp near, nook right of stairs", + "White Preacher Head"), + DS3LocationData("RC: Titanite Scale - swamp far, by miniboss", "Titanite Scale"), + DS3LocationData("RC: Dragonhead Greatshield - lower cliff, under bridge", + "Dragonhead Greatshield"), + DS3LocationData("RC: Titanite Scale - lower cliff, path under bridge", "Titanite Scale x2"), + DS3LocationData("RC: Rubbish - lower cliff, middle", "Rubbish"), + DS3LocationData("RC: Large Soul of a Weary Warrior - lower cliff, end", + "Large Soul of a Weary Warrior"), + DS3LocationData("RC: Titanite Scale - lower cliff, first alcove", "Titanite Scale x2"), + DS3LocationData("RC: Titanite Scale - lower cliff, lower path", "Titanite Scale"), + DS3LocationData("RC: Lightning Gem - grave, room after first drop", "Lightning Gem"), + DS3LocationData("RC: Blessed Gem - grave, down lowest stairs", "Blessed Gem"), + DS3LocationData("RC: Simple Gem - grave, up stairs after first drop", "Simple Gem"), + DS3LocationData("RC: Large Soul of a Weary Warrior - wall lower, past two illusory walls", + "Large Soul of a Weary Warrior", hidden=True), + DS3LocationData("RC: Lightning Arrow - wall lower, past three illusory walls", + "Lightning Arrow"), + DS3LocationData("RC: Chloranthy Ring+3 - wall hidden, drop onto statue", + "Chloranthy Ring+3", hidden=True), # Hidden fall + DS3LocationData("RC: Ember - wall hidden, statue room", "Ember"), + DS3LocationData("RC: Filianore's Spear Ornament - wall hidden, by ladder", + "Filianore's Spear Ornament"), + DS3LocationData("RC: Antiquated Plain Garb - wall hidden, before boss", + "Antiquated Plain Garb"), + DS3LocationData("RC: Violet Wrappings - wall hidden, before boss", "Violet Wrappings"), + DS3LocationData("RC: Soul of a Weary Warrior - lower cliff, by first alcove", + "Soul of a Weary Warrior"), + DS3LocationData("RC: Twinkling Titanite - church path, left of boss door", + "Twinkling Titanite x2"), + DS3LocationData("RC: Budding Green Blossom - church path", "Budding Green Blossom x3"), + DS3LocationData("RC: Titanite Chunk - swamp center, peninsula edge", "Titanite Chunk"), + DS3LocationData("RC: Large Soul of a Weary Warrior - swamp center, by peninsula", + "Large Soul of a Weary Warrior"), + DS3LocationData("RC: Soul of a Weary Warrior - swamp right, by sunken church", + "Soul of a Weary Warrior"), + DS3LocationData("RC: Titanite Scale - upper cliff, bridge", "Titanite Scale"), + DS3LocationData("RC: Soul of a Crestfallen Knight - swamp far, behind crystal", + "Soul of a Crestfallen Knight"), + DS3LocationData("RC: White Birch Bow - swamp far left, up hill", "White Birch Bow"), + DS3LocationData("RC: Titanite Chunk - swamp far left, up hill", "Titanite Chunk"), + DS3LocationData("RC: Young White Branch - swamp far left, by white tree #1", + "Young White Branch"), + DS3LocationData("RC: Young White Branch - swamp far left, by white tree #2", + "Young White Branch"), + DS3LocationData("RC: Young White Branch - swamp far left, by white tree #3", + "Young White Branch"), + DS3LocationData("RC: Ringed Knight Paired Greatswords - church path, mob drop", + "Ringed Knight Paired Greatswords", drop=True, + hidden=True), # Guaranteed drop from a normal-looking Ringed Knight + DS3LocationData("RC: Hidden Blessing - swamp center, mob drop", "Hidden Blessing", + drop=True, hidden=True), # Guaranteed drop from Judicator + DS3LocationData("RC: Divine Blessing - wall top, mob drop", "Divine Blessing", + drop=True, hidden=True), # Guaranteed drop from Judicator + DS3LocationData("RC: Divine Blessing - streets monument, mob drop", "Divine Blessing", + drop=True, + hidden=True), # Guaranteed drop from Judicator, "Show Your Humanity" puzzle + DS3LocationData("RC: Ring of the Evil Eye+3 - grave, mimic", "Ring of the Evil Eye+3", + mimic=True), + DS3LocationData("RC: Iron Dragonslayer Helm - swamp far, miniboss drop", + "Iron Dragonslayer Helm", miniboss=True), + DS3LocationData("RC: Iron Dragonslayer Armor - swamp far, miniboss drop", + "Iron Dragonslayer Armor", miniboss=True), + DS3LocationData("RC: Iron Dragonslayer Gauntlets - swamp far, miniboss drop", + "Iron Dragonslayer Gauntlets", miniboss=True), + DS3LocationData("RC: Iron Dragonslayer Leggings - swamp far, miniboss drop", + "Iron Dragonslayer Leggings", miniboss=True), + DS3LocationData("RC: Church Guardian Shiv - swamp far left, in building", + "Church Guardian Shiv"), + DS3LocationData("RC: Spears of the Church - hidden boss drop", "Spears of the Church", + boss=True), # Midir drop + DS3LocationData("RC: Ritual Spear Fragment - church path", "Ritual Spear Fragment"), + DS3LocationData("RC: Titanite Scale - swamp far, lagoon entrance", "Titanite Scale"), + DS3LocationData("RC: Twinkling Titanite - grave, lizard past first drop", + "Twinkling Titanite", lizard=True), + DS3LocationData("RC: Titanite Scale - grave, lizard past first drop", "Titanite Scale", + lizard=True), + DS3LocationData("RC: Twinkling Titanite - streets high, lizard", "Twinkling Titanite x2", + lizard=True), + DS3LocationData("RC: Titanite Scale - wall lower, lizard", "Titanite Scale", lizard=True), + DS3LocationData("RC: Twinkling Titanite - wall top, lizard on side path", + "Twinkling Titanite", lizard=True), + DS3LocationData("RC: Soul of Slave Knight Gael", "Soul of Slave Knight Gael", + prominent=True, boss=True), + DS3LocationData("RC: Blood of the Dark Soul - end boss drop", "Blood of the Dark Soul"), + DS3LocationData("RC: Titanite Slab - ashes, mob drop", "Titanite Slab", + drop=True, + hidden=True), # Guaranteed drop from normal-looking Ringed Knight + + # Lapp + DS3LocationData("RC: Siegbräu - Lapp", "Siegbräu", missable=True, + npc=True), # Lapp (quest) + # Quest or Shrine Handmaiden after death + DS3LocationData("RC: Lapp's Helm - Lapp", "Lapp's Helm", npc=True, shop=True), + DS3LocationData("RC: Lapp's Armor - Lapp", "Lapp's Armor", npc=True, shop=True), + DS3LocationData("RC: Lapp's Gauntlets - Lapp", "Lapp's Gauntlets", npc=True, shop=True), + DS3LocationData("RC: Lapp's Leggings - Lapp", "Lapp's Leggings", npc=True, shop=True), + ], + + # Unlockable shops. We only bother creating a "region" for these for shops that are locked + # behind keys and always have items available either through the shop or through the NPC's + # ashes. + "Greirat's Shop": [ + DS3LocationData("FS: Blue Tearstone Ring - Greirat", "Blue Tearstone Ring", + static='01,0:50006120::', npc=True), + DS3LocationData("FS: Ember - Greirat", "Ember", static="99,0:-1:110000,120000,70000110:", + shop=True, npc=True), + + # Undead Settlement rewards + DS3LocationData("FS: Divine Blessing - Greirat from US", "Divine Blessing", + static='99,0:-1:110000,120000,70000150,70000175:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Ember - Greirat from US", "Ember", + static='99,0:-1:110000,120000,70000150,70000175:', missable=True, + shop=True, npc=True), + + # Irityhll rewards + DS3LocationData("FS: Divine Blessing - Greirat from IBV", "Divine Blessing", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Hidden Blessing - Greirat from IBV", "Hidden Blessing", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Titanite Scale - Greirat from IBV", "Titanite Scale", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + DS3LocationData("FS: Twinkling Titanite - Greirat from IBV", "Twinkling Titanite", + static='99,0:-1:110000,120000,70000151,70000176:', missable=True, + shop=True, npc=True), + + # Lothric rewards (from Shrine Handmaid) + DS3LocationData("FS: Ember - shop for Greirat's Ashes", "Twinkling Titanite", + static='99,0:-1:110000,120000,70000152,70000177:', missable=True, + shop=True, npc=True), + ], + "Karla's Shop": [ + DS3LocationData("FS: Affinity - Karla", "Affinity", shop=True, npc=True), + DS3LocationData("FS: Dark Edge - Karla", "Dark Edge", shop=True, npc=True), + + # Quelana Pyromancy Tome + DS3LocationData("FS: Firestorm - Karla for Quelana Tome", "Firestorm", missable=True, + shop=True, npc=True), + DS3LocationData("FS: Rapport - Karla for Quelana Tome", "Rapport", missable=True, + shop=True, npc=True), + DS3LocationData("FS: Fire Whip - Karla for Quelana Tome", "Fire Whip", missable=True, + shop=True, npc=True), + + # Grave Warden Pyromancy Tome + DS3LocationData("FS: Black Flame - Karla for Grave Warden Tome", "Black Flame", + missable=True, shop=True, npc=True), + DS3LocationData("FS: Black Fire Orb - Karla for Grave Warden Tome", "Black Fire Orb", + missable=True, shop=True, npc=True), + + # Deep Braille Divine Tome. This can also be given to Irina, but it'll fail her quest + DS3LocationData("FS: Gnaw - Karla for Deep Braille Tome", "Gnaw", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Deep Protection - Karla for Deep Braille Tome", "Deep Protection", + missable=True, npc=True, shop=True), + + # Londor Braille Divine Tome. This can also be given to Irina, but it'll fail her quest + DS3LocationData("FS: Vow of Silence - Karla for Londor Tome", "Vow of Silence", + missable=True, npc=True, shop=True), + DS3LocationData("FS: Dark Blade - Karla for Londor Tome", "Dark Blade", missable=True, + npc=True, shop=True), + DS3LocationData("FS: Dead Again - Karla for Londor Tome", "Dead Again", missable=True, + npc=True, shop=True), + + # Drops on death. Missable because the player would have to decide between killing her or + # seeing everything she sells. + DS3LocationData("FS: Karla's Pointed Hat - kill Karla", "Karla's Pointed Hat", + static='07,0:50006150::', missable=True, drop=True, npc=True), + DS3LocationData("FS: Karla's Coat - kill Karla", "Karla's Coat", + static='07,0:50006150::', missable=True, drop=True, npc=True), + DS3LocationData("FS: Karla's Gloves - kill Karla", "Karla's Gloves", + static='07,0:50006150::', missable=True, drop=True, npc=True), + DS3LocationData("FS: Karla's Trousers - kill Karla", "Karla's Trousers", + static='07,0:50006150::', missable=True, drop=True, npc=True), ], +} + +for i, region in enumerate(region_order): + for location in location_tables[region]: location.region_value = i + +for region in [ + "Painted World of Ariandel (Before Contraption)", + "Painted World of Ariandel (After Contraption)", + "Dreg Heap", + "Ringed City", +]: + for location in location_tables[region]: + location.dlc = True - # Progressive - "Progressive Items 1": [] + - # Upgrade materials - [DS3LocationData(f"Titanite Shard #{i + 1}", "Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(26)] + - [DS3LocationData(f"Large Titanite Shard #{i + 1}", "Large Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(28)] + - [DS3LocationData(f"Titanite Slab #{i + 1}", "Titanite Slab", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Twinkling Titanite #{i + 1}", "Twinkling Titanite", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(15)], - - "Progressive Items 2": [] + - # Items - [DS3LocationData(f"Green Blossom #{i + 1}", "Green Blossom", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Firebomb #{i + 1}", "Firebomb", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Alluring Skull #{i + 1}", "Alluring Skull", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Undead Hunter Charm #{i + 1}", "Undead Hunter Charm", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Duel Charm #{i + 1}", "Duel Charm", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Throwing Knife #{i + 1}", "Throwing Knife", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Gold Pine Resin #{i + 1}", "Gold Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Charcoal Pine Resin #{i + 1}", "Charcoal Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Human Pine Resin #{i + 1}", "Human Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Carthus Rouge #{i + 1}", "Carthus Rouge", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Pale Pine Resin #{i + 1}", "Pale Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Charcoal Pine Bundle #{i + 1}", "Charcoal Pine Bundle", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Rotten Pine Resin #{i + 1}", "Rotten Pine Resin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Homeward Bone #{i + 1}", "Homeward Bone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(16)] + - [DS3LocationData(f"Pale Tongue #{i + 1}", "Pale Tongue", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Rusted Coin #{i + 1}", "Rusted Coin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Rusted Gold Coin #{i + 1}", "Rusted Gold Coin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Ember #{i + 1}", "Ember", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(45)], - - "Progressive Items 3": [] + - # Souls & Bulk Upgrade Materials - [DS3LocationData(f"Fading Soul #{i + 1}", "Fading Soul", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Soul of a Deserted Corpse #{i + 1}", "Soul of a Deserted Corpse", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Large Soul of a Deserted Corpse #{i + 1}", "Large Soul of a Deserted Corpse", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Soul of an Unknown Traveler #{i + 1}", "Soul of an Unknown Traveler", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Large Soul of an Unknown Traveler #{i + 1}", "Large Soul of an Unknown Traveler", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Soul of a Nameless Soldier #{i + 1}", "Soul of a Nameless Soldier", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Large Soul of a Nameless Soldier #{i + 1}", "Large Soul of a Nameless Soldier", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Soul of a Weary Warrior #{i + 1}", "Soul of a Weary Warrior", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Soul of a Crestfallen Knight #{i + 1}", "Soul of a Crestfallen Knight", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Titanite Chunk #{i + 1}", "Titanite Chunk", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(22)] + - [DS3LocationData(f"Titanite Scale #{i + 1}", "Titanite Scale", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(29)], - - "Progressive Items 4": [] + - # Gems & Random Consumables - [DS3LocationData(f"Ring of Sacrifice #{i + 1}", "Ring of Sacrifice", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(4)] + - [DS3LocationData(f"Divine Blessing #{i + 1}", "Divine Blessing", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Hidden Blessing #{i + 1}", "Hidden Blessing", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Budding Green Blossom #{i + 1}", "Budding Green Blossom", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Bloodred Moss Clump #{i + 1}", "Bloodred Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Purple Moss Clump #{i + 1}", "Purple Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Blooming Purple Moss Clump #{i + 1}", "Blooming Purple Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Purging Stone #{i + 1}", "Purging Stone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Rime-blue Moss Clump #{i + 1}", "Rime-blue Moss Clump", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Repair Powder #{i + 1}", "Repair Powder", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Kukri #{i + 1}", "Kukri", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Lightning Urn #{i + 1}", "Lightning Urn", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Rubbish #{i + 1}", "Rubbish", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Blue Bug Pellet #{i + 1}", "Blue Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Red Bug Pellet #{i + 1}", "Red Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Yellow Bug Pellet #{i + 1}", "Yellow Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Black Bug Pellet #{i + 1}", "Black Bug Pellet", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Heavy Gem #{i + 1}", "Heavy Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Sharp Gem #{i + 1}", "Sharp Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Refined Gem #{i + 1}", "Refined Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Crystal Gem #{i + 1}", "Crystal Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Simple Gem #{i + 1}", "Simple Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Fire Gem #{i + 1}", "Fire Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Chaos Gem #{i + 1}", "Chaos Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Lightning Gem #{i + 1}", "Lightning Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Deep Gem #{i + 1}", "Deep Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Dark Gem #{i + 1}", "Dark Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Poison Gem #{i + 1}", "Poison Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Blood Gem #{i + 1}", "Blood Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Raw Gem #{i + 1}", "Raw Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Blessed Gem #{i + 1}", "Blessed Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Hollow Gem #{i + 1}", "Hollow Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Shriving Stone #{i + 1}", "Shriving Stone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)], - - "Progressive Items DLC": [] + - # Upgrade materials - [DS3LocationData(f"Large Titanite Shard ${i + 1}", "Large Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Titanite Chunk ${i + 1}", "Titanite Chunk", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(15)] + - [DS3LocationData(f"Titanite Slab ${i + 1}", "Titanite Slab", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Twinkling Titanite ${i + 1}", "Twinkling Titanite", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Titanite Scale ${i + 1}", "Titanite Scale", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(11)] + - - - # Items - [DS3LocationData(f"Homeward Bone ${i + 1}", "Homeward Bone", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Rusted Coin ${i + 1}", "Rusted Coin", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - [DS3LocationData(f"Ember ${i + 1}", "Ember", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(10)] + - - # Souls - [DS3LocationData(f"Large Soul of an Unknown Traveler ${i + 1}", "Large Soul of an Unknown Traveler", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(9)] + - [DS3LocationData(f"Soul of a Weary Warrior ${i + 1}", "Soul of a Weary Warrior", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(5)] + - [DS3LocationData(f"Large Soul of a Weary Warrior ${i + 1}", "Large Soul of a Weary Warrior", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Soul of a Crestfallen Knight ${i + 1}", "Soul of a Crestfallen Knight", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(6)] + - [DS3LocationData(f"Large Soul of a Crestfallen Knight ${i + 1}", "Large Soul of a Crestfallen Knight", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] + - - # Gems - [DS3LocationData(f"Dark Gem ${i + 1}", "Dark Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Blood Gem ${i + 1}", "Blood Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] + - [DS3LocationData(f"Blessed Gem ${i + 1}", "Blessed Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] + - [DS3LocationData(f"Hollow Gem ${i + 1}", "Hollow Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)], - - "Progressive Items Health": [] + - # Healing - [DS3LocationData(f"Estus Shard #{i + 1}", "Estus Shard", DS3LocationCategory.HEALTH) for i in range(11)] + - [DS3LocationData(f"Undead Bone Shard #{i + 1}", "Undead Bone Shard", DS3LocationCategory.HEALTH) for i in range(10)], +for region in [ + "Firelink Shrine Bell Tower", + "Greirat's Shop", + "Karla's Shop" +]: + for location in location_tables[region]: + location.conditional = True + +location_name_groups: Dict[str, Set[str]] = { + # We could insert these locations automatically with setdefault(), but we set them up explicitly + # instead so we can choose the ordering. + "Prominent": set(), + "Progression": set(), + "Boss Rewards": set(), + "Miniboss Rewards": set(), + "Mimic Rewards": set(), + "Hostile NPC Rewards": set(), + "Friendly NPC Rewards": set(), + "Small Crystal Lizards": set(), + "Upgrade": set(), + "Small Souls": set(), + "Boss Souls": set(), + "Unique": set(), + "Healing": set(), + "Miscellaneous": set(), + "Hidden": set(), + "Weapons": set(), + "Shields": set(), + "Armor": set(), + "Rings": set(), + "Spells": set(), +} + +location_descriptions = { + "Prominent": "A small number of locations that are in very obvious locations. Mostly boss " + \ + "drops. Ideal for setting as priority locations.", + "Progression": "Locations that contain items in vanilla which unlock other locations.", + "Boss Rewards": "Boss drops. Does not include soul transfusions or shop items.", + "Miniboss Rewards": "Miniboss drops. Only includes enemies considered minibosses by the " + \ + "enemy randomizer.", + "Mimic Rewards": "Drops from enemies that are mimics in vanilla.", + "Hostile NPC Rewards": "Drops from NPCs that are hostile to you. This includes scripted " + \ + "invaders and initially-friendly NPCs that must be fought as part of their quest.", + "Friendly NPC Rewards": "Items given by friendly NPCs as part of their quests or from " + \ + "non-violent interaction.", + "Upgrade": "Locations that contain upgrade items in vanilla, including titanite, gems, and " + \ + "Shriving Stones.", + "Small Souls": "Locations that contain soul items in vanilla, not including boss souls.", + "Boss Souls": "Locations that contain boss souls in vanilla, as well as Soul of Rosaria.", + "Unique": "Locations that contain items in vanilla that are unique per NG cycle, such as " + \ + "scrolls, keys, ashes, and so on. Doesn't cover equipment, spells, or souls.", + "Healing": "Locations that contain Undead Bone Shards and Estus Shards in vanilla.", + "Miscellaneous": "Locations that contain generic stackable items in vanilla, such as arrows, " + + "firebombs, buffs, and so on.", + "Hidden": "Locations that are particularly difficult to find, such as behind illusory " + \ + "walls, down hidden drops, and so on. Does not include large locations like Untended " + \ + "Graves or Archdragon Peak.", + "Weapons": "Locations that contain weapons in vanilla.", + "Shields": "Locations that contain shields in vanilla.", + "Armor": "Locations that contain armor in vanilla.", + "Rings": "Locations that contain rings in vanilla.", + "Spells": "Locations that contain spells in vanilla.", } location_dictionary: Dict[str, DS3LocationData] = {} -for location_table in location_tables.values(): +for location_name, location_table in location_tables.items(): location_dictionary.update({location_data.name: location_data for location_data in location_table}) + + for location_data in location_table: + if not location_data.is_event: + for group_name in location_data.location_groups(): + location_name_groups[group_name].add(location_data.name) + + # Allow entire locations to be added to location sets. + if not location_name.endswith(" Shop"): + location_name_groups[location_name] = set([ + location_data.name for location_data in location_table + if not location_data.is_event + ]) + +location_name_groups["Painted World of Ariandel"] = ( + location_name_groups["Painted World of Ariandel (Before Contraption)"] + .union(location_name_groups["Painted World of Ariandel (After Contraption)"]) +) +del location_name_groups["Painted World of Ariandel (Before Contraption)"] +del location_name_groups["Painted World of Ariandel (After Contraption)"] + +location_name_groups["DLC"] = ( + location_name_groups["Painted World of Ariandel"] + .union(location_name_groups["Dreg Heap"]) + .union(location_name_groups["Ringed City"]) +) diff --git a/worlds/dark_souls_3/Options.py b/worlds/dark_souls_3/Options.py index df0bb953b8d..ad81dd9f7b8 100644 --- a/worlds/dark_souls_3/Options.py +++ b/worlds/dark_souls_3/Options.py @@ -1,80 +1,78 @@ -import typing +from dataclasses import dataclass +import json +from typing import Any, Dict -from Options import Toggle, DefaultOnToggle, Option, Range, Choice, ItemDict, DeathLink +from Options import Choice, DeathLink, DefaultOnToggle, ExcludeLocations, NamedRange, OptionDict, \ + OptionGroup, PerGameCommonOptions, Range, Removed, Toggle +## Game Options -class RandomizeWeaponLocations(DefaultOnToggle): - """Randomizes weapons (+76 locations)""" - display_name = "Randomize Weapon Locations" - -class RandomizeShieldLocations(DefaultOnToggle): - """Randomizes shields (+24 locations)""" - display_name = "Randomize Shield Locations" - - -class RandomizeArmorLocations(DefaultOnToggle): - """Randomizes armor pieces (+97 locations)""" - display_name = "Randomize Armor Locations" - - -class RandomizeRingLocations(DefaultOnToggle): - """Randomizes rings (+49 locations)""" - display_name = "Randomize Ring Locations" - - -class RandomizeSpellLocations(DefaultOnToggle): - """Randomizes spells (+18 locations)""" - display_name = "Randomize Spell Locations" - - -class RandomizeKeyLocations(DefaultOnToggle): - """Randomizes items which unlock doors or bypass barriers""" - display_name = "Randomize Key Locations" +class EarlySmallLothricBanner(Choice): + """Force Small Lothric Banner into an early sphere in your world or across all worlds.""" + display_name = "Early Small Lothric Banner" + option_off = 0 + option_early_global = 1 + option_early_local = 2 + default = option_off -class RandomizeBossSoulLocations(DefaultOnToggle): - """Randomizes Boss Souls (+18 Locations)""" - display_name = "Randomize Boss Soul Locations" +class LateBasinOfVowsOption(Choice): + """Guarantee that you don't need to enter Lothric Castle until later in the run. + - **Off:** You may have to enter Lothric Castle and the areas beyond it immediately after High + Wall of Lothric. + - **After Small Lothric Banner:** You may have to enter Lothric Castle after Catacombs of + Carthus. + - **After Small Doll:** You won't have to enter Lothric Castle until after Irithyll of the + Boreal Valley. + """ + display_name = "Late Basin of Vows" + option_off = 0 + alias_false = 0 + option_after_small_lothric_banner = 1 + alias_true = 1 + option_after_small_doll = 2 -class RandomizeNPCLocations(Toggle): - """Randomizes friendly NPC drops (meaning you will probably have to kill them) (+14 locations)""" - display_name = "Randomize NPC Locations" +class LateDLCOption(Choice): + """Guarantee that you don't need to enter the DLC until later in the run. -class RandomizeMiscLocations(Toggle): - """Randomizes miscellaneous items (ashes, tomes, scrolls, etc.) to the pool. (+36 locations)""" - display_name = "Randomize Miscellaneous Locations" + - **Off:** You may have to enter the DLC after Catacombs of Carthus. + - **After Small Doll:** You may have to enter the DLC after Irithyll of the Boreal Valley. + - **After Basin:** You won't have to enter the DLC until after Lothric Castle. + """ + display_name = "Late DLC" + option_off = 0 + alias_false = 0 + option_after_small_doll = 1 + alias_true = 1 + option_after_basin = 2 -class RandomizeHealthLocations(Toggle): - """Randomizes health upgrade items. (+21 locations)""" - display_name = "Randomize Health Upgrade Locations" +class EnableDLCOption(Toggle): + """Include DLC locations, items, and enemies in the randomized pools. + To use this option, you must own both the "Ashes of Ariandel" and the "Ringed City" DLCs. + """ + display_name = "Enable DLC" -class RandomizeProgressiveLocationsOption(Toggle): - """Randomizes upgrade materials and consumables such as the titanite shards, firebombs, resin, etc... - Instead of specific locations, these are progressive, so Titanite Shard #1 is the first titanite shard - you pick up, regardless of whether it's from an enemy drop late in the game or an item on the ground in the - first 5 minutes.""" - display_name = "Randomize Progressive Locations" +class EnableNGPOption(Toggle): + """Include items and locations exclusive to NG+ cycles.""" + display_name = "Enable NG+" -class PoolTypeOption(Choice): - """Changes which non-progression items you add to the pool +## Equipment - Shuffle: Items are picked from the locations being randomized - Various: Items are picked from a list of all items in the game, but are the same type of item they replace""" - display_name = "Pool Type" - option_shuffle = 0 - option_various = 1 +class RandomizeStartingLoadout(DefaultOnToggle): + """Randomizes the equipment characters begin with.""" + display_name = "Randomize Starting Loadout" -class GuaranteedItemsOption(ItemDict): - """Guarantees that the specified items will be in the item pool""" - display_name = "Guaranteed Items" +class RequireOneHandedStartingWeapons(DefaultOnToggle): + """Require starting equipment to be usable one-handed.""" + display_name = "Require One-Handed Starting Weapons" class AutoEquipOption(Toggle): @@ -83,47 +81,56 @@ class AutoEquipOption(Toggle): class LockEquipOption(Toggle): - """Lock the equipment slots so you cannot change your armor or your left/right weapons. Works great with the - Auto-equip option.""" + """Lock the equipment slots so you cannot change your armor or your left/right weapons. + + Works great with the Auto-equip option. + """ display_name = "Lock Equipment Slots" +class NoEquipLoadOption(Toggle): + """Disable the equip load constraint from the game.""" + display_name = "No Equip Load" + + class NoWeaponRequirementsOption(Toggle): - """Disable the weapon requirements by removing any movement or damage penalties. - Permitting you to use any weapon early""" + """Disable the weapon requirements by removing any movement or damage penalties, permitting you + to use any weapon early. + """ display_name = "No Weapon Requirements" class NoSpellRequirementsOption(Toggle): - """Disable the spell requirements permitting you to use any spell""" + """Disable the spell requirements permitting you to use any spell.""" display_name = "No Spell Requirements" -class NoEquipLoadOption(Toggle): - """Disable the equip load constraint from the game""" - display_name = "No Equip Load" - +## Weapons class RandomizeInfusionOption(Toggle): """Enable this option to infuse a percentage of the pool of weapons and shields.""" display_name = "Randomize Infusion" -class RandomizeInfusionPercentageOption(Range): - """The percentage of weapons/shields in the pool to be infused if Randomize Infusion is toggled""" +class RandomizeInfusionPercentageOption(NamedRange): + """The percentage of weapons/shields in the pool to be infused if Randomize Infusion is toggled. + """ display_name = "Percentage of Infused Weapons" range_start = 0 range_end = 100 default = 33 + # 3/155 weapons are infused in the base game, or about 2% + special_range_names = {"similar to base game": 2} class RandomizeWeaponLevelOption(Choice): - """Enable this option to upgrade a percentage of the pool of weapons to a random value between the minimum and - maximum levels defined. + """Enable this option to upgrade a percentage of the pool of weapons to a random value between + the minimum and maximum levels defined. - All: All weapons are eligible, both basic and epic - Basic: Only weapons that can be upgraded to +10 - Epic: Only weapons that can be upgraded to +5""" + - **All:** All weapons are eligible, both basic and epic + - **Basic:** Only weapons that can be upgraded to +10 + - **Epic:** Only weapons that can be upgraded to +5 + """ display_name = "Randomize Weapon Level" option_none = 0 option_all = 1 @@ -132,7 +139,7 @@ class RandomizeWeaponLevelOption(Choice): class RandomizeWeaponLevelPercentageOption(Range): - """The percentage of weapons in the pool to be upgraded if randomize weapons level is toggled""" + """The percentage of weapons in the pool to be upgraded if randomize weapons level is toggled.""" display_name = "Percentage of Randomized Weapons" range_start = 0 range_end = 100 @@ -140,7 +147,7 @@ class RandomizeWeaponLevelPercentageOption(Range): class MinLevelsIn5WeaponPoolOption(Range): - """The minimum upgraded value of a weapon in the pool of weapons that can only reach +5""" + """The minimum upgraded value of a weapon in the pool of weapons that can only reach +5.""" display_name = "Minimum Level of +5 Weapons" range_start = 0 range_end = 5 @@ -148,7 +155,7 @@ class MinLevelsIn5WeaponPoolOption(Range): class MaxLevelsIn5WeaponPoolOption(Range): - """The maximum upgraded value of a weapon in the pool of weapons that can only reach +5""" + """The maximum upgraded value of a weapon in the pool of weapons that can only reach +5.""" display_name = "Maximum Level of +5 Weapons" range_start = 0 range_end = 5 @@ -156,7 +163,7 @@ class MaxLevelsIn5WeaponPoolOption(Range): class MinLevelsIn10WeaponPoolOption(Range): - """The minimum upgraded value of a weapon in the pool of weapons that can reach +10""" + """The minimum upgraded value of a weapon in the pool of weapons that can reach +10.""" display_name = "Minimum Level of +10 Weapons" range_start = 0 range_end = 10 @@ -164,72 +171,308 @@ class MinLevelsIn10WeaponPoolOption(Range): class MaxLevelsIn10WeaponPoolOption(Range): - """The maximum upgraded value of a weapon in the pool of weapons that can reach +10""" + """The maximum upgraded value of a weapon in the pool of weapons that can reach +10.""" display_name = "Maximum Level of +10 Weapons" range_start = 0 range_end = 10 default = 10 -class EarlySmallLothricBanner(Choice): - """This option makes it so the user can choose to force the Small Lothric Banner into an early sphere in their world or - into an early sphere across all worlds.""" - display_name = "Early Small Lothric Banner" - option_off = 0 - option_early_global = 1 - option_early_local = 2 - default = option_off +## Item Smoothing +class SmoothSoulItemsOption(DefaultOnToggle): + """Distribute soul items in a similar order as the base game. -class LateBasinOfVowsOption(Toggle): - """This option makes it so the Basin of Vows is still randomized, but guarantees you that you wont have to venture into - Lothric Castle to find your Small Lothric Banner to get out of High Wall of Lothric. So you may find Basin of Vows early, - but you wont have to fight Dancer to find your Small Lothric Banner.""" - display_name = "Late Basin of Vows" + By default, soul items will be distributed totally randomly. If this is set, less valuable soul + items will generally appear in earlier spheres and more valuable ones will generally appear + later. + """ + display_name = "Smooth Soul Items" -class LateDLCOption(Toggle): - """This option makes it so you are guaranteed to find your Small Doll without having to venture off into the DLC, - effectively putting anything in the DLC in logic after finding both Contraption Key and Small Doll, - and being able to get into Irithyll of the Boreal Valley.""" - display_name = "Late DLC" +class SmoothUpgradeItemsOption(DefaultOnToggle): + """Distribute upgrade items in a similar order as the base game. + By default, upgrade items will be distributed totally randomly. If this is set, lower-level + upgrade items will generally appear in earlier spheres and higher-level ones will generally + appear later. + """ + display_name = "Smooth Upgrade Items" -class EnableDLCOption(Toggle): - """To use this option, you must own both the ASHES OF ARIANDEL and the RINGED CITY DLC""" - display_name = "Enable DLC" +class SmoothUpgradedWeaponsOption(DefaultOnToggle): + """Distribute upgraded weapons in a similar order as the base game. + + By default, upgraded weapons will be distributed totally randomly. If this is set, lower-level + weapons will generally appear in earlier spheres and higher-level ones will generally appear + later. + """ + display_name = "Smooth Upgraded Weapons" + + +### Enemies + +class RandomizeEnemiesOption(DefaultOnToggle): + """Randomize enemy and boss placements.""" + display_name = "Randomize Enemies" + + +class SimpleEarlyBossesOption(DefaultOnToggle): + """Avoid replacing Iudex Gundyr and Vordt with late bosses. + + This excludes all bosses after Dancer of the Boreal Valley from these two boss fights. Disable + it for a chance at a much harder early game. + + This is ignored unless enemies are randomized. + """ + display_name = "Simple Early Bosses" + + +class ScaleEnemiesOption(DefaultOnToggle): + """Scale randomized enemy stats to match the areas in which they appear. + + Disabling this will tend to make the early game much more difficult and the late game much + easier. + + This is ignored unless enemies are randomized. + """ + display_name = "Scale Enemies" + + +class RandomizeMimicsWithEnemiesOption(Toggle): + """Mix Mimics into the main enemy pool. + + If this is enabled, Mimics will be replaced by normal enemies who drop the Mimic rewards on + death, and Mimics will be placed randomly in place of normal enemies. It's recommended to enable + Impatient Mimics as well if you enable this. + + This is ignored unless enemies are randomized. + """ + display_name = "Randomize Mimics With Enemies" + + +class RandomizeSmallCrystalLizardsWithEnemiesOption(Toggle): + """Mix small Crystal Lizards into the main enemy pool. + + If this is enabled, Crystal Lizards will be replaced by normal enemies who drop the Crystal + Lizard rewards on death, and Crystal Lizards will be placed randomly in place of normal enemies. + + This is ignored unless enemies are randomized. + """ + display_name = "Randomize Small Crystal Lizards With Enemies" + + +class ReduceHarmlessEnemiesOption(Toggle): + """Reduce the frequency that "harmless" enemies appear. + + Enable this to add a bit of extra challenge. This severely limits the number of enemies that are + slow to aggro, slow to attack, and do very little damage that appear in the enemy pool. + + This is ignored unless enemies are randomized. + """ + display_name = "Reduce Harmless Enemies" + + +class AllChestsAreMimicsOption(Toggle): + """Replace all chests with mimics that drop the same items. + + If "Randomize Mimics With Enemies" is set, these chests will instead be replaced with random + enemies that drop the same items. + + This is ignored unless enemies are randomized. + """ + display_name = "All Chests Are Mimics" + + +class ImpatientMimicsOption(Toggle): + """Mimics attack as soon as you get close instead of waiting for you to open them. + + This is ignored unless enemies are randomized. + """ + display_name = "Impatient Mimics" + + +class RandomEnemyPresetOption(OptionDict): + """The YAML preset for the static enemy randomizer. + + See the static randomizer documentation in `randomizer\\presets\\README.txt` for details. + Include this as nested YAML. For example: + + .. code-block:: YAML + + random_enemy_preset: + RemoveSource: Ancient Wyvern; Darkeater Midir + DontRandomize: Iudex Gundyr + """ + display_name = "Random Enemy Preset" + supports_weighting = False + default = {} + + valid_keys = ["Description", "RecommendFullRandomization", "RecommendNoEnemyProgression", + "OopsAll", "Boss", "Miniboss", "Basic", "BuffBasicEnemiesAsBosses", + "DontRandomize", "RemoveSource", "Enemies"] + + @classmethod + def get_option_name(cls, value: Dict[str, Any]) -> str: + return json.dumps(value) + + +## Item & Location + +class DS3ExcludeLocations(ExcludeLocations): + """Prevent these locations from having an important item.""" + default = frozenset({"Hidden", "Small Crystal Lizards", "Upgrade", "Small Souls", "Miscellaneous"}) -dark_souls_options: typing.Dict[str, Option] = { - "enable_weapon_locations": RandomizeWeaponLocations, - "enable_shield_locations": RandomizeShieldLocations, - "enable_armor_locations": RandomizeArmorLocations, - "enable_ring_locations": RandomizeRingLocations, - "enable_spell_locations": RandomizeSpellLocations, - "enable_key_locations": RandomizeKeyLocations, - "enable_boss_locations": RandomizeBossSoulLocations, - "enable_npc_locations": RandomizeNPCLocations, - "enable_misc_locations": RandomizeMiscLocations, - "enable_health_upgrade_locations": RandomizeHealthLocations, - "enable_progressive_locations": RandomizeProgressiveLocationsOption, - "pool_type": PoolTypeOption, - "guaranteed_items": GuaranteedItemsOption, - "auto_equip": AutoEquipOption, - "lock_equip": LockEquipOption, - "no_weapon_requirements": NoWeaponRequirementsOption, - "randomize_infusion": RandomizeInfusionOption, - "randomize_infusion_percentage": RandomizeInfusionPercentageOption, - "randomize_weapon_level": RandomizeWeaponLevelOption, - "randomize_weapon_level_percentage": RandomizeWeaponLevelPercentageOption, - "min_levels_in_5": MinLevelsIn5WeaponPoolOption, - "max_levels_in_5": MaxLevelsIn5WeaponPoolOption, - "min_levels_in_10": MinLevelsIn10WeaponPoolOption, - "max_levels_in_10": MaxLevelsIn10WeaponPoolOption, - "early_banner": EarlySmallLothricBanner, - "late_basin_of_vows": LateBasinOfVowsOption, - "late_dlc": LateDLCOption, - "no_spell_requirements": NoSpellRequirementsOption, - "no_equip_load": NoEquipLoadOption, - "death_link": DeathLink, - "enable_dlc": EnableDLCOption, -} + +class ExcludedLocationBehaviorOption(Choice): + """How to choose items for excluded locations in DS3. + + - **Allow Useful:** Excluded locations can't have progression items, but they can have useful + items. + - **Forbid Useful:** Neither progression items nor useful items can be placed in excluded + locations. + - **Do Not Randomize:** Excluded locations always contain the same item as in vanilla Dark Souls + III. + + A "progression item" is anything that's required to unlock another location in some game. A + "useful item" is something each game defines individually, usually items that are quite + desirable but not strictly necessary. + """ + display_name = "Excluded Locations Behavior" + option_allow_useful = 1 + option_forbid_useful = 2 + option_do_not_randomize = 3 + default = 2 + + +class MissableLocationBehaviorOption(Choice): + """Which items can be placed in locations that can be permanently missed. + + - **Allow Useful:** Missable locations can't have progression items, but they can have useful + items. + - **Forbid Useful:** Neither progression items nor useful items can be placed in missable + locations. + - **Do Not Randomize:** Missable locations always contain the same item as in vanilla Dark Souls + III. + + A "progression item" is anything that's required to unlock another location in some game. A + "useful item" is something each game defines individually, usually items that are quite + desirable but not strictly necessary. + """ + display_name = "Missable Locations Behavior" + option_allow_useful = 1 + option_forbid_useful = 2 + option_do_not_randomize = 3 + default = 2 + + +@dataclass +class DarkSouls3Options(PerGameCommonOptions): + # Game Options + early_banner: EarlySmallLothricBanner + late_basin_of_vows: LateBasinOfVowsOption + late_dlc: LateDLCOption + death_link: DeathLink + enable_dlc: EnableDLCOption + enable_ngp: EnableNGPOption + + # Equipment + random_starting_loadout: RandomizeStartingLoadout + require_one_handed_starting_weapons: RequireOneHandedStartingWeapons + auto_equip: AutoEquipOption + lock_equip: LockEquipOption + no_equip_load: NoEquipLoadOption + no_weapon_requirements: NoWeaponRequirementsOption + no_spell_requirements: NoSpellRequirementsOption + + # Weapons + randomize_infusion: RandomizeInfusionOption + randomize_infusion_percentage: RandomizeInfusionPercentageOption + randomize_weapon_level: RandomizeWeaponLevelOption + randomize_weapon_level_percentage: RandomizeWeaponLevelPercentageOption + min_levels_in_5: MinLevelsIn5WeaponPoolOption + max_levels_in_5: MaxLevelsIn5WeaponPoolOption + min_levels_in_10: MinLevelsIn10WeaponPoolOption + max_levels_in_10: MaxLevelsIn10WeaponPoolOption + + # Item Smoothing + smooth_soul_items: SmoothSoulItemsOption + smooth_upgrade_items: SmoothUpgradeItemsOption + smooth_upgraded_weapons: SmoothUpgradedWeaponsOption + + # Enemies + randomize_enemies: RandomizeEnemiesOption + simple_early_bosses: SimpleEarlyBossesOption + scale_enemies: ScaleEnemiesOption + randomize_mimics_with_enemies: RandomizeMimicsWithEnemiesOption + randomize_small_crystal_lizards_with_enemies: RandomizeSmallCrystalLizardsWithEnemiesOption + reduce_harmless_enemies: ReduceHarmlessEnemiesOption + all_chests_are_mimics: AllChestsAreMimicsOption + impatient_mimics: ImpatientMimicsOption + random_enemy_preset: RandomEnemyPresetOption + + # Item & Location + exclude_locations: DS3ExcludeLocations + excluded_location_behavior: ExcludedLocationBehaviorOption + missable_location_behavior: MissableLocationBehaviorOption + + # Removed + pool_type: Removed + enable_weapon_locations: Removed + enable_shield_locations: Removed + enable_armor_locations: Removed + enable_ring_locations: Removed + enable_spell_locations: Removed + enable_key_locations: Removed + enable_boss_locations: Removed + enable_npc_locations: Removed + enable_misc_locations: Removed + enable_health_upgrade_locations: Removed + enable_progressive_locations: Removed + guaranteed_items: Removed + excluded_locations: Removed + missable_locations: Removed + + +option_groups = [ + OptionGroup("Equipment", [ + RandomizeStartingLoadout, + RequireOneHandedStartingWeapons, + AutoEquipOption, + LockEquipOption, + NoEquipLoadOption, + NoWeaponRequirementsOption, + NoSpellRequirementsOption, + ]), + OptionGroup("Weapons", [ + RandomizeInfusionOption, + RandomizeInfusionPercentageOption, + RandomizeWeaponLevelOption, + RandomizeWeaponLevelPercentageOption, + MinLevelsIn5WeaponPoolOption, + MaxLevelsIn5WeaponPoolOption, + MinLevelsIn10WeaponPoolOption, + MaxLevelsIn10WeaponPoolOption, + ]), + OptionGroup("Item Smoothing", [ + SmoothSoulItemsOption, + SmoothUpgradeItemsOption, + SmoothUpgradedWeaponsOption, + ]), + OptionGroup("Enemies", [ + RandomizeEnemiesOption, + SimpleEarlyBossesOption, + ScaleEnemiesOption, + RandomizeMimicsWithEnemiesOption, + RandomizeSmallCrystalLizardsWithEnemiesOption, + ReduceHarmlessEnemiesOption, + AllChestsAreMimicsOption, + ImpatientMimicsOption, + RandomEnemyPresetOption, + ]), + OptionGroup("Item & Location Options", [ + DS3ExcludeLocations, + ExcludedLocationBehaviorOption, + MissableLocationBehaviorOption, + ]) +] diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index 02001098116..159a870c765 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -1,15 +1,19 @@ # world/dark_souls_3/__init__.py -from typing import Dict, Set, List +from collections.abc import Sequence +from collections import defaultdict +import json +from logging import warning +from typing import cast, Any, Callable, Dict, Set, List, Optional, TextIO, Union -from BaseClasses import MultiWorld, Region, Item, Entrance, Tutorial, ItemClassification -from Options import Toggle +from BaseClasses import CollectionState, MultiWorld, Region, Location, LocationProgressType, Entrance, Tutorial, ItemClassification from worlds.AutoWorld import World, WebWorld -from worlds.generic.Rules import set_rule, add_rule, add_item_rule +from worlds.generic.Rules import CollectionRule, ItemRule, add_rule, add_item_rule -from .Items import DarkSouls3Item, DS3ItemCategory, item_dictionary, key_item_names, item_descriptions -from .Locations import DarkSouls3Location, DS3LocationCategory, location_tables, location_dictionary -from .Options import RandomizeWeaponLevelOption, PoolTypeOption, EarlySmallLothricBanner, dark_souls_options +from .Bosses import DS3BossInfo, all_bosses, default_yhorm_location +from .Items import DarkSouls3Item, DS3ItemData, Infusion, UsefulIf, filler_item_names, item_descriptions, item_dictionary, item_name_groups +from .Locations import DarkSouls3Location, DS3LocationData, location_tables, location_descriptions, location_dictionary, location_name_groups, region_order +from .Options import DarkSouls3Options, option_groups class DarkSouls3Web(WebWorld): @@ -34,91 +38,117 @@ class DarkSouls3Web(WebWorld): ) tutorials = [setup_en, setup_fr] - + option_groups = option_groups item_descriptions = item_descriptions + rich_text_options_doc = True class DarkSouls3World(World): """ Dark souls III is an Action role-playing game and is part of the Souls series developed by FromSoftware. - Played in a third-person perspective, players have access to various weapons, armour, magic, and consumables that + Played from a third-person perspective, players have access to various weapons, armour, magic, and consumables that they can use to fight their enemies. """ - game: str = "Dark Souls III" - option_definitions = dark_souls_options - topology_present: bool = True + game = "Dark Souls III" + options: DarkSouls3Options + options_dataclass = DarkSouls3Options web = DarkSouls3Web() base_id = 100000 - enabled_location_categories: Set[DS3LocationCategory] required_client_version = (0, 4, 2) - item_name_to_id = DarkSouls3Item.get_name_to_id() - location_name_to_id = DarkSouls3Location.get_name_to_id() - item_name_groups = { - "Cinders": { - "Cinders of a Lord - Abyss Watcher", - "Cinders of a Lord - Aldrich", - "Cinders of a Lord - Yhorm the Giant", - "Cinders of a Lord - Lothric Prince" - } + item_name_to_id = {data.name: data.ap_code for data in item_dictionary.values() if data.ap_code is not None} + location_name_to_id = { + location.name: location.ap_code + for locations in location_tables.values() + for location in locations + if location.ap_code is not None } + location_name_groups = location_name_groups + item_name_groups = item_name_groups + location_descriptions = location_descriptions + item_descriptions = item_descriptions + + yhorm_location: DS3BossInfo = default_yhorm_location + """If enemy randomization is enabled, this is the boss who Yhorm the Giant should replace. + + This is used to determine where the Storm Ruler can be placed. + """ + + all_excluded_locations: Set[str] = set() + """This is the same value as `self.options.exclude_locations.value` initially, but if + `options.exclude_locations` gets cleared due to `excluded_locations: allow_useful` this still + holds the old locations so we can ensure they don't get necessary items. + """ + + local_itempool: List[DarkSouls3Item] = [] + """The pool of all items within this particular world. This is a subset of + `self.multiworld.itempool`.""" def __init__(self, multiworld: MultiWorld, player: int): super().__init__(multiworld, player) - self.locked_items = [] - self.locked_locations = [] - self.main_path_locations = [] - self.enabled_location_categories = set() - - - def generate_early(self): - if self.multiworld.enable_weapon_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.WEAPON) - if self.multiworld.enable_shield_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.SHIELD) - if self.multiworld.enable_armor_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.ARMOR) - if self.multiworld.enable_ring_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.RING) - if self.multiworld.enable_spell_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.SPELL) - if self.multiworld.enable_npc_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.NPC) - if self.multiworld.enable_key_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.KEY) - if self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_global: - self.multiworld.early_items[self.player]['Small Lothric Banner'] = 1 - elif self.multiworld.early_banner[self.player] == EarlySmallLothricBanner.option_early_local: - self.multiworld.local_early_items[self.player]['Small Lothric Banner'] = 1 - if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.BOSS) - if self.multiworld.enable_misc_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.MISC) - if self.multiworld.enable_health_upgrade_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.HEALTH) - if self.multiworld.enable_progressive_locations[self.player] == Toggle.option_true: - self.enabled_location_categories.add(DS3LocationCategory.PROGRESSIVE_ITEM) - - - def create_regions(self): - progressive_location_table = [] - if self.multiworld.enable_progressive_locations[self.player]: - progressive_location_table = [] + \ - location_tables["Progressive Items 1"] + \ - location_tables["Progressive Items 2"] + \ - location_tables["Progressive Items 3"] + \ - location_tables["Progressive Items 4"] - - if self.multiworld.enable_dlc[self.player].value: - progressive_location_table += location_tables["Progressive Items DLC"] - - if self.multiworld.enable_health_upgrade_locations[self.player]: - progressive_location_table += location_tables["Progressive Items Health"] - + self.all_excluded_locations = set() + + def generate_early(self) -> None: + self.all_excluded_locations.update(self.options.exclude_locations.value) + + # Inform Universal Tracker where Yhorm is being randomized to. + if hasattr(self.multiworld, "re_gen_passthrough"): + if "Dark Souls III" in self.multiworld.re_gen_passthrough: + if self.multiworld.re_gen_passthrough["Dark Souls III"]["options"]["randomize_enemies"]: + yhorm_data = self.multiworld.re_gen_passthrough["Dark Souls III"]["yhorm"] + for boss in all_bosses: + if yhorm_data.startswith(boss.name): + self.yhorm_location = boss + + # Randomize Yhorm manually so that we know where to place the Storm Ruler. + elif self.options.randomize_enemies: + self.yhorm_location = self.random.choice( + [boss for boss in all_bosses if self._allow_boss_for_yhorm(boss)]) + + # If Yhorm is early, make sure the Storm Ruler is easily available to avoid BK + # Iudex Gundyr is handled separately in _fill_local_items + if ( + self.yhorm_location.name == "Vordt of the Boreal Valley" or ( + self.yhorm_location.name == "Dancer of the Boreal Valley" and + not self.options.late_basin_of_vows + ) + ): + self.multiworld.local_early_items[self.player]["Storm Ruler"] = 1 + + def _allow_boss_for_yhorm(self, boss: DS3BossInfo) -> bool: + """Returns whether boss is a valid location for Yhorm in this seed.""" + + if not self.options.enable_dlc and boss.dlc: return False + + if not self._is_location_available("PC: Storm Ruler - boss room"): + # If the Storm Ruler isn't randomized, make sure the player can get to the normal Storm + # Ruler location before they need to get through Yhorm. + if boss.before_storm_ruler: return False + + # If the Small Doll also wasn't randomized, make sure Yhorm isn't blocking access to it + # or it won't be possible to get into Profaned Capital before beating him. + if ( + not self._is_location_available("CD: Small Doll - boss drop") + and boss.name in {"Crystal Sage", "Deacons of the Deep"} + ): + return False + + if boss.name != "Iudex Gundyr": return True + + # Cemetery of Ash has very few locations and all of them are excluded by default, so only + # allow Yhorm as Iudex Gundyr if there's at least one available location. + return any( + self._is_location_available(location) + and location.name not in self.all_excluded_locations + and location.name != "CA: Coiled Sword - boss drop" + for location in location_tables["Cemetery of Ash"] + ) + + def create_regions(self) -> None: # Create Vanilla Regions - regions: Dict[str, Region] = {} - regions["Menu"] = self.create_region("Menu", progressive_location_table) + regions: Dict[str, Region] = {"Menu": self.create_region("Menu", {})} regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [ + "Cemetery of Ash", "Firelink Shrine", "Firelink Shrine Bell Tower", "High Wall of Lothric", @@ -138,18 +168,15 @@ def create_regions(self): "Untended Graves", "Archdragon Peak", "Kiln of the First Flame", + "Greirat's Shop", + "Karla's Shop", ]}) - # Adds Path of the Dragon as an event item for Archdragon Peak access - potd_location = DarkSouls3Location(self.player, "CKG: Path of the Dragon", DS3LocationCategory.EVENT, "Path of the Dragon", None, regions["Consumed King's Garden"]) - potd_location.place_locked_item(Item("Path of the Dragon", ItemClassification.progression, None, self.player)) - regions["Consumed King's Garden"].locations.append(potd_location) - # Create DLC Regions - if self.multiworld.enable_dlc[self.player]: + if self.options.enable_dlc: regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [ - "Painted World of Ariandel 1", - "Painted World of Ariandel 2", + "Painted World of Ariandel (Before Contraption)", + "Painted World of Ariandel (After Contraption)", "Dreg Heap", "Ringed City", ]}) @@ -161,7 +188,9 @@ def create_connection(from_region: str, to_region: str): connection.connect(regions[to_region]) regions["Menu"].exits.append(Entrance(self.player, "New Game", regions["Menu"])) - self.multiworld.get_entrance("New Game", self.player).connect(regions["Firelink Shrine"]) + self.multiworld.get_entrance("New Game", self.player).connect(regions["Cemetery of Ash"]) + + create_connection("Cemetery of Ash", "Firelink Shrine") create_connection("Firelink Shrine", "High Wall of Lothric") create_connection("Firelink Shrine", "Firelink Shrine Bell Tower") @@ -169,6 +198,7 @@ def create_connection(from_region: str, to_region: str): create_connection("High Wall of Lothric", "Undead Settlement") create_connection("High Wall of Lothric", "Lothric Castle") + create_connection("High Wall of Lothric", "Greirat's Shop") create_connection("Undead Settlement", "Road of Sacrifices") @@ -185,6 +215,7 @@ def create_connection(from_region: str, to_region: str): create_connection("Irithyll Dungeon", "Archdragon Peak") create_connection("Irithyll Dungeon", "Profaned Capital") + create_connection("Irithyll Dungeon", "Karla's Shop") create_connection("Lothric Castle", "Consumed King's Garden") create_connection("Lothric Castle", "Grand Archives") @@ -192,357 +223,1349 @@ def create_connection(from_region: str, to_region: str): create_connection("Consumed King's Garden", "Untended Graves") # Connect DLC Regions - if self.multiworld.enable_dlc[self.player]: - create_connection("Cathedral of the Deep", "Painted World of Ariandel 1") - create_connection("Painted World of Ariandel 1", "Painted World of Ariandel 2") - create_connection("Painted World of Ariandel 2", "Dreg Heap") + if self.options.enable_dlc: + create_connection("Cathedral of the Deep", "Painted World of Ariandel (Before Contraption)") + create_connection("Painted World of Ariandel (Before Contraption)", + "Painted World of Ariandel (After Contraption)") + create_connection("Painted World of Ariandel (After Contraption)", "Dreg Heap") create_connection("Dreg Heap", "Ringed City") - # For each region, add the associated locations retrieved from the corresponding location_table def create_region(self, region_name, location_table) -> Region: new_region = Region(region_name, self.player, self.multiworld) + # Use this to un-exclude event locations so the fill doesn't complain about items behind + # them being unreachable. + excluded = self.options.exclude_locations.value + for location in location_table: - if location.category in self.enabled_location_categories: - new_location = DarkSouls3Location( - self.player, - location.name, - location.category, - location.default_item, - self.location_name_to_id[location.name], - new_region - ) + if self._is_location_available(location): + new_location = DarkSouls3Location(self.player, location, new_region) + if ( + # Exclude missable locations that don't allow useful items + location.missable and self.options.missable_location_behavior == "forbid_useful" + and not ( + # Unless they are excluded to a higher degree already + location.name in self.all_excluded_locations + and self.options.missable_location_behavior < self.options.excluded_location_behavior + ) + ) or ( + # Lift Chamber Key is missable. Exclude Lift-Chamber-Key-Locked locations if it isn't randomized + not self._is_location_available("FS: Lift Chamber Key - Leonhard") + and location.name == "HWL: Red Eye Orb - wall tower, miniboss" + ) or ( + # Chameleon is missable. Exclude Chameleon-locked locations if it isn't randomized + not self._is_location_available("AL: Chameleon - tomb after marrying Anri") + and location.name in {"RC: Dragonhead Shield - streets monument, across bridge", + "RC: Large Soul of a Crestfallen Knight - streets monument, across bridge", + "RC: Divine Blessing - streets monument, mob drop", "RC: Lapp's Helm - Lapp", + "RC: Lapp's Armor - Lapp", + "RC: Lapp's Gauntlets - Lapp", + "RC: Lapp's Leggings - Lapp"} + ): + new_location.progress_type = LocationProgressType.EXCLUDED else: - # Replace non-randomized progression items with events - event_item = self.create_item(location.default_item) - if event_item.classification != ItemClassification.progression: + # Don't allow missable duplicates of progression items to be expected progression. + if location.name in {"PC: Storm Ruler - Siegward", + "US: Pyromancy Flame - Cornyx", + "US: Tower Key - kill Irina"}: continue + # Replace non-randomized items with events that give the default item + event_item = ( + self.create_item(location.default_item_name) if location.default_item_name + else DarkSouls3Item.event(location.name, self.player) + ) + new_location = DarkSouls3Location( self.player, - location.name, - location.category, - location.default_item, - None, - new_region + location, + parent = new_region, + event = True, ) event_item.code = None new_location.place_locked_item(event_item) - - if region_name == "Menu": - add_item_rule(new_location, lambda item: not item.advancement) + if location.name in excluded: + excluded.remove(location.name) + # Only remove from all_excluded if excluded does not have priority over missable + if not (self.options.missable_location_behavior < self.options.excluded_location_behavior): + self.all_excluded_locations.remove(location.name) new_region.locations.append(new_location) self.multiworld.regions.append(new_region) return new_region - - def create_items(self): - dlc_enabled = self.multiworld.enable_dlc[self.player] == Toggle.option_true - - itempool_by_category = {category: [] for category in self.enabled_location_categories} + def create_items(self) -> None: + # Just used to efficiently deduplicate items + item_set: Set[str] = set() # Gather all default items on randomized locations + self.local_itempool = [] num_required_extra_items = 0 - for location in self.multiworld.get_locations(self.player): - if location.category in itempool_by_category: - if item_dictionary[location.default_item_name].category == DS3ItemCategory.SKIP: + for location in cast(List[DarkSouls3Location], self.multiworld.get_unfilled_locations(self.player)): + if not self._is_location_available(location.name): + raise Exception("DS3 generation bug: Added an unavailable location.") + + default_item_name = cast(str, location.data.default_item_name) + item = item_dictionary[default_item_name] + if item.skip: + num_required_extra_items += 1 + elif not item.unique: + self.local_itempool.append(self.create_item(default_item_name)) + else: + # For unique items, make sure there aren't duplicates in the item set even if there + # are multiple in-game locations that provide them. + if default_item_name in item_set: num_required_extra_items += 1 else: - itempool_by_category[location.category].append(location.default_item_name) - - # Replace each item category with a random sample of items of those types - if self.multiworld.pool_type[self.player] == PoolTypeOption.option_various: - def create_random_replacement_list(item_categories: Set[DS3ItemCategory], num_items: int): - candidates = [ - item.name for item - in item_dictionary.values() - if (item.category in item_categories and (not item.is_dlc or dlc_enabled)) - ] - return self.multiworld.random.sample(candidates, num_items) - - if DS3LocationCategory.WEAPON in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.WEAPON] = create_random_replacement_list( - { - DS3ItemCategory.WEAPON_UPGRADE_5, - DS3ItemCategory.WEAPON_UPGRADE_10, - DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE - }, - len(itempool_by_category[DS3LocationCategory.WEAPON]) - ) - if DS3LocationCategory.SHIELD in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.SHIELD] = create_random_replacement_list( - {DS3ItemCategory.SHIELD, DS3ItemCategory.SHIELD_INFUSIBLE}, - len(itempool_by_category[DS3LocationCategory.SHIELD]) - ) - if DS3LocationCategory.ARMOR in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.ARMOR] = create_random_replacement_list( - {DS3ItemCategory.ARMOR}, - len(itempool_by_category[DS3LocationCategory.ARMOR]) - ) - if DS3LocationCategory.RING in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.RING] = create_random_replacement_list( - {DS3ItemCategory.RING}, - len(itempool_by_category[DS3LocationCategory.RING]) - ) - if DS3LocationCategory.SPELL in self.enabled_location_categories: - itempool_by_category[DS3LocationCategory.SPELL] = create_random_replacement_list( - {DS3ItemCategory.SPELL}, - len(itempool_by_category[DS3LocationCategory.SPELL]) - ) - - itempool: List[DarkSouls3Item] = [] - for category in self.enabled_location_categories: - itempool += [self.create_item(name) for name in itempool_by_category[category]] - - # A list of items we can replace - removable_items = [item for item in itempool if item.classification != ItemClassification.progression] - - guaranteed_items = self.multiworld.guaranteed_items[self.player].value - for item_name in guaranteed_items: - # Break early just in case nothing is removable (if user is trying to guarantee more - # items than the pool can hold, for example) - if len(removable_items) == 0: - break - - num_existing_copies = len([item for item in itempool if item.name == item_name]) - for _ in range(guaranteed_items[item_name]): - if num_existing_copies > 0: - num_existing_copies -= 1 - continue - - if num_required_extra_items > 0: - # We can just add them instead of using "Soul of an Intrepid Hero" later - num_required_extra_items -= 1 - else: - if len(removable_items) == 0: - break + item_set.add(default_item_name) + self.local_itempool.append(self.create_item(default_item_name)) - # Try to construct a list of items with the same category that can be removed - # If none exist, just remove something at random - removable_shortlist = [ - item for item - in removable_items - if item_dictionary[item.name].category == item_dictionary[item_name].category - ] - if len(removable_shortlist) == 0: - removable_shortlist = removable_items + injectables = self._create_injectable_items(num_required_extra_items) + num_required_extra_items -= len(injectables) + self.local_itempool.extend(injectables) - removed_item = self.multiworld.random.choice(removable_shortlist) - removable_items.remove(removed_item) # To avoid trying to replace the same item twice - itempool.remove(removed_item) + # Extra filler items for locations containing skip items + self.local_itempool.extend(self.create_item(self.get_filler_item_name()) for _ in range(num_required_extra_items)) - itempool.append(self.create_item(item_name)) - - # Extra filler items for locations containing SKIP items - itempool += [self.create_filler() for _ in range(num_required_extra_items)] + # Potentially fill some items locally and remove them from the itempool + self._fill_local_items() # Add items to itempool - self.multiworld.itempool += itempool - - - def create_item(self, name: str) -> Item: - useful_categories = { - DS3ItemCategory.WEAPON_UPGRADE_5, - DS3ItemCategory.WEAPON_UPGRADE_10, - DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, - DS3ItemCategory.SPELL, - } - data = self.item_name_to_id[name] - - if name in key_item_names: - item_classification = ItemClassification.progression - elif item_dictionary[name].category in useful_categories or name in {"Estus Shard", "Undead Bone Shard"}: - item_classification = ItemClassification.useful - else: - item_classification = ItemClassification.filler - - return DarkSouls3Item(name, item_classification, data, self.player) + self.multiworld.itempool += self.local_itempool + + def _create_injectable_items(self, num_required_extra_items: int) -> List[DarkSouls3Item]: + """Returns a list of items to inject into the multiworld instead of skipped items. + + If there isn't enough room to inject all the necessary progression items + that are in missable locations by default, this adds them to the + player's starting inventory. + """ + + all_injectable_items = [ + item for item + in item_dictionary.values() + if item.inject and (not item.is_dlc or self.options.enable_dlc) + ] + injectable_mandatory = [ + item for item in all_injectable_items + if item.classification == ItemClassification.progression + ] + injectable_optional = [ + item for item in all_injectable_items + if item.classification != ItemClassification.progression + ] + + number_to_inject = min(num_required_extra_items, len(all_injectable_items)) + items = ( + self.random.sample( + injectable_mandatory, + k=min(len(injectable_mandatory), number_to_inject) + ) + + self.random.sample( + injectable_optional, + k=max(0, number_to_inject - len(injectable_mandatory)) + ) + ) + + if number_to_inject < len(injectable_mandatory): + # It's worth considering the possibility of _removing_ unimportant + # items from the pool to inject these instead rather than just + # making them part of the starting health back + for item in injectable_mandatory: + if item in items: continue + self.multiworld.push_precollected(self.create_item(item)) + warning( + f"Couldn't add \"{item.name}\" to the item pool for " + + f"{self.player_name}. Adding it to the starting " + + f"inventory instead." + ) + return [self.create_item(item) for item in items] + + def create_item(self, item: Union[str, DS3ItemData]) -> DarkSouls3Item: + data = item if isinstance(item, DS3ItemData) else item_dictionary[item] + classification = None + if self.multiworld and data.useful_if != UsefulIf.DEFAULT and ( + ( + data.useful_if == UsefulIf.BASE and + not self.options.enable_dlc and + not self.options.enable_ngp + ) + or (data.useful_if == UsefulIf.NO_DLC and not self.options.enable_dlc) + or (data.useful_if == UsefulIf.NO_NGP and not self.options.enable_ngp) + ): + classification = ItemClassification.useful + + if ( + self.options.randomize_weapon_level != "none" + and data.category.upgrade_level + # Because we require the Pyromancy Flame to be available early, don't upgrade it so it + # doesn't get shuffled around by weapon smoothing. + and data.name != "Pyromancy Flame" + ): + # if the user made an error and set a min higher than the max we default to the max + max_5 = self.options.max_levels_in_5.value + min_5 = min(self.options.min_levels_in_5.value, max_5) + max_10 = self.options.max_levels_in_10.value + min_10 = min(self.options.min_levels_in_10.value, max_10) + weapon_level_percentage = self.options.randomize_weapon_level_percentage + + if self.random.randint(0, 99) < weapon_level_percentage: + if data.category.upgrade_level == 5: + data = data.upgrade(self.random.randint(min_5, max_5)) + elif data.category.upgrade_level == 10: + data = data.upgrade(self.random.randint(min_10, max_10)) + + if self.options.randomize_infusion and data.category.is_infusible: + infusion_percentage = self.options.randomize_infusion_percentage + if self.random.randint(0, 99) < infusion_percentage: + data = data.infuse(self.random.choice(list(Infusion))) + + return DarkSouls3Item(self.player, data, classification=classification) + + def _fill_local_items(self) -> None: + """Removes certain items from the item pool and manually places them in the local world. + + We can't do this in pre_fill because the itempool may not be modified after create_items. + """ + # If Yhorm is at Iudex Gundyr, Storm Ruler must be randomized, so it can always be moved. + # Fill this manually so that, if very few slots are available in Cemetery of Ash, this + # doesn't get locked out by bad rolls on the next two fills. + if self.yhorm_location.name == "Iudex Gundyr": + self._fill_local_item("Storm Ruler", ["Cemetery of Ash"], + lambda location: location.name != "CA: Coiled Sword - boss drop") + + # If the Coiled Sword is vanilla, it is early enough and doesn't need to be placed. + # Don't place this in the multiworld because it's necessary almost immediately, and don't + # mark it as a blocker for HWL because having a miniscule Sphere 1 screws with progression balancing. + if self._is_location_available("CA: Coiled Sword - boss drop"): + self._fill_local_item("Coiled Sword", ["Cemetery of Ash", "Firelink Shrine"]) + + # If the HWL Raw Gem is vanilla, it is early enough and doesn't need to be removed. If + # upgrade smoothing is enabled, make sure one raw gem is available early for SL1 players + if ( + self._is_location_available("HWL: Raw Gem - fort roof, lizard") + and self.options.smooth_upgrade_items + ): + self._fill_local_item("Raw Gem", [ + "Cemetery of Ash", + "Firelink Shrine", + "High Wall of Lothric" + ]) + + def _fill_local_item( + self, name: str, + regions: List[str], + additional_condition: Optional[Callable[[DS3LocationData], bool]] = None, + ) -> None: + """Chooses a valid location for the item with the given name and places it there. + + This always chooses a local location among the given regions. If additional_condition is + passed, only locations meeting that condition will be considered. + + If the item could not be placed, it will be added to starting inventory. + """ + item = next((item for item in self.local_itempool if item.name == name), None) + if not item: return + + candidate_locations = [ + location for location in ( + self.multiworld.get_location(location.name, self.player) + for region in regions + for location in location_tables[region] + if self._is_location_available(location) + and not location.missable + and not location.conditional + and (not additional_condition or additional_condition(location)) + ) + # We can't use location.progress_type here because it's not set + # until after `set_rules()` runs. + if not location.item and location.name not in self.all_excluded_locations + and location.item_rule(item) + ] + + self.local_itempool.remove(item) + + if not candidate_locations: + warning(f"Couldn't place \"{name}\" in a valid location for {self.player_name}. Adding it to starting inventory instead.") + location = next( + (location for location in self._get_our_locations() if location.data.default_item_name == item.name), + None + ) + if location: self._replace_with_filler(location) + self.multiworld.push_precollected(self.create_item(name)) + return + + location = self.random.choice(candidate_locations) + location.place_locked_item(item) + + def _replace_with_filler(self, location: DarkSouls3Location) -> None: + """If possible, choose a filler item to replace location's current contents with.""" + if location.locked: return + + # Try 10 filler items. If none of them work, give up and leave it as-is. + for _ in range(0, 10): + candidate = self.create_filler() + if location.item_rule(candidate): + location.item = candidate + return def get_filler_item_name(self) -> str: - return "Soul of an Intrepid Hero" - + return self.random.choice(filler_item_names) def set_rules(self) -> None: - # Define the access rules to the entrances - set_rule(self.multiworld.get_entrance("Go To Undead Settlement", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - set_rule(self.multiworld.get_entrance("Go To Lothric Castle", self.player), - lambda state: state.has("Basin of Vows", self.player)) - set_rule(self.multiworld.get_entrance("Go To Irithyll of the Boreal Valley", self.player), - lambda state: state.has("Small Doll", self.player)) - set_rule(self.multiworld.get_entrance("Go To Archdragon Peak", self.player), - lambda state: state.has("Path of the Dragon", self.player)) - set_rule(self.multiworld.get_entrance("Go To Grand Archives", self.player), - lambda state: state.has("Grand Archives Key", self.player)) - set_rule(self.multiworld.get_entrance("Go To Kiln of the First Flame", self.player), - lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and - state.has("Cinders of a Lord - Yhorm the Giant", self.player) and - state.has("Cinders of a Lord - Aldrich", self.player) and - state.has("Cinders of a Lord - Lothric Prince", self.player)) - - if self.multiworld.late_basin_of_vows[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_entrance("Go To Lothric Castle", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) + randomized_items = {item.name for item in self.local_itempool} + + self._add_shop_rules() + self._add_npc_rules() + self._add_transposition_rules() + self._add_crow_rules() + self._add_allow_useful_location_rules() + self._add_early_item_rules(randomized_items) + + self._add_entrance_rule("Firelink Shrine Bell Tower", "Tower Key") + self._add_entrance_rule("Undead Settlement", lambda state: ( + state.has("Small Lothric Banner", self.player) + and self._can_get(state, "HWL: Soul of Boreal Valley Vordt") + )) + self._add_entrance_rule("Road of Sacrifices", "US -> RS") + self._add_entrance_rule( + "Cathedral of the Deep", + lambda state: self._can_get(state, "RS: Soul of a Crystal Sage") + ) + self._add_entrance_rule("Farron Keep", "RS -> FK") + self._add_entrance_rule( + "Catacombs of Carthus", + lambda state: self._can_get(state, "FK: Soul of the Blood of the Wolf") + ) + self._add_entrance_rule("Irithyll Dungeon", "IBV -> ID") + self._add_entrance_rule( + "Lothric Castle", + lambda state: self._can_get(state, "HWL: Soul of the Dancer") + ) + self._add_entrance_rule( + "Untended Graves", + lambda state: self._can_get(state, "CKG: Soul of Consumed Oceiros") + ) + self._add_entrance_rule("Irithyll of the Boreal Valley", lambda state: ( + state.has("Small Doll", self.player) + and self._can_get(state, "CC: Soul of High Lord Wolnir") + )) + self._add_entrance_rule( + "Anor Londo", + lambda state: self._can_get(state, "IBV: Soul of Pontiff Sulyvahn") + ) + self._add_entrance_rule("Archdragon Peak", "Path of the Dragon") + self._add_entrance_rule("Grand Archives", lambda state: ( + state.has("Grand Archives Key", self.player) + and self._can_get(state, "LC: Soul of Dragonslayer Armour") + )) + self._add_entrance_rule("Kiln of the First Flame", lambda state: ( + state.has("Cinders of a Lord - Abyss Watcher", self.player) + and state.has("Cinders of a Lord - Yhorm the Giant", self.player) + and state.has("Cinders of a Lord - Aldrich", self.player) + and state.has("Cinders of a Lord - Lothric Prince", self.player) + and state.has("Transposing Kiln", self.player) + )) + + if self.options.late_basin_of_vows: + self._add_entrance_rule("Lothric Castle", lambda state: ( + state.has("Small Lothric Banner", self.player) + # Make sure these are actually available early. + and ( + "Transposing Kiln" not in randomized_items + or state.has("Transposing Kiln", self.player) + ) and ( + "Pyromancy Flame" not in randomized_items + or state.has("Pyromancy Flame", self.player) + ) + # This isn't really necessary, but it ensures that the game logic knows players will + # want to do Lothric Castle after at least being _able_ to access Catacombs. This is + # useful for smooth item placement. + and self._has_any_scroll(state) + )) + + if self.options.late_basin_of_vows > 1: # After Small Doll + self._add_entrance_rule("Lothric Castle", "Small Doll") # DLC Access Rules Below - if self.multiworld.enable_dlc[self.player]: - set_rule(self.multiworld.get_entrance("Go To Ringed City", self.player), - lambda state: state.has("Small Envoy Banner", self.player)) + if self.options.enable_dlc: + self._add_entrance_rule("Painted World of Ariandel (Before Contraption)", "CD -> PW1") + self._add_entrance_rule("Painted World of Ariandel (After Contraption)", "Contraption Key") + self._add_entrance_rule( + "Dreg Heap", + lambda state: self._can_get(state, "PW2: Soul of Sister Friede") + ) + self._add_entrance_rule("Ringed City", lambda state: ( + state.has("Small Envoy Banner", self.player) + and self._can_get(state, "DH: Soul of the Demon Prince") + )) - # If key items are randomized, must have contraption key to enter second half of Ashes DLC - # If key items are not randomized, Contraption Key is guaranteed to be accessible before it is needed - if self.multiworld.enable_key_locations[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 2", self.player), - lambda state: state.has("Contraption Key", self.player)) + if self.options.late_dlc: + self._add_entrance_rule( + "Painted World of Ariandel (Before Contraption)", + lambda state: state.has("Small Doll", self.player) and self._has_any_scroll(state)) - if self.multiworld.late_dlc[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_entrance("Go To Painted World of Ariandel 1", self.player), - lambda state: state.has("Small Doll", self.player)) + if self.options.late_dlc > 1: # After Basin + self._add_entrance_rule("Painted World of Ariandel (Before Contraption)", "Basin of Vows") # Define the access rules to some specific locations - set_rule(self.multiworld.get_location("PC: Cinders of a Lord - Yhorm the Giant", self.player), - lambda state: state.has("Storm Ruler", self.player)) - - if self.multiworld.enable_ring_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player), - lambda state: state.has("Jailbreaker's Key", self.player)) - set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player), - lambda state: state.has("Old Cell Key", self.player)) - set_rule(self.multiworld.get_location("UG: Hornet Ring", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - - if self.multiworld.enable_npc_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player), - lambda state: state.has("Cell Key", self.player)) - set_rule(self.multiworld.get_location("HWL: Blue Tearstone Ring", self.player), - lambda state: state.has("Cell Key", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Ashes", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Pointed Hat", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Coat", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Gloves", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - set_rule(self.multiworld.get_location("ID: Karla's Trousers", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - - if self.multiworld.enable_misc_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player), - lambda state: state.has("Jailer's Key Ring", self.player)) - - if self.multiworld.enable_boss_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("PC: Soul of Yhorm the Giant", self.player), - lambda state: state.has("Storm Ruler", self.player)) - set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), - lambda state: state.has("Basin of Vows", self.player)) - - # Lump Soul of the Dancer in with LC for locations that should not be reachable - # before having access to US. (Prevents requiring getting Basin to fight Dancer to get SLB to go to US) - if self.multiworld.late_basin_of_vows[self.player] == Toggle.option_true: - add_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - - gotthard_corpse_rule = lambda state: \ - (state.can_reach("AL: Cinders of a Lord - Aldrich", "Location", self.player) and - state.can_reach("PC: Cinders of a Lord - Yhorm the Giant", "Location", self.player)) - - set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), gotthard_corpse_rule) - - if self.multiworld.enable_weapon_locations[self.player] == Toggle.option_true: - set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), gotthard_corpse_rule) - - self.multiworld.completion_condition[self.player] = lambda state: \ - state.has("Cinders of a Lord - Abyss Watcher", self.player) and \ - state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \ - state.has("Cinders of a Lord - Aldrich", self.player) and \ - state.has("Cinders of a Lord - Lothric Prince", self.player) + if self._is_location_available("FS: Lift Chamber Key - Leonhard"): + self._add_location_rule("HWL: Red Eye Orb - wall tower, miniboss", + "Lift Chamber Key") + self._add_location_rule("ID: Bellowing Dragoncrest Ring - drop from B1 towards pit", + "Jailbreaker's Key") + self._add_location_rule("ID: Covetous Gold Serpent Ring - Siegward's cell", "Old Cell Key") + self._add_location_rule([ + "UG: Hornet Ring - environs, right of main path after killing FK boss", + "UG: Wolf Knight Helm - shop after killing FK boss", + "UG: Wolf Knight Armor - shop after killing FK boss", + "UG: Wolf Knight Gauntlets - shop after killing FK boss", + "UG: Wolf Knight Leggings - shop after killing FK boss" + ], lambda state: self._can_get(state, "FK: Cinders of a Lord - Abyss Watcher")) + self._add_location_rule( + "ID: Prisoner Chief's Ashes - B2 near, locked cell by stairs", + "Jailer's Key Ring" + ) + self._add_entrance_rule("Karla's Shop", "Jailer's Key Ring") + + # The static randomizer edits events to guarantee that Greirat won't go to Lothric until + # Grand Archives is available, so his shop will always be available one way or another. + self._add_entrance_rule("Greirat's Shop", "Cell Key") + + self._add_location_rule("HWL: Soul of the Dancer", "Basin of Vows") + + # Lump Soul of the Dancer in with LC for locations that should not be reachable + # before having access to US. (Prevents requiring getting Basin to fight Dancer to get SLB to go to US) + if self.options.late_basin_of_vows: + self._add_location_rule("HWL: Soul of the Dancer", lambda state: ( + state.has("Small Lothric Banner", self.player) + # Make sure these are actually available early. + and ( + "Transposing Kiln" not in randomized_items + or state.has("Transposing Kiln", self.player) + ) and ( + "Pyromancy Flame" not in randomized_items + or state.has("Pyromancy Flame", self.player) + ) + # This isn't really necessary, but it ensures that the game logic knows players will + # want to do Lothric Castle after at least being _able_ to access Catacombs. This is + # useful for smooth item placement. + and self._has_any_scroll(state) + )) + + if self.options.late_basin_of_vows > 1: # After Small Doll + self._add_location_rule("HWL: Soul of the Dancer", "Small Doll") + + self._add_location_rule([ + "LC: Grand Archives Key - by Grand Archives door, after PC and AL bosses", + "LC: Gotthard Twinswords - by Grand Archives door, after PC and AL bosses" + ], lambda state: ( + self._can_get(state, "AL: Cinders of a Lord - Aldrich") and + self._can_get(state, "PC: Cinders of a Lord - Yhorm the Giant") + )) + + self._add_location_rule([ + "FS: Morne's Great Hammer - Eygon", + "FS: Moaning Shield - Eygon" + ], lambda state: ( + self._can_get(state, "LC: Soul of Dragonslayer Armour") and + self._can_get(state, "FK: Soul of the Blood of the Wolf") + )) + + self._add_location_rule([ + "CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPC", + "CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC", + ], lambda state: self._can_go_to(state, "Archdragon Peak")) + + self._add_location_rule([ + "FK: Havel's Helm - upper keep, after killing AP belfry roof NPC", + "FK: Havel's Armor - upper keep, after killing AP belfry roof NPC", + "FK: Havel's Gauntlets - upper keep, after killing AP belfry roof NPC", + "FK: Havel's Leggings - upper keep, after killing AP belfry roof NPC", + ], lambda state: self._can_go_to(state, "Archdragon Peak")) + + self._add_location_rule([ + "RC: Dragonhead Shield - streets monument, across bridge", + "RC: Large Soul of a Crestfallen Knight - streets monument, across bridge", + "RC: Divine Blessing - streets monument, mob drop", + "RC: Lapp's Helm - Lapp", + "RC: Lapp's Armor - Lapp", + "RC: Lapp's Gauntlets - Lapp", + "RC: Lapp's Leggings - Lapp", + ], "Chameleon") + + # Forbid shops from carrying items with multiple counts (the static randomizer has its own + # logic for choosing how many shop items to sell), and from carrying soul items. + for location in location_dictionary.values(): + if location.shop: + self._add_item_rule( + location.name, + lambda item: ( + item.player != self.player or + (item.data.count == 1 and not item.data.souls) + ) + ) + + # This particular location is bugged, and will drop two copies of whatever item is placed + # there. + if self._is_location_available("US: Young White Branch - by white tree #2"): + self._add_item_rule( + "US: Young White Branch - by white tree #2", + lambda item: item.player == self.player and not item.data.unique + ) + + # Make sure the Storm Ruler is available BEFORE Yhorm the Giant + if self.yhorm_location.name == "Ancient Wyvern": + # This is a white lie, you can get to a bunch of items in AP before you beat the Wyvern, + # but this saves us from having to split the entire region in two just to mark which + # specific items are before and after. + self._add_entrance_rule("Archdragon Peak", "Storm Ruler") + for location in self.yhorm_location.locations: + self._add_location_rule(location, "Storm Ruler") + + self.multiworld.completion_condition[self.player] = lambda state: self._can_get(state, "KFF: Soul of the Lords") + + def _add_shop_rules(self) -> None: + """Adds rules for items unlocked in shops.""" + + # Ashes + ashes = { + "Mortician's Ashes": ["Alluring Skull", "Ember", "Grave Key"], + "Dreamchaser's Ashes": ["Life Ring", "Hidden Blessing"], + "Paladin's Ashes": ["Lloyd's Shield Ring"], + "Grave Warden's Ashes": ["Ember"], + "Prisoner Chief's Ashes": [ + "Karla's Pointed Hat", "Karla's Coat", "Karla's Gloves", "Karla's Trousers" + ], + "Xanthous Ashes": ["Xanthous Overcoat", "Xanthous Gloves", "Xanthous Trousers"], + "Dragon Chaser's Ashes": ["Ember"], + "Easterner's Ashes": [ + "Washing Pole", "Eastern Helm", "Eastern Armor", "Eastern Gauntlets", + "Eastern Leggings", "Wood Grain Ring", + ], + "Captain's Ashes": [ + "Millwood Knight Helm", "Millwood Knight Armor", "Millwood Knight Gauntlets", + "Millwood Knight Leggings", "Refined Gem", + ] + } + for (ash, items) in ashes.items(): + self._add_location_rule([f"FS: {item} - {ash}" for item in items], ash) + + # Shop unlocks + shop_unlocks = { + "Cornyx": [ + ( + "Great Swamp Pyromancy Tome", "Great Swamp Tome", + ["Poison Mist", "Fire Orb", "Profuse Sweat", "Bursting Fireball"] + ), + ( + "Carthus Pyromancy Tome", "Carthus Tome", + ["Acid Surge", "Carthus Flame Arc", "Carthus Beacon"] + ), + ("Izalith Pyromancy Tome", "Izalith Tome", ["Great Chaos Fire Orb", "Chaos Storm"]), + ], + "Irina": [ + ( + "Braille Divine Tome of Carim", "Tome of Carim", + ["Med Heal", "Tears of Denial", "Force"] + ), + ( + "Braille Divine Tome of Lothric", "Tome of Lothric", + ["Bountiful Light", "Magic Barrier", "Blessed Weapon"] + ), + ], + "Orbeck": [ + ("Sage's Scroll", "Sage's Scroll", ["Great Farron Dart", "Farron Hail"]), + ( + "Golden Scroll", "Golden Scroll", + [ + "Cast Light", "Repair", "Hidden Weapon", "Hidden Body", + "Twisted Wall of Light" + ], + ), + ("Logan's Scroll", "Logan's Scroll", ["Homing Soulmass", "Soul Spear"]), + ( + "Crystal Scroll", "Crystal Scroll", + ["Homing Crystal Soulmass", "Crystal Soul Spear", "Crystal Magic Weapon"] + ), + ], + "Karla": [ + ("Quelana Pyromancy Tome", "Quelana Tome", ["Firestorm", "Rapport", "Fire Whip"]), + ( + "Grave Warden Pyromancy Tome", "Grave Warden Tome", + ["Black Flame", "Black Fire Orb"] + ), + ("Deep Braille Divine Tome", "Deep Braille Tome", ["Gnaw", "Deep Protection"]), + ( + "Londor Braille Divine Tome", "Londor Tome", + ["Vow of Silence", "Dark Blade", "Dead Again"] + ), + ], + } + for (shop, unlocks) in shop_unlocks.items(): + for (key, key_name, items) in unlocks: + self._add_location_rule( + [f"FS: {item} - {shop} for {key_name}" for item in items], key) + + def _add_npc_rules(self) -> None: + """Adds rules for items accessible via NPC quests. + + We list missable locations here even though they never contain progression items so that the + game knows what sphere they're in. This is especially useful for item smoothing. (We could + add rules for boss transposition items as well, but then we couldn't freely reorder boss + soul locations for smoothing.) + + Generally, for locations that can be accessed early by killing NPCs, we set up requirements + assuming the player _doesn't_ so they aren't forced to start killing allies to advance the + quest. + """ + + ## Greirat + + self._add_location_rule([ + "FS: Divine Blessing - Greirat from US", + "FS: Ember - Greirat from US", + ], lambda state: ( + self._can_go_to(state, "Undead Settlement") + and state.has("Loretta's Bone", self.player) + )) + self._add_location_rule([ + "FS: Divine Blessing - Greirat from IBV", + "FS: Hidden Blessing - Greirat from IBV", + "FS: Titanite Scale - Greirat from IBV", + "FS: Twinkling Titanite - Greirat from IBV", + "FS: Ember - shop for Greirat's Ashes" + ], lambda state: ( + self._can_go_to(state, "Irithyll of the Boreal Valley") + and self._can_get(state, "FS: Divine Blessing - Greirat from US") + # Either Patches or Siegward can save Greirat, but we assume the player will want to use + # Patches because it's harder to screw up + and self._can_get(state, "CD: Shotel - Patches") + )) + self._add_location_rule([ + "FS: Ember - shop for Greirat's Ashes", + ], lambda state: ( + self._can_go_to(state, "Grand Archives") + and self._can_get(state, "FS: Divine Blessing - Greirat from IBV") + )) + + ## Patches + + # Patches will only set up shop in Firelink once he's tricked you in the bell tower. He'll + # only do _that_ once you've spoken to Siegward after killing the Fire Demon and lit the + # Rosaria's Bed Chamber bonfire. He _won't_ set up shop in the Cathedral if you light the + # Rosaria's Bed Chamber bonfire before getting tricked by him, so we assume these locations + # require the bell tower. + self._add_location_rule([ + "CD: Shotel - Patches", + "CD: Ember - Patches", + "FS: Rusted Gold Coin - don't forgive Patches" + ], lambda state: ( + self._can_go_to(state, "Firelink Shrine Bell Tower") + and self._can_go_to(state, "Cathedral of the Deep") + )) + + # Patches sells this after you tell him to search for Greirat in Grand Archives + self._add_location_rule([ + "FS: Hidden Blessing - Patches after searching GA" + ], lambda state: ( + self._can_get(state, "CD: Shotel - Patches") + and self._can_get(state, "FS: Ember - shop for Greirat's Ashes") + )) + + # Only make the player kill Patches once all his other items are available + self._add_location_rule([ + "CD: Winged Spear - kill Patches", + # You don't _have_ to kill him for this, but he has to be in Firelink at the same time + # as Greirat to get it in the shop and that may not be feasible if the player progresses + # Greirat's quest much faster. + "CD: Horsehoof Ring - Patches", + ], lambda state: ( + self._can_get(state, "FS: Hidden Blessing - Patches after searching GA") + and self._can_get(state, "FS: Rusted Gold Coin - don't forgive Patches") + )) + + ## Leonhard + + self._add_location_rule([ + # Talk to Leonhard in Firelink with a Pale Tongue after lighting Cliff Underside or + # killing Greatwood. This doesn't consume the Pale Tongue, it just has to be in + # inventory + "FS: Lift Chamber Key - Leonhard", + # Progress Leonhard's quest and then return to Rosaria after lighting Profaned Capital + "CD: Black Eye Orb - Rosaria from Leonhard's quest", + ], "Pale Tongue") + + self._add_location_rule([ + "CD: Black Eye Orb - Rosaria from Leonhard's quest", + ], lambda state: ( + # The Black Eye Orb location won't spawn until you kill the HWL miniboss and resting at + # the Profaned Capital bonfire. + self._can_get(state, "HWL: Red Eye Orb - wall tower, miniboss") + and self._can_go_to(state, "Profaned Capital") + )) + + # Perhaps counterintuitively, you CAN fight Leonhard before you access the location that + # would normally give you the Black Eye Orb. + self._add_location_rule([ + "AL: Crescent Moon Sword - Leonhard drop", + "AL: Silver Mask - Leonhard drop", + "AL: Soul of Rosaria - Leonhard drop", + ] + [ + f"FS: {item} - shop after killing Leonhard" + for item in ["Leonhard's Garb", "Leonhard's Gauntlets", "Leonhard's Trousers"] + ], "Black Eye Orb") + + ## Hawkwood + + # After Hawkwood leaves and once you have the Torso Stone, you can fight him for dragon + # stones. Andre will give Swordgrass as a hint as well + self._add_location_rule([ + "FK: Twinkling Dragon Head Stone - Hawkwood drop", + "FS: Hawkwood's Swordgrass - Andre after gesture in AP summit" + ], lambda state: ( + self._can_get(state, "FS: Hawkwood's Shield - gravestone after Hawkwood leaves") + and state.has("Twinkling Dragon Torso Stone", self.player) + )) + + ## Siegward + + # Unlock Siegward's cell after progressing his quest + self._add_location_rule([ + "ID: Titanite Slab - Siegward", + ], lambda state: ( + state.has("Old Cell Key", self.player) + # Progressing Siegward's quest requires buying his armor from Patches. + and self._can_get(state, "CD: Shotel - Patches") + )) + + # These drop after completing Siegward's quest and talking to him in Yhorm's arena + self._add_location_rule([ + "PC: Siegbräu - Siegward after killing boss", + "PC: Storm Ruler - Siegward", + "PC: Pierce Shield - Siegward", + ], lambda state: ( + self._can_get(state, "ID: Titanite Slab - Siegward") + and self._can_get(state, "PC: Soul of Yhorm the Giant") + )) + + ## Sirris + + # Kill Greatwood and turn in Dreamchaser's Ashes to trigger this opportunity for invasion + self._add_location_rule([ + "FS: Mail Breaker - Sirris for killing Creighton", + "FS: Silvercat Ring - Sirris for killing Creighton", + "IBV: Creighton's Steel Mask - bridge after killing Creighton", + "IBV: Mirrah Chain Gloves - bridge after killing Creighton", + "IBV: Mirrah Chain Leggings - bridge after killing Creighton", + "IBV: Mirrah Chain Mail - bridge after killing Creighton", + "IBV: Dragonslayer's Axe - Creighton drop", + # Killing Pontiff without progressing Sirris's quest will break it. + "IBV: Soul of Pontiff Sulyvahn" + ], lambda state: ( + self._can_get(state, "US: Soul of the Rotted Greatwood") + and state.has("Dreamchaser's Ashes", self.player) + )) + # Add indirect condition since reaching AL requires defeating Pontiff which requires defeating Greatwood in US + self.multiworld.register_indirect_condition( + self.get_region("Undead Settlement"), + self.get_entrance("Go To Anor Londo") + ) + + # Kill Creighton and Aldrich to trigger this opportunity for invasion + self._add_location_rule([ + "FS: Budding Green Blossom - shop after killing Creighton and AL boss", + "FS: Sunset Shield - by grave after killing Hodrick w/Sirris", + "US: Sunset Helm - Pit of Hollows after killing Hodrick w/Sirris", + "US: Sunset Armor - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Gauntlets - pit of hollows after killing Hodrick w/Sirris", + "US: Sunset Leggings - pit of hollows after killing Hodrick w/Sirris", + ], lambda state: ( + self._can_get(state, "FS: Mail Breaker - Sirris for killing Creighton") + and self._can_get(state, "AL: Soul of Aldrich") + )) + + # Kill Hodrick and Twin Princes to trigger the end of the quest + self._add_location_rule([ + "FS: Sunless Talisman - Sirris, kill GA boss", + "FS: Sunless Veil - shop, Sirris quest, kill GA boss", + "FS: Sunless Armor - shop, Sirris quest, kill GA boss", + "FS: Sunless Gauntlets - shop, Sirris quest, kill GA boss", + "FS: Sunless Leggings - shop, Sirris quest, kill GA boss", + # Killing Yorshka will anger Sirris and stop her quest, so don't expect it until the + # quest is done + "AL: Yorshka's Chime - kill Yorshka", + ], lambda state: ( + self._can_get(state, "US: Soul of the Rotted Greatwood") + and state.has("Dreamchaser's Ashes", self.player) + )) + + ## Cornyx + + self._add_location_rule([ + "US: Old Sage's Blindfold - kill Cornyx", + "US: Cornyx's Garb - kill Cornyx", + "US: Cornyx's Wrap - kill Cornyx", + "US: Cornyx's Skirt - kill Cornyx", + ], lambda state: ( + state.has("Great Swamp Pyromancy Tome", self.player) + and state.has("Carthus Pyromancy Tome", self.player) + and state.has("Izalith Pyromancy Tome", self.player) + )) + + self._add_location_rule([ + "US: Old Sage's Blindfold - kill Cornyx", "US: Cornyx's Garb - kill Cornyx", + "US: Cornyx's Wrap - kill Cornyx", "US: Cornyx's Skirt - kill Cornyx" + ], lambda state: ( + state.has("Great Swamp Pyromancy Tome", self.player) + and state.has("Carthus Pyromancy Tome", self.player) + and state.has("Izalith Pyromancy Tome", self.player) + )) + + ## Irina + + self._add_location_rule([ + "US: Tower Key - kill Irina", + ], lambda state: ( + state.has("Braille Divine Tome of Carim", self.player) + and state.has("Braille Divine Tome of Lothric", self.player) + )) + + ## Karla + + self._add_location_rule([ + "FS: Karla's Pointed Hat - kill Karla", + "FS: Karla's Coat - kill Karla", + "FS: Karla's Gloves - kill Karla", + "FS: Karla's Trousers - kill Karla", + ], lambda state: ( + state.has("Quelana Pyromancy Tome", self.player) + and state.has("Grave Warden Pyromancy Tome", self.player) + and state.has("Deep Braille Divine Tome", self.player) + and state.has("Londor Braille Divine Tome", self.player) + )) + + ## Emma + + self._add_location_rule("HWL: Basin of Vows - Emma", "Small Doll") + + ## Orbeck + + self._add_location_rule([ + "FS: Morion Blade - Yuria for Orbeck's Ashes", + "FS: Clandestine Coat - shop with Orbeck's Ashes" + ], lambda state: ( + state.has("Golden Scroll", self.player) + and state.has("Logan's Scroll", self.player) + and state.has("Crystal Scroll", self.player) + and state.has("Sage's Scroll", self.player) + )) + + self._add_location_rule([ + "FS: Pestilent Mist - Orbeck for any scroll", + "FS: Young Dragon Ring - Orbeck for one scroll and buying three spells", + # Make sure that the player can keep Orbeck around by giving him at least one scroll + # before killing Abyss Watchers. + "FK: Soul of the Blood of the Wolf", + "FK: Cinders of a Lord - Abyss Watcher", + "FS: Undead Legion Helm - shop after killing FK boss", + "FS: Undead Legion Armor - shop after killing FK boss", + "FS: Undead Legion Gauntlet - shop after killing FK boss", + "FS: Undead Legion Leggings - shop after killing FK boss", + "FS: Farron Ring - Hawkwood", + "FS: Hawkwood's Shield - gravestone after Hawkwood leaves", + "UG: Hornet Ring - environs, right of main path after killing FK boss", + "UG: Wolf Knight Helm - shop after killing FK boss", + "UG: Wolf Knight Armor - shop after killing FK boss", + "UG: Wolf Knight Gauntlets - shop after killing FK boss", + "UG: Wolf Knight Leggings - shop after killing FK boss", + ], self._has_any_scroll) + + # Not really necessary but ensures players can decide which way to go + if self.options.enable_dlc: + self._add_entrance_rule( + "Painted World of Ariandel (After Contraption)", + self._has_any_scroll + ) + + ## Anri + + # Anri only leaves Road of Sacrifices once Deacons is defeated + self._add_location_rule([ + "IBV: Ring of the Evil Eye - Anri", + "AL: Chameleon - tomb after marrying Anri", + ], lambda state: self._can_get(state, "CD: Soul of the Deacons of the Deep")) + + # If the player does Anri's non-marriage quest, they'll need to defeat the AL boss as well + # before it's complete. + self._add_location_rule([ + "AL: Anri's Straight Sword - Anri quest", + "FS: Elite Knight Helm - shop after Anri quest", + "FS: Elite Knight Armor - shop after Anri quest", + "FS: Elite Knight Gauntlets - shop after Anri quest", + "FS: Elite Knight Leggings - shop after Anri quest", + ], lambda state: ( + self._can_get(state, "IBV: Ring of the Evil Eye - Anri") and + self._can_get(state, "AL: Soul of Aldrich") + )) + + def _add_transposition_rules(self) -> None: + """Adds rules for items obtainable from Ludleth by soul transposition.""" + + transpositions = [ + ( + "Soul of Boreal Valley Vordt", "Vordt", + ["Vordt's Great Hammer", "Pontiff's Left Eye"] + ), + ("Soul of Rosaria", "Rosaria", ["Bountiful Sunlight"]), + ("Soul of Aldrich", "Aldrich", ["Darkmoon Longbow", "Lifehunt Scythe"]), + ( + "Soul of the Rotted Greatwood", "Greatwood", + ["Hollowslayer Greatsword", "Arstor's Spear"] + ), + ("Soul of a Crystal Sage", "Sage", ["Crystal Sage's Rapier", "Crystal Hail"]), + ("Soul of the Deacons of the Deep", "Deacons", ["Cleric's Candlestick", "Deep Soul"]), + ("Soul of a Stray Demon", "Stray Demon", ["Havel's Ring", "Boulder Heave"]), + ( + "Soul of the Blood of the Wolf", "Abyss Watchers", + ["Farron Greatsword", "Wolf Knight's Greatsword"] + ), + ("Soul of High Lord Wolnir", "Wolnir", ["Wolnir's Holy Sword", "Black Serpent"]), + ("Soul of a Demon", "Fire Demon", ["Demon's Greataxe", "Demon's Fist"]), + ( + "Soul of the Old Demon King", "Old Demon King", + ["Old King's Great Hammer", "Chaos Bed Vestiges"] + ), + ( + "Soul of Pontiff Sulyvahn", "Pontiff", + ["Greatsword of Judgment", "Profaned Greatsword"] + ), + ("Soul of Yhorm the Giant", "Yhorm", ["Yhorm's Great Machete", "Yhorm's Greatshield"]), + ("Soul of the Dancer", "Dancer", ["Dancer's Enchanted Swords", "Soothing Sunlight"]), + ( + "Soul of Dragonslayer Armour", "Dragonslayer", + ["Dragonslayer Greataxe", "Dragonslayer Greatshield"] + ), + ( + "Soul of Consumed Oceiros", "Oceiros", + ["Moonlight Greatsword", "White Dragon Breath"] + ), + ( + "Soul of the Twin Princes", "Princes", + ["Lorian's Greatsword", "Lothric's Holy Sword"] + ), + ("Soul of Champion Gundyr", "Champion", ["Gundyr's Halberd", "Prisoner's Chain"]), + ( + "Soul of the Nameless King", "Nameless", + ["Storm Curved Sword", "Dragonslayer Swordspear", "Lightning Storm"] + ), + ("Soul of the Lords", "Cinder", ["Firelink Greatsword", "Sunlight Spear"]), + ("Soul of Sister Friede", "Friede", ["Friede's Great Scythe", "Rose of Ariandel"]), + ("Soul of the Demon Prince", "Demon Prince", ["Demon's Scar", "Seething Chaos"]), + ("Soul of Darkeater Midir", "Midir", ["Frayed Blade", "Old Moonlight"]), + ("Soul of Slave Knight Gael", "Gael", ["Gael's Greatsword", "Repeating Crossbow"]), + ] + for (soul, soul_name, items) in transpositions: + self._add_location_rule([ + f"FS: {item} - Ludleth for {soul_name}" for item in items + ], lambda state, s=soul: ( + state.has(s, self.player) and state.has("Transposing Kiln", self.player) + )) + + def _add_crow_rules(self) -> None: + """Adds rules for items obtainable by trading items to the crow on Firelink roof.""" + + crow = { + "Loretta's Bone": "Ring of Sacrifice", + # "Avelyn": "Titanite Scale", # Missing from static randomizer + "Coiled Sword Fragment": "Titanite Slab", + "Seed of a Giant Tree": "Iron Leggings", + "Siegbräu": "Armor of the Sun", + # Static randomizer can't randomize Hodrick's drop yet + # "Vertebra Shackle": "Lucatiel's Mask", + "Xanthous Crown": "Lightning Gem", + "Mendicant's Staff": "Sunlight Shield", + "Blacksmith Hammer": "Titanite Scale", + "Large Leather Shield": "Twinkling Titanite", + "Moaning Shield": "Blessed Gem", + "Eleonora": "Hollow Gem", + } + for (given, received) in crow.items(): + name = f"FSBT: {received} - crow for {given}" + self._add_location_rule(name, given) + + # Don't let crow items have foreign items because they're picked up in a way that's + # missed by the hook we use to send location items + self._add_item_rule(name, lambda item: ( + item.player == self.player + # Because of the weird way they're delivered, crow items don't seem to support + # infused or upgraded weapons. + and not item.data.is_infused + and not item.data.is_upgraded + )) + + def _add_allow_useful_location_rules(self) -> None: + """Adds rules for locations that can contain useful but not necessary items. + + If we allow useful items in the excluded locations, we don't want Archipelago's fill + algorithm to consider them excluded because it never allows useful items there. Instead, we + manually add item rules to exclude important items. + """ + + all_locations = self._get_our_locations() + + allow_useful_locations = ( + ( + { + location.name + for location in all_locations + if location.name in self.all_excluded_locations + and not location.data.missable + } + if self.options.excluded_location_behavior < self.options.missable_location_behavior + else self.all_excluded_locations + ) + if self.options.excluded_location_behavior == "allow_useful" + else set() + ).union( + { + location.name + for location in all_locations + if location.data.missable + and not ( + location.name in self.all_excluded_locations + and self.options.missable_location_behavior < + self.options.excluded_location_behavior + ) + } + if self.options.missable_location_behavior == "allow_useful" + else set() + ) + for location in allow_useful_locations: + self._add_item_rule( + location, + lambda item: not item.advancement + ) + + if self.options.excluded_location_behavior == "allow_useful": + self.options.exclude_locations.value.clear() + + def _add_early_item_rules(self, randomized_items: Set[str]) -> None: + """Adds rules to make sure specific items are available early.""" + + if "Pyromancy Flame" in randomized_items: + # Make this available early because so many items are useless without it. + self._add_entrance_rule("Road of Sacrifices", "Pyromancy Flame") + self._add_entrance_rule("Consumed King's Garden", "Pyromancy Flame") + self._add_entrance_rule("Grand Archives", "Pyromancy Flame") + if "Transposing Kiln" in randomized_items: + # Make this available early so players can make use of their boss souls. + self._add_entrance_rule("Road of Sacrifices", "Transposing Kiln") + self._add_entrance_rule("Consumed King's Garden", "Transposing Kiln") + self._add_entrance_rule("Grand Archives", "Transposing Kiln") + # Make this available pretty early + if "Small Lothric Banner" in randomized_items: + if self.options.early_banner == "early_global": + self.multiworld.early_items[self.player]["Small Lothric Banner"] = 1 + elif self.options.early_banner == "early_local": + self.multiworld.local_early_items[self.player]["Small Lothric Banner"] = 1 + + def _has_any_scroll(self, state: CollectionState) -> bool: + """Returns whether the given state has any scroll item.""" + return ( + state.has("Sage's Scroll", self.player) + or state.has("Golden Scroll", self.player) + or state.has("Logan's Scroll", self.player) + or state.has("Crystal Scroll", self.player) + ) + + def _add_location_rule(self, location: Union[str, List[str]], rule: Union[CollectionRule, str]) -> None: + """Sets a rule for the given location if it that location is randomized. + + The rule can just be a single item/event name as well as an explicit rule lambda. + """ + locations = location if isinstance(location, list) else [location] + for location in locations: + data = location_dictionary[location] + if data.dlc and not self.options.enable_dlc: return + if data.ngp and not self.options.enable_ngp: return + + if not self._is_location_available(location): return + if isinstance(rule, str): + assert item_dictionary[rule].classification == ItemClassification.progression + rule = lambda state, item=rule: state.has(item, self.player) + add_rule(self.multiworld.get_location(location, self.player), rule) + + def _add_entrance_rule(self, region: str, rule: Union[CollectionRule, str]) -> None: + """Sets a rule for the entrance to the given region.""" + assert region in location_tables + if not any(region == reg for reg in self.multiworld.regions.region_cache[self.player]): return + if isinstance(rule, str): + if " -> " not in rule: + assert item_dictionary[rule].classification == ItemClassification.progression + rule = lambda state, item=rule: state.has(item, self.player) + add_rule(self.multiworld.get_entrance("Go To " + region, self.player), rule) + + def _add_item_rule(self, location: str, rule: ItemRule) -> None: + """Sets a rule for what items are allowed in a given location.""" + if not self._is_location_available(location): return + add_item_rule(self.multiworld.get_location(location, self.player), rule) + + def _can_go_to(self, state, region) -> bool: + """Returns whether state can access the given region name.""" + return state.can_reach_entrance(f"Go To {region}", self.player) + + def _can_get(self, state, location) -> bool: + """Returns whether state can access the given location name.""" + return state.can_reach_location(location, self.player) + + def _is_location_available( + self, + location: Union[str, DS3LocationData, DarkSouls3Location] + ) -> bool: + """Returns whether the given location is being randomized.""" + if isinstance(location, DS3LocationData): + data = location + elif isinstance(location, DarkSouls3Location): + data = location.data + else: + data = location_dictionary[location] + + return ( + not data.is_event + and (not data.dlc or bool(self.options.enable_dlc)) + and (not data.ngp or bool(self.options.enable_ngp)) + and not ( + self.options.excluded_location_behavior == "do_not_randomize" + and data.name in self.all_excluded_locations + ) + and not ( + self.options.missable_location_behavior == "do_not_randomize" + and data.missable + ) + ) + + def write_spoiler(self, spoiler_handle: TextIO) -> None: + text = "" + + if self.yhorm_location != default_yhorm_location: + text += f"\nYhorm takes the place of {self.yhorm_location.name} in {self.player_name}'s world\n" + + if self.options.excluded_location_behavior == "allow_useful": + text += f"\n{self.player_name}'s world excluded: {sorted(self.all_excluded_locations)}\n" + + if text: + text = "\n" + text + "\n" + spoiler_handle.write(text) + + def post_fill(self): + """If item smoothing is enabled, rearrange items so they scale up smoothly through the run. + + This determines the approximate order a given silo of items (say, soul items) show up in the + main game, then rearranges their shuffled placements to match that order. It determines what + should come "earlier" or "later" based on sphere order: earlier spheres get lower-level + items, later spheres get higher-level ones. Within a sphere, items in DS3 are distributed in + region order, and then the best items in a sphere go into the multiworld. + """ + + locations_by_sphere = [ + sorted(loc for loc in sphere if loc.item.player == self.player and not loc.locked) + for sphere in self.multiworld.get_spheres() + ] + + # All items in the base game in approximately the order they appear + all_item_order: List[DS3ItemData] = [ + item_dictionary[location.default_item_name] + for region in region_order + # Shuffle locations within each region. + for location in self._shuffle(location_tables[region]) + if self._is_location_available(location) + ] + + # All DarkSouls3Items for this world that have been assigned anywhere, grouped by name + full_items_by_name: Dict[str, List[DarkSouls3Item]] = defaultdict(list) + for location in self.multiworld.get_filled_locations(): + if location.item.player == self.player and ( + location.player != self.player or self._is_location_available(location) + ): + full_items_by_name[location.item.name].append(location.item) + + def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None: + """Rearrange all items in item_order to match that order. + + Note: this requires that item_order exactly matches the number of placed items from this + world matching the given names. + """ + + # Convert items to full DarkSouls3Items. + converted_item_order: List[DarkSouls3Item] = [ + item for item in ( + ( + # full_items_by_name won't contain DLC items if the DLC is disabled. + (full_items_by_name[item.name] or [None]).pop(0) + if isinstance(item, DS3ItemData) else item + ) + for item in item_order + ) + # Never re-order event items, because they weren't randomized in the first place. + if item and item.code is not None + ] + + names = {item.name for item in converted_item_order} + + all_matching_locations = [ + loc + for sphere in locations_by_sphere + for loc in sphere + if loc.item.name in names + ] + + # It's expected that there may be more total items than there are matching locations if + # the player has chosen a more limited accessibility option, since the matching + # locations *only* include items in the spheres of accessibility. + if len(converted_item_order) < len(all_matching_locations): + raise Exception( + f"DS3 bug: there are {len(all_matching_locations)} locations that can " + + f"contain smoothed items, but only {len(converted_item_order)} items to smooth." + ) + for sphere in locations_by_sphere: + locations = [loc for loc in sphere if loc.item.name in names] + + # Check the game, not the player, because we know how to sort within regions for DS3 + offworld = self._shuffle([loc for loc in locations if loc.game != "Dark Souls III"]) + onworld = sorted((loc for loc in locations if loc.game == "Dark Souls III"), + key=lambda loc: loc.data.region_value) + + # Give offworld regions the last (best) items within a given sphere + for location in onworld + offworld: + new_item = self._pop_item(location, converted_item_order) + location.item = new_item + new_item.location = location + + if self.options.smooth_upgrade_items: + base_names = { + "Titanite Shard", "Large Titanite Shard", "Titanite Chunk", "Titanite Slab", + "Titanite Scale", "Twinkling Titanite", "Farron Coal", "Sage's Coal", "Giant's Coal", + "Profaned Coal" + } + smooth_items([item for item in all_item_order if item.base_name in base_names]) + + if self.options.smooth_soul_items: + smooth_items([ + item for item in all_item_order + if item.souls and item.classification != ItemClassification.progression + ]) + + if self.options.smooth_upgraded_weapons: + upgraded_weapons = [ + location.item + for location in self.multiworld.get_filled_locations() + if location.item.player == self.player + and location.item.level and location.item.level > 0 + and location.item.classification != ItemClassification.progression + ] + upgraded_weapons.sort(key=lambda item: item.level) + smooth_items(upgraded_weapons) + + def _shuffle(self, seq: Sequence) -> List: + """Returns a shuffled copy of a sequence.""" + copy = list(seq) + self.random.shuffle(copy) + return copy + + def _pop_item( + self, + location: Location, + items: List[DarkSouls3Item] + ) -> DarkSouls3Item: + """Returns the next item in items that can be assigned to location.""" + for i, item in enumerate(items): + if location.can_fill(self.multiworld.state, item, False): + return items.pop(i) + + # If we can't find a suitable item, give up and assign an unsuitable one. + return items.pop(0) + + def _get_our_locations(self) -> List[DarkSouls3Location]: + return cast(List[DarkSouls3Location], self.multiworld.get_locations(self.player)) def fill_slot_data(self) -> Dict[str, object]: slot_data: Dict[str, object] = {} - # Depending on the specified option, modify items hexadecimal value to add an upgrade level or infusion - name_to_ds3_code = {item.name: item.ds3_code for item in item_dictionary.values()} - - # Randomize some weapon upgrades - if self.multiworld.randomize_weapon_level[self.player] != RandomizeWeaponLevelOption.option_none: - # if the user made an error and set a min higher than the max we default to the max - max_5 = self.multiworld.max_levels_in_5[self.player] - min_5 = min(self.multiworld.min_levels_in_5[self.player], max_5) - max_10 = self.multiworld.max_levels_in_10[self.player] - min_10 = min(self.multiworld.min_levels_in_10[self.player], max_10) - weapon_level_percentage = self.multiworld.randomize_weapon_level_percentage[self.player] - - for item in item_dictionary.values(): - if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < weapon_level_percentage: - if item.category == DS3ItemCategory.WEAPON_UPGRADE_5: - name_to_ds3_code[item.name] += self.multiworld.per_slot_randoms[self.player].randint(min_5, max_5) - elif item.category in {DS3ItemCategory.WEAPON_UPGRADE_10, DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE}: - name_to_ds3_code[item.name] += self.multiworld.per_slot_randoms[self.player].randint(min_10, max_10) - - # Randomize some weapon infusions - if self.multiworld.randomize_infusion[self.player] == Toggle.option_true: - infusion_percentage = self.multiworld.randomize_infusion_percentage[self.player] - for item in item_dictionary.values(): - if item.category in {DS3ItemCategory.WEAPON_UPGRADE_10_INFUSIBLE, DS3ItemCategory.SHIELD_INFUSIBLE}: - if self.multiworld.per_slot_randoms[self.player].randint(0, 99) < infusion_percentage: - name_to_ds3_code[item.name] += 100 * self.multiworld.per_slot_randoms[self.player].randint(0, 15) - - # Create the mandatory lists to generate the player's output file - items_id = [] - items_address = [] - locations_id = [] - locations_address = [] - locations_target = [] - for location in self.multiworld.get_filled_locations(): - # Skip events - if location.item.code is None: - continue - - if location.item.player == self.player: - items_id.append(location.item.code) - items_address.append(name_to_ds3_code[location.item.name]) - - if location.player == self.player: - locations_address.append(item_dictionary[location_dictionary[location.name].default_item].ds3_code) - locations_id.append(location.address) - if location.item.player == self.player: - locations_target.append(name_to_ds3_code[location.item.name]) - else: - locations_target.append(0) + # Once all clients support overlapping item IDs, adjust the DS3 AP item IDs to encode the + # in-game ID as well as the count so that we don't need to send this information at all. + # + # We include all the items the game knows about so that users can manually request items + # that aren't randomized, and then we _also_ include all the items that are placed in + # practice `item_dictionary.values()` doesn't include upgraded or infused weapons. + all_items = { + cast(DarkSouls3Item, location.item).data + for location in self.multiworld.get_filled_locations() + # item.code None is used for events, which we want to skip + if location.item.code is not None and location.item.player == self.player + }.union(item_dictionary.values()) + + ap_ids_to_ds3_ids: Dict[str, int] = {} + item_counts: Dict[str, int] = {} + for item in all_items: + if item.ap_code is None: continue + if item.ds3_code: ap_ids_to_ds3_ids[str(item.ap_code)] = item.ds3_code + if item.count != 1: item_counts[str(item.ap_code)] = item.count + + # A map from Archipelago's location IDs to the keys the static randomizer uses to identify + # locations. + location_ids_to_keys: Dict[int, str] = {} + for location in cast(List[DarkSouls3Location], self.multiworld.get_filled_locations(self.player)): + # Skip events and only look at this world's locations + if (location.address is not None and location.item.code is not None + and location.data.static): + location_ids_to_keys[location.address] = location.data.static slot_data = { "options": { - "enable_weapon_locations": self.multiworld.enable_weapon_locations[self.player].value, - "enable_shield_locations": self.multiworld.enable_shield_locations[self.player].value, - "enable_armor_locations": self.multiworld.enable_armor_locations[self.player].value, - "enable_ring_locations": self.multiworld.enable_ring_locations[self.player].value, - "enable_spell_locations": self.multiworld.enable_spell_locations[self.player].value, - "enable_key_locations": self.multiworld.enable_key_locations[self.player].value, - "enable_boss_locations": self.multiworld.enable_boss_locations[self.player].value, - "enable_npc_locations": self.multiworld.enable_npc_locations[self.player].value, - "enable_misc_locations": self.multiworld.enable_misc_locations[self.player].value, - "auto_equip": self.multiworld.auto_equip[self.player].value, - "lock_equip": self.multiworld.lock_equip[self.player].value, - "no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value, - "death_link": self.multiworld.death_link[self.player].value, - "no_spell_requirements": self.multiworld.no_spell_requirements[self.player].value, - "no_equip_load": self.multiworld.no_equip_load[self.player].value, - "enable_dlc": self.multiworld.enable_dlc[self.player].value + "random_starting_loadout": self.options.random_starting_loadout.value, + "require_one_handed_starting_weapons": self.options.require_one_handed_starting_weapons.value, + "auto_equip": self.options.auto_equip.value, + "lock_equip": self.options.lock_equip.value, + "no_weapon_requirements": self.options.no_weapon_requirements.value, + "death_link": self.options.death_link.value, + "no_spell_requirements": self.options.no_spell_requirements.value, + "no_equip_load": self.options.no_equip_load.value, + "enable_dlc": self.options.enable_dlc.value, + "enable_ngp": self.options.enable_ngp.value, + "smooth_soul_locations": self.options.smooth_soul_items.value, + "smooth_upgrade_locations": self.options.smooth_upgrade_items.value, + "randomize_enemies": self.options.randomize_enemies.value, + "randomize_mimics_with_enemies": self.options.randomize_mimics_with_enemies.value, + "randomize_small_crystal_lizards_with_enemies": self.options.randomize_small_crystal_lizards_with_enemies.value, + "reduce_harmless_enemies": self.options.reduce_harmless_enemies.value, + "simple_early_bosses": self.options.simple_early_bosses.value, + "scale_enemies": self.options.scale_enemies.value, + "all_chests_are_mimics": self.options.all_chests_are_mimics.value, + "impatient_mimics": self.options.impatient_mimics.value, }, "seed": self.multiworld.seed_name, # to verify the server's multiworld "slot": self.multiworld.player_name[self.player], # to connect to server - "base_id": self.base_id, # to merge location and items lists - "locationsId": locations_id, - "locationsAddress": locations_address, - "locationsTarget": locations_target, - "itemsId": items_id, - "itemsAddress": items_address + # Reserializing here is silly, but it's easier for the static randomizer. + "random_enemy_preset": json.dumps(self.options.random_enemy_preset.value), + "yhorm": ( + f"{self.yhorm_location.name} {self.yhorm_location.id}" + if self.yhorm_location != default_yhorm_location + else None + ), + "apIdsToItemIds": ap_ids_to_ds3_ids, + "itemCounts": item_counts, + "locationIdsToKeys": location_ids_to_keys, } return slot_data + + @staticmethod + def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]: + return slot_data diff --git a/worlds/dark_souls_3/detailed_location_descriptions.py b/worlds/dark_souls_3/detailed_location_descriptions.py new file mode 100644 index 00000000000..e20c700ab1b --- /dev/null +++ b/worlds/dark_souls_3/detailed_location_descriptions.py @@ -0,0 +1,97 @@ +# python -m worlds.dark_souls_3.detailed_location_descriptions \ +# worlds/dark_souls_3/detailed_location_descriptions.py +# +# This script downloads the static randomizer's descriptions for each location and adds them to +# the location documentation. + +from collections import defaultdict +import html +import os +import re +import requests +import yaml + +from .Locations import location_dictionary + + +location_re = re.compile(r'^([A-Z0-9]+): (.*?)(?:$| - )') + +if __name__ == '__main__': + # TODO: update this to the main branch of the main randomizer once Archipelago support is merged + url = 'https://raw.githubusercontent.com/nex3/SoulsRandomizers/archipelago-server/dist/Base/annotations.txt' + response = requests.get(url) + if response.status_code != 200: + raise Exception(f"Got {response.status_code} when downloading static randomizer locations") + annotations = yaml.load(response.text, Loader=yaml.Loader) + + static_to_archi_regions = { + area['Name']: area['Archipelago'] + for area in annotations['Areas'] + } + + descriptions_by_key = {slot['Key']: slot['Text'] for slot in annotations['Slots']} + + # A map from (region, item name) pairs to all the descriptions that match those pairs. + descriptions_by_location = defaultdict(list) + + # A map from item names to all the descriptions for those item names. + descriptions_by_item = defaultdict(list) + + for slot in annotations['Slots']: + region = static_to_archi_regions[slot['Area']] + for item in slot['DebugText']: + name = item.split(" - ")[0] + descriptions_by_location[(region, name)].append(slot['Text']) + descriptions_by_item[name].append(slot['Text']) + counts_by_location = { + location: len(descriptions) for (location, descriptions) in descriptions_by_location.items() + } + + location_names_to_descriptions = {} + for location in location_dictionary.values(): + if location.ap_code is None: continue + if location.static: + location_names_to_descriptions[location.name] = descriptions_by_key[location.static] + continue + + match = location_re.match(location.name) + if not match: + raise Exception(f"Location name \"{location.name}\" doesn't match expected format.") + + item_candidates = descriptions_by_item[match[2]] + if len(item_candidates) == 1: + location_names_to_descriptions[location.name] = item_candidates[0] + continue + + key = (match[1], match[2]) + if key not in descriptions_by_location: + raise Exception(f'No static randomizer location found matching "{match[1]}: {match[2]}".') + + candidates = descriptions_by_location[key] + if len(candidates) == 0: + raise Exception( + f'There are only {counts_by_location[key]} locations in the static randomizer ' + + f'matching "{match[1]}: {match[2]}", but there are more in Archipelago.' + ) + + location_names_to_descriptions[location.name] = candidates.pop(0) + + table = "\n" + for (name, description) in sorted( + location_names_to_descriptions.items(), + key = lambda pair: pair[0] + ): + table += f"\n" + table += "
Location nameDetailed description
{html.escape(name)}{html.escape(description)}
\n" + + with open(os.path.join(os.path.dirname(__file__), 'docs/locations_en.md'), 'r+') as f: + original = f.read() + start_flag = "\n" + start = original.index(start_flag) + len(start_flag) + end = original.index("") + + f.seek(0) + f.write(original[:start] + table + original[end:]) + f.truncate() + + print("Updated docs/locations_en.md!") diff --git a/worlds/dark_souls_3/docs/en_Dark Souls III.md b/worlds/dark_souls_3/docs/en_Dark Souls III.md index f31358bb9c2..06227226aaf 100644 --- a/worlds/dark_souls_3/docs/en_Dark Souls III.md +++ b/worlds/dark_souls_3/docs/en_Dark Souls III.md @@ -1,28 +1,201 @@ # Dark Souls III +Game Page | [Items] | [Locations] + +[Items]: /tutorial/Dark%20Souls%20III/items/en +[Locations]: /tutorial/Dark%20Souls%20III/locations/en + +## What do I need to do to randomize DS3? + +See full instructions on [the setup page]. + +[the setup page]: /tutorial/Dark%20Souls%20III/setup/en + ## Where is the options page? -The [player options page for this game](../player-options) contains all the options you need to configure and export a -config file. +The [player options page for this game][options] contains all the options you +need to configure and export a config file. + +[options]: ../player-options ## What does randomization do to this game? -Items that can be picked up from static corpses, taken from chests, or earned from defeating enemies or NPCs can be -randomized. Common pickups like titanite shards or firebombs can be randomized as "progressive" items. That is, the -location "Titanite Shard #5" is the fifth titanite shard you pick up, no matter where it was from. This is also what -happens when you randomize Estus Shards and Undead Bone Shards. +1. All item locations are randomized, including those in the overworld, in + shops, and dropped by enemies. Most locations can contain games from other + worlds, and any items from your world can appear in other players' worlds. + +2. By default, all enemies and bosses are randomized. This can be disabled by + setting "Randomize Enemies" to false. + +3. By default, the starting equipment for each class is randomized. This can be + disabled by setting "Randomize Starting Loadout" to false. + +4. By setting the "Randomize Weapon Level" or "Randomize Infusion" options, you + can randomize whether the weapons you find will be upgraded or infused. + +There are also options that can make playing the game more convenient or +bring a new experience, like removing equip loads or auto-equipping weapons as +you pick them up. Check out [the options page][options] for more! + +## What's the goal? + +Your goal is to find the four "Cinders of a Lord" items randomized into the +multiworld and defeat the boss in the Kiln of the First Flame. + +## Do I have to check every item in every area? + +Dark Souls III has about 1500 item locations, which is a lot of checks for a +single run! But you don't necessarily need to check all of them. Locations that +you can potentially miss, such as rewards for failable quests or soul +transposition items, will _never_ have items required for any game to progress. +The following types of locations are also guaranteed not to contain progression +items by default: + +* **Hidden:** Locations that are particularly difficult to find, such as behind + illusory walls, down hidden drops, and so on. Does not include large locations + like Untended Graves or Archdragon Peak. + +* **Small Crystal Lizards:** Drops from small crystal lizards. + +* **Upgrade:** Locations that contain upgrade items in vanilla, including + titanite, gems, and Shriving Stones. + +* **Small Souls:** Locations that contain soul items in vanilla, not including + boss souls. + +* **Miscellaneous:** Locations that contain generic stackable items in vanilla, + such as arrows, firebombs, buffs, and so on. + +You can customize which locations are guaranteed not to contain progression +items by setting the `exclude_locations` field in your YAML to the [location +groups] you want to omit. For example, this is the default setting but without +"Hidden" so that hidden locations can contain progression items: + +[location groups]: /tutorial/Dark%20Souls%20III/locations/en#location-groups + +```yaml +Dark Souls III: + exclude_locations: + - Small Crystal Lizards + - Upgrade + - Small Souls + - Miscellaneous +``` + +This allows _all_ non-missable locations to have progression items, if you're in +for the long haul: + +```yaml +Dark Souls III: + exclude_locations: [] +``` + +## What if I don't want to do the whole game? + +If you want a shorter DS3 randomizer experience, you can exclude entire regions +from containing progression items. The items and enemies from those regions will +still be included in the randomization pool, but none of them will be mandatory. +For example, the following configuration just requires you to play the game +through Irithyll of the Boreal Valley: + +```yaml +Dark Souls III: + # Enable the DLC so it's included in the randomization pool + enable_dlc: true + + exclude_locations: + # Exclude late-game and DLC regions + - Anor Londo + - Lothric Castle + - Consumed King's Garden + - Untended Graves + - Grand Archives + - Archdragon Peak + - Painted World of Ariandel + - Dreg Heap + - Ringed City + + # Default exclusions + - Hidden + - Small Crystal Lizards + - Upgrade + - Small Souls + - Miscellaneous +``` + +## Where can I learn more about Dark Souls III locations? + +Location names have to pack a lot of information into very little space. To +better understand them, check out the [location guide], which explains all the +names used in locations and provides more detailed descriptions for each +individual location. + +[location guide]: /tutorial/Dark%20Souls%20III/locations/en + +## Where can I learn more about Dark Souls III items? + +Check out the [item guide], which explains the named groups available for items. + +[item guide]: /tutorial/Dark%20Souls%20III/items/en + +## What's new from 2.x.x? + +Version 3.0.0 of the Dark Souls III Archipelago client has a number of +substantial differences with the older 2.x.x versions. Improvements include: + +* Support for randomizing all item locations, not just unique items. + +* Support for randomizing items in shops, starting loadouts, Path of the Dragon, + and more. + +* Built-in integration with the enemy randomizer, including consistent seeding + for races. + +* Support for the latest patch for Dark Souls III, 1.15.2. Older patches are + *not* supported. + +* Optional smooth distribution for upgrade items, upgraded weapons, and soul + items so you're more likely to see weaker items earlier and more powerful + items later. + +* More detailed location names that indicate where a location is, not just what + it replaces. + +* Other players' item names are visible in DS3. + +* If you pick up items while static, they'll still send once you reconnect. + +However, 2.x.x YAMLs are not compatible with 3.0.0. You'll need to [generate a +new YAML configuration] for use with 3.x.x. + +[generating a new YAML configuration]: /games/Dark%20Souls%20III/player-options + +The following options have been removed: + +* `enable_boss_locations` is now controlled by the `soul_locations` option. + +* `enable_progressive_locations` was removed because all locations are now + individually randomized rather than replaced with a progressive list. + +* `pool_type` has been removed. Since there are no longer any non-randomized + items in randomized categories, there's not a meaningful distinction between + "shuffle" and "various" mode. -It's also possible to randomize the upgrade level of weapons and shields as well as their infusions (if they can have -one). Additionally, there are options that can make the randomized experience more convenient or more interesting, such as -removing weapon requirements or auto-equipping whatever equipment you most recently received. +* `enable_*_locations` options have all been removed. Instead, you can now add + [location group names] to the `exclude_locations` option to prevent them from + containing important items. -The goal is to find the four "Cinders of a Lord" items randomized into the multiworld and defeat the Soul of Cinder. + [location group names]: /tutorial/Dark%20Souls%20III/locations/en#location-groups -## What Dark Souls III items can appear in other players' worlds? + By default, the Hidden, Small Crystal Lizards, Upgrade, Small Souls, and + Miscellaneous groups are in `exclude_locations`. Once you've chosen your + excluded locations, you can set `excluded_locations: unrandomized` to preserve + the default vanilla item placements for all excluded locations. -Practically anything can be found in other worlds including pieces of armor, upgraded weapons, key items, consumables, -spells, upgrade materials, etc... +* `guaranteed_items`: In almost all cases, all items from the base game are now + included somewhere in the multiworld. -## What does another world's item look like in Dark Souls III? +In addition, the following options have changed: -In Dark Souls III, items which are sent to other worlds appear as Prism Stones. +* The location names used in options like `exclude_locations` have changed. See + the [location guide] for a full description. diff --git a/worlds/dark_souls_3/docs/items_en.md b/worlds/dark_souls_3/docs/items_en.md new file mode 100644 index 00000000000..b9de5e500a9 --- /dev/null +++ b/worlds/dark_souls_3/docs/items_en.md @@ -0,0 +1,24 @@ +# Dark Souls III Items + +[Game Page] | Items | [Locations] + +[Game Page]: /games/Dark%20Souls%20III/info/en +[Locations]: /tutorial/Dark%20Souls%20III/locations/en + +## Item Groups + +The Dark Souls III randomizer supports a number of item group names, which can +be used in YAML options like `local_items` to refer to many items at once: + +* **Progression:** Items which unlock locations. +* **Cinders:** All four Cinders of a Lord. Once you have these four, you can + fight Soul of Cinder and win the game. +* **Miscellaneous:** Generic stackable items, such as arrows, firebombs, buffs, + and so on. +* **Unique:** Items that are unique per NG cycle, such as scrolls, keys, ashes, + and so on. Doesn't include equipment, spells, or souls. +* **Boss Souls:** Souls that can be traded with Ludleth, including Soul of + Rosaria. +* **Small Souls:** Soul items, not including boss souls. +* **Upgrade:** Upgrade items, including titanite, gems, and Shriving Stones. +* **Healing:** Undead Bone Shards and Estus Shards. diff --git a/worlds/dark_souls_3/docs/locations_en.md b/worlds/dark_souls_3/docs/locations_en.md new file mode 100644 index 00000000000..ef07b84b2b3 --- /dev/null +++ b/worlds/dark_souls_3/docs/locations_en.md @@ -0,0 +1,2276 @@ +# Dark Souls III Locations + +[Game Page] | [Items] | Locations + +[Game Page]: /games/Dark%20Souls%20III/info/en +[Items]: /tutorial/Dark%20Souls%20III/items/en + +## Table of Contents + +* [Location Groups](#location-groups) +* [Understanding Location Names](#understanding-location-names) + * [HWL: High Wall of Lothric](#high-wall-of-lothric) + * [US: Undead Settlement](#undead-settlement) + * [RS: Road of Sacrifices](#road-of-sacrifices) + * [CD: Cathedral of the Deep](#cathedral-of-the-deep) + * [FK: Farron Keep](#farron-keep) + * [CC: Catacombs of Carthus](#catacombs-of-carthus) + * [SL: Smouldering Lake](#smouldering-lake) + * [IBV: Irithyll of the Boreal Valley](#irithyll-of-the-boreal-valley) + * [ID: Irithyll Dungeon](#irithyll-dungeon) + * [PC: Profaned Capital](#profaned-capital) + * [AL: Anor Londo](#anor-londo) + * [LC: Lothric Castle](#lothric-castle) + * [CKG: Consumed King's Garden](#consumed-kings-garden) + * [GA: Grand Archives](#grand-archives) + * [UG: Untended Graves](#untended-graves) + * [AP: Archdragon Peak](#archdragon-peak) + * [PW1: Painted World of Ariandel (Before Contraption)](#painted-world-of-ariandel-before-contraption) + * [PW2: Painted World of Ariandel (After Contraption)](#painted-world-of-ariandel-after-contraption) + * [DH: Dreg Heap](#dreg-heap) + * [RC: Ringed City](#ringed-city) +* [Detailed Location Descriptions](#detailed-location-descriptions) + +## Location Groups + +The Dark Souls III randomizer supports a number of location group names, which +can be used in YAML options like `exclude_locations` to refer to many locations +at once: + +* **Prominent:** A small number of locations that are in very obvious locations. + Mostly boss drops. Ideal for setting as priority locations. + +* **Progression:** Locations that contain items in vanilla which unlock other + locations. + +* **Boss Rewards:** Boss drops. Does not include soul transfusions or shop + items. + +* **Miniboss Rewards:** Miniboss drops. Minibosses are large enemies that don't + respawn after being killed and usually drop some sort of treasure, such as + Boreal Outrider Knights and Ravenous Crystal Lizards. Only includes enemies + considered minibosses by the enemy randomizer. + +* **Mimic Rewards:** Drops from enemies that are mimics in vanilla. + +* **Hostile NPC Rewards:** Drops from NPCs that are hostile to you. This + includes scripted invaders and initially-friendly NPCs that must be fought as + part of their quest. + +* **Friendly NPC Rewards:** Items given by friendly NPCs as part of their quests + or from non-violent interaction. + +* **Small Crystal Lizards:** Drops from small crystal lizards. + +* **Upgrade:** Locations that contain upgrade items in vanilla, including + titanite, gems, and Shriving Stones. + +* **Small Souls:** Locations that contain soul items in vanilla, not including + boss souls. + +* **Boss Souls:** Locations that contain boss souls in vanilla, as well as Soul + of Rosaria. + +* **Unique:** Locations that contain items in vanilla that are unique per NG + cycle, such as scrolls, keys, ashes, and so on. Doesn't cover equipment, + spells, or souls. + +* **Healing:** Locations that contain Undead Bone Shards and Estus Shards in + vanilla. + +* **Miscellaneous:** Locations that contain generic stackable items in vanilla, + such as arrows, firebombs, buffs, and so on. + +* **Hidden:** Locations that are particularly difficult to find, such as behind + illusory walls, down hidden drops, and so on. Does not include large locations + like Untended Graves or Archdragon Peak. + +* **Weapons:** Locations that contain weapons in vanilla. + +* **Shields:** Locations that contain shields in vanilla. + +* **Armor:** Locations that contain armor in vanilla. + +* **Rings:** Locations that contain rings in vanilla. + +* **Spells:** Locations that contain spells in vanilla. + +## Understanding Location Names + +All locations begin with an abbreviation indicating their general region. Most +locations have a set of landmarks that are used in location names to keep them +short. + +* **FS:** Firelink Shrine +* **FSBT:** Firelink Shrine belltower +* **HWL:** [High Wall of Lothric](#high-wall-of-lothric) +* **US:** [Undead Settlement](#undead-settlement) +* **RS:** [Road of Sacrifices](#road-of-sacrifices) +* **CD:** [Cathedral of the Deep](#cathedral-of-the-deep) +* **FK:** [Farron Keep](#farron-keep) +* **CC:** [Catacombs of Carthus](#catacombs-of-carthus) +* **SL:** [Smouldering Lake](#smouldering-lake) +* **IBV:** [Irithyll of the Boreal Valley](#irithyll-of-the-boreal-valley) +* **ID:** [Irithyll Dungeon](#irithyll-dungeon) +* **PC:** [Profaned Capital](#profaned-capital) +* **AL:** [Anor Londo](#anor-londo) +* **LC:** [Lothric Castle](#lothric-castle) +* **CKG:** [Consumed King's Garden](#consumed-kings-garden) +* **GA:** [Grand Archives](#grand-archives) +* **UG:** [Untended Graves](#untended-graves) +* **AP:** [Archdragon Peak](#archdragon-peak) +* **PW1:** [Painted World of Ariandel (Before Contraption)](#painted-world-of-ariandel-before-contraption) +* **PW2:** [Painted World of Ariandel (After Contraption)](#painted-world-of-ariandel-after-contraption) +* **DH:** [Dreg Heap](#dreg-heap) +* **RC:** [Ringed City](#ringed-city) + +General notes: + +* "Lizard" always refers to a small crystal lizard. + +* "Miniboss" are large enemies that don't respawn after being killed and usually + drop some sort of treasure, such as Boreal Outrider Knights and Ravenous + Crystal Lizards. + +* NPC quest items are always in the first location you can get them _without_ + killing the NPC or ending the quest early. + +### High Wall of Lothric + +* **Back tower:** The tower _behind_ the High Wall of Lothric bonfire, past the + path to the shortcut elevator. + +* **Corpse tower:** The first tower after the High Wall of Lothric bonfire, with + a dead Wyvern on top of it. + +* **Fire tower:** The second tower after the High Wall of Lothric bonfire, where + a living Wyvern lands and breathes fire at you. + +* **Flame plaza:** The open area with many items where the Wyvern breathes fire. + +* **Wall tower:** The third tower after the High Wall of Lothric bonfire, with + the Tower on the Wall bonfire. + +* **Fort:** The large building after the Tower on the Wall bonfire, with the + transforming hollow on top. + + * "Entry": The first room you enter after descending the ladder from the roof. + + * "Walkway": The top floor of the tall room, with a path around the edge + hidden by a large wheel. + + * "Mezzanine": The middle floor of the tall room, with a chest. + + * "Ground": The bottom floor of the tall room, with an anvil and many mobs. + +* **Fountain:** The large fountain with many dead knights around it, where the + Winged Knight patrols in vanilla. + +* **Shortcut:** The unlockable path between the promenade and the High Wall of + Lothric bonfire, including both the elevator and the area at its base. + +* **Promenade:** The long, wide path between the two boss arenas. + +### Undead Settlement + +* **Foot:** The area where you first appear, around the Foot of the High Wall + bonfire. + +* **Burning tree:** The tree near the beginning of the region, with the + Cathedral Evangelist in front of it in vanilla. + +* **Hanging corpse room:** The dark room to the left of the burning tree with + many hanging corpses inside, on the way to the Dilapidated Bridge bonfire. + +* **Back alley:** The path between buildings leading to the Dilapidated Bridge + bonfire. + +* **Stable:** The building complex across the bridge to the right of the burning + tree. + +* **White tree:** The birch tree by the Dilapidated Bridge bonfire, where the + giant shoots arrows. + +* **Sewer:** The underground passage between the chasm and the Dilapidated + Bridge bonfire. + +* **Chasm:** The chasm underneath the bridge on the way to the tower. It's + possible to get into the chasm without a key by dropping down next to Eygon of + Carim with a full health bar. + +* **Tower:** The tower at the end of the region with the giant archer at the + top. + +* **Tower village:** The village reachable from the tower, where the Fire Demon + patrols in vanilla. + +### Road of Sacrifices + +The area after the Crystal Sage is considered part of the Cathedral of the Deep +region. + +* **Road:** The path from the Road of Sacrifices bonfire to the Halfway Fortress + bonfire. + +* **Woods:** The wooded area on land, after the Halfway Fortress bonfire and + surrounding the Crucifixion Woods bonfire. + +* **Water:** The watery area, covered in crabs in vanilla. + +* **Deep water:** The area in the water near the ladder to Farron Keep, where + your walking is slowed. + +* **Stronghold:** The stone building complex on the way to Crystal Sage. + + * "Left room" is the room whose entrance is near the Crucifixion Woods + bonfire. + + * "Right room" is the room up the stairs closer to Farron Keep. + +* **Keep perimeter:** The building with the Black Knight and the locked door to + the Farron Keep Perimeter bonfire. + +### Cathedral of the Deep + +* **Path:** The path from Road of Sacrifices to the cathedral proper. + +* **Moat:** The circular path around the base of the front of the + cathedral, with the Ravenous Crystal Lizard and Corpse-Grubs in vanilla. + +* **Graveyard:** The area with respawning enemies up the hill from the Cleansing + Chapel bonfire. + +* **White tree:** The birch tree below the front doors of the chapel and across + the moat from the graveyard, where the giant shoots arrows if he's still + alive. + +* **Lower roofs:** The roofs, flying buttresses, and associated areas to the + right of the front door, which must be traversed before entering the + cathedral. + +* **Upper roofs:** The roofs, flying buttresses, and rafters leading to the + Rosaria's Bedchamber bonfire. + +* **Main hall:** The central and largest room in the cathedral, with the muck + that slows your movement. Divided into the south (with the sleeping giant in + vanilla) and east (with many items) wings, with north pointing towards the + door to the boss. + +* **Side chapel:** The room with rows of pews and the patrolling Cathedral + Knight in vanilla, to the side of the main hall. + +### Farron Keep + +* **Left island:** The large island with the ritual flame, to the left as you + leave the Farron Keep bonfire. + +* **Right island:** The large island with the ritual flame, to the right as you + leave the Farron Keep bonfire. + +* **Hidden cave:** A small cave in the far corner of the map, closest to the + right island. Near a bunch of basilisks in vanilla. + +* **Keep ruins:** The following two islands: + + * "Bonfire island": The island with the Keep Ruins bonfire. + * "Ritual island": The island with one of the three ritual fires. + +* **White tree**: The birch tree by the ramp down from the keep ruins bonfire + island, where the giant shoots arrows if he's still alive. + +* **Keep proper:** The building with the Old Wolf of Farron bonfire. + +* **Upper keep:** The area on top of the keep proper, reachable from the + elevator from the Old Wolf of Farron bonfire. + +* **Perimeter:** The area from near the Farron Keep Perimeter bonfire, including + the stone building and the path to the boss. + +### Catacombs of Carthus + +All the area up to the Small Doll wall into Irithyll is considered part of the +Catacombs of Carthus region. + +* **Atrium:** The large open area you first enter and the rooms attached to it. + + * "Upper" is the floor you begin on. + * "Lower" is the floor down the short stairs but at the top of the long + stairway that the skeleton ball rolls down. + +* **Crypt:** The enclosed area at the bottom of the long stairway that the + skeleton ball rolls down. + + * "Upper" is the floor the long stairway leads to that also contains the + Catacombs of Carthus bonfire. + * "Lower" is the floor with rats and bonewheels in vanilla. + * "Across" is the area reached by going up the set of stairs across from + the entrance downstairs. + +* **Cavern:** The even larger open area past the crypt with the rope bridge to + the boss arena. + +* **Tomb:** The area on the way to Smouldering Lake, reachable by cutting down + the rope bridge and climbing down it. + +* **Irithyll Bridge:** The outdoor bridge leading to Irithyll of the Boreal + Valley. + +### Smouldering Lake + +* **Lake:** The watery area you enter initially, where you get shot at by the + ballista. + +* **Side lake:** The small lake accessible via a passage from the larger one, in + which you face Horace the Hushed as part of his quest. + +* **Ruins main:** The area you first enter after the Demon Ruins bonfire. + + * "Upper" is the floor you begin on. + * "Lower" is the floor down the stairs. + +* **Antechamber:** The area up the flight of stairs near the +Old King's Antechamber bonfire. + +* **Ruins basement:** The area further down from ruins main lower, with many + basilisks and Knight Slayer Tsorig in vanilla. + +### Irithyll of the Boreal Valley + +This region starts _after_ the Small Doll wall and ends with Pontiff Sulyvahn. +Everything after that, including the contents of Sulyvahn's cathedral is +considered part of Anor Londo. + +* **Central:** The beginning of the region, from the Central Irithyll bonfire up + to the plaza. + +* **Dorhys:** The sobbing mob (a Cathedral Evangelist in vanilla) behind the + locked door opening onto central. Accessed through an illusory railing by the + crystal lizard just before the plaza. + +* **Plaza:** The area in front of and below the cathedral, with a locked door up + to the cathedral and a locked elevator to the Ascent. + +* **Descent:** The path from the Church of Yorshka bonfire down to the lake. + +* **Lake:** The open watery area outside the room with the Distant Manor + bonfire. + +* **Sewer:** The room between the lake and the beginning of the ascent, filled + with Sewer Centipedes in vanilla. + +* **Ascent:** The path up from the lake to the cathedral, through several + buildings and some open stairs. + +* **Great hall:** The building along the ascent with a large picture of + Gwynevere and several Silver Knights in vanilla. + +### Irithyll Dungeon + +In Irithyll Dungeon locations, "left" and "right" are always oriented as though +"near" is where you stand and "far" is where you're facing. (For example, you +enter the dungeon from the bonfire on the near left.) + +* **B1:** The floor on which the player enters the dungeon, with the Irithyll + Dungeon bonfire. + + * "Near" is the side of the dungeon with the bonfire. + * "Far" is the opposite side. + +* **B2:** The floor directly below B1, which can be reached by going down the + stairs or dropping. + + * "Near" is the same side of the dungeon as the bonfire. + * "Far" is the opposite side. + +* **Pit:** The large room with the Giant Slave and many Rats in vanilla. + +* **Pit lift:** The elevator from the pit up to B1 near, right to the Irithyll + Dungeon bonfire. + +* **B3:** The lowest floor, with Karla's cell, a lift back to B2, and the exit + onwards to the Profaned Capital. + + * "Near" is the side with Karla's cell and the path from the pit. + * "Far" is the opposite side with the mimic. + +* **B3 lift:** The elevator from B3 (near where you can use Path of the Dragon + to go to Archdragon Peak) up to B2. + +### Profaned Capital + +* **Tower:** The tower that contains the Profaned Capital bonfire. + +* **Swamp:** The pool of toxic liquid accessible by falling down out of the + lower floor of the tower, going into the corridor to the left, and falling + down a hole. + +* **Chapel:** The building in the swamp containing Monstrosities of Sin in + vanilla. + +* **Bridge:** The long bridge from the tower into the palace. + +* **Palace:** The large building carved into the wall of the cavern, full of + chalices and broken pillars. + +### Anor Londo + +This region includes everything after Sulyvahn's cathedral, including its upper +story. + +* **Light cathedral:** The cathedral in which you fight Pontiff Sulyvahn in + vanilla. + +* **Plaza:** The wide open area filled with Giant Slaves in vanilla. + +* **Walkway:** The path above the plaza leading to the second floor of the light + cathedral, with Deacons in vanilla. + +* **Buttresses:** The flying buttresses that you have to climb to get to the + spiral staircase. "Near" and "far" are relative to the light cathedral, so the + nearest buttress is the one that leads back to the walkway. + +* **Tomb:** The area past the illusory wall just before the spiral staircase, in + which you marry Anri during Yoel and Yuria's quest. + +* **Dark cathedral:** The darkened cathedral just before the Aldrich fight in + vanilla. + +### Lothric Castle + +This region covers everything up the ladder from the Dancer of the Boreal Valley +bonfire up to the door into Grand Archives, except the area to the left of the +ladder which is part of Consumed King's Garden. + +* **Lift:** The elevator from the room straight after the Dancer of the Boreal + Valley bonfire up to just before the boss fight. + +* **Ascent:** The set of stairways and turrets leading from the Lothric Castle + bonfire to the Dragon Barracks bonfire. + +* **Barracks:** The large building with two fire-breathing wyverns across from + the Dragon Barracks bonfire. + +* **Moat:** The ditch beneath the bridge leading to the barracks. + + * The "right path" leads to the right as you face the barracks, around and + above the stairs up to the Dragon Barracks bonfire. + +* **Plaza:** The open area in the center of the barracks, where the two wyverns + breathe fire. + + * "Left" is the enclosed area on the left as you're coming from the Dragon + Barracks bonfire, with the stairs down to the basement. + +* **Basement:** The room beneath plaza left, with the Boreal Outrider in + vanilla. + +* **Dark room:** The large darkened room on the right of the barracks as you're + coming from the Dragon Barracks bonfire, with firebomb-throwing Hollows in + vanilla. + + * "Lower" is the bottom floor that you enter onto from the plaza. + * "Upper" is the top floor with the door to the main hall. + * "Mid" is the middle floor accessible by climbing a ladder from lower or + going down stairs from upper. + +* **Main hall:** The central room of the barracks, behind the gate. + +* **Chapel:** The building to the right just before the stairs to the boss, with + a locked elevator to Grand Archives. + +* **Wyvern room:** The room where you can fight the Pus of Man infecting the + left wyvern, accessible by dropping down to the left of the stairs to the + boss. + +* **Altar:** The building containing the Altar of Sunlight, accessible by + climbing up a ladder onto a roof around the corner from the stairs to the + boss. + +### Consumed King's Garden + +This region covers everything to the left of the ladder up from the Dancer of +the Boreal Valley bonfire up to the illusory wall into Untended Graves. + +* **Balcony:** The walkway accessible by getting off the first elevator halfway + down. + +* **Rotunda:** The building in the center of the toxic pool, with a Cathedral + Knight on it in vanilla. + +* **Lone stairway:** A set of stairs leading nowhere in the far left of the main + area as you enter from the first elevator. + +* **Shortcut:** The path from the locked door into Lothric Castle, through the + room filled with thralls in vanilla, and down a lift. + +* **Tomb:** The area after the boss room. + +### Grand Archives + +* **1F:** The first floor of the Grand Archives, including the first wax pool. + +* **Dark room:** The unlit room on 1F to the right of the wax pool. + +* **2F:** The second floor of the grand archives. It's split into two sections + that are separated by retractable bookshelves. + + * "Early" is the first part you reach and has an outdoor balcony with a ladder + to 3F and a wax pool up a short set of stairs. + * "Late" is the part you can only reach by climbing down from F3, where you + encounter the teleporting miniboss for the final time. + +* **3F:** The third floor of the grand archives, where you encounter the + teleporting miniboss for the second time. Includes the area with a hidden room + with another miniboss. + +* **4F:** The topmost and most well-lit section of bookshelves, overlooking the + rest of the archives. + +* **Rooftops:** The outer rooftop area between 4F and 5F, with Gargoyles in + vanilla. + + * "Lower" is the balcony you can reach by dropping off the rooftops, as well + as the further rooftops leading down to the 2F early balcony. + +* **5F:** The topmost floor of the archives interior, accessible from the + rooftops, with a ladder down to 4F. + +* **Dome:** The domed roof of the Grand Archives, with Ascended Winged Knights + in vanilla. + +* **Rafters:** The narrow walkways above the Grand Archives, accessible by + dropping down from the dome. + +### Untended Graves + +* **Swamp:** The watery area immediately after the Untended graves bonfire, up + to the cemetery. + +* **Cemetery:** The area past where the Cemetery of Ash bonfire would be, up to + the boss arena. + +* **Environs:** The area after the boss and outside the abandoned Firelink + Shrine. + +* **Shrine:** The area inside the abandoned Firelink Shrine. + +### Archdragon Peak + +"Gesture" always means the Path of the Dragon gesture. + +* **Intro:** The first section, from where you warp in from Irithyll Dungeon up + to the first boss fight. + + * "Archway": The large stone archway in front of the boss door. + +* **Fort:** The arena where you fight Ancient Wyvern in vanilla. + + * "Overlook": The area down the stairs from where the Ancient Wyvern first + lands in vanilla, overlooking the fog. + + * "Rotunda": The top of the spiral staircase building, to the left before the + bridge with the chain-axe Man-Serpent in vanilla. + +* **Mausoleum:** The building with the Dragon-Kin Mausoleum bonfire, where + you're warped after the first boss fight. + +* **Walkway:** The path from the mausoleum to the belfry, looking out over + clouds. + + * "Building": The building along the walkway, just before the wyvern in + vanilla. + +* **Belfry:** The building with the Great Belfry bonfire, including the room + with the summoner. + +* **Plaza:** The arena that appears after you defeat Nameless King in vanilla. + +* **Summit:** The path up from the belfry to the final altar at the top of the + mountain. + +### Painted World of Ariandel (Before Contraption) + +This region covers the Ashes of Ariandel DLC up to the point where you must use +the Contraption Key to ascend to the second level of the building and first meet +the painter. + +* **Snowfield:** The area around the Snowfield bonfire, + + * "Upper": The area immediately after the Snowfield bonfire, before the + collapsing overhang, with the Followers in vanilla. + + * "Lower": The snowy tree-filled area after the collapsing overhang, with the + Wolves in vanilla. + + * "Village": The area with broken-down buildings and Millwood Knights in + vanilla. + + * "Tower": The tower by the village, with Millwood Knights in Vanilla. + +* **Bridge:** The rope bridge to the chapel. + + * "Near": The side of the bridge by the Rope Bridge Cave bonfire. + + * "Far": The side of the bridge by the Ariandel Chapel bonfire. + +* **Chapel:** The building with the Ariandel Chapel bonfire and Lady Friede. + +* **Depths:** The area reachable by cutting down the bridge and descending on + the far side, with the Depths of the Painting bonfire. + +* **Settlement:** The area reachable by cutting down the bridge and descending + on the near side, with the Corvian Settlement bonfire. Everything after the + slide down the hill is considered part of the settlement. + + * "Courtyard": The area in front of the settlement, immediately after the + slide. + + * "Main": The main road of the settlement leading up to the locked gate to the + library. Also includes the buildings that are immediately accessible from + this road. + + * "Loop": A side path that loops left from the main road and goes up and + behind the building with the bonfire. + + * "Back": The back alley of the settlement, accessible by dropping down to the + right of the locked gate to the library. Also includes the buildings that + are immediately accessible from this alley. + + * "Roofs": The village rooftops, first accessible by climbing a ladder from + the back alley. Also includes the buildings and items that are first + accessible from the roofs. + + * "Hall": The largest building in the settlement, with two Corvian Knights in + vanilla. + +* **Library:** The building where you use the contraption key, where Vilhelm + appears in vanilla. + +### Painted World of Ariandel (After Contraption) + +This region covers the Ashes of Ariandel DLC past the point where you must use +the Contraption Key to ascend to the second level of the building and first meet +the painter, including the basement beneath the chapel. + +* **Pass:** The mountainous area past the Snowy Mountain Pass bonfire. + +* **Pit:** The area with a large tree and numerous Millwood Knights in vanilla, + reached by a collapsing overhang in the pass. + +* **B1:** The floor immediately below the chapel, first accessible from the + pass. Filled with Giant Flies in vanilla. + +* **B2:** The floor below B1, with lots of fly eggs. Filled with even more Giant + Flies than B1 in vanilla. + +* **B3:** The floor below B2, accessible through an illusory wall. + +* **Rotunda:** The round arena out in the open, accessible by platforming down + tree roots from B3. + +### Dreg Heap + +* **Shop:** Items sold by the Stone-Humped Hag by The Dreg Heap bonfire. + +* **Castle:** The building with The Dreg Heap bonfire, up to the large fall into + the library. + +* **Library:** The building with the stained-glass window that you fall into + from the castle. + +* **Church:** The building below and to the right of the library, which the + pillar falls into to make a bridge. + +* **Pantry:** The set of rooms entered through a door near the fountain just + past the church, with boxes and barrels. + + * "Upstairs": The room with an open side, accessible through an illusory wall + in the furthest pantry room. + +* **Parapets:** The area with balconies and Overgrown Lothric Knights in + vanilla, accessible by taking the pillar bridge from the church, following + that path to the end, and dropping down to the right. + +* **Ruins:** The area around the Earthen Peak Ruins bonfire, up to the swamp. + +* **Swamp:** The area in and above the poisonous water, up to the point the + branches deposit you back on the ruins. + + * "Left": Left as you enter from the ruins, towards the cliff edge. + + * "Right": Right as you enter from the ruins, towards higher ground. + + * "Upper": The path up and over the swamp towards the Within Earthen Peak + Ruins bonfire. + +### Ringed City + +The "mid boss", "end boss", and "hidden boss" are the bosses who take the place +of Halflight, Gael, and Midir, respectively. + +* **Wall:** The large wall in which you spawn when you first enter the area, + with the Mausoleum Lookout bonfire. + + * "Top": The open-air top of the wall, where you first spawn in. + + * "Upper": The upper area of the wall, with the Ringed Inner Wall bonfire. + + * "Tower": The tiered tower leading down from the upper area to the stairs. + + * "Lower": The lower rooms of the wall, accessible from the lower cliff, with + an elevator back to upper. + + * "Hidden": The hidden floor accessible from the elevator from lower to upper, + from which you can reach Midir in vanilla. + +* **Streets:** The streets and skyways of the city proper. "Left" and "right" + are relative to the main staircase as you head down towards the swamp, "near" + and "far" are relative to Shira's chamber at the top of the stairs. + + * "Garden": The flower-filled back alley accessible from the left side of the + nearest bridge over the stairs. + + * "High": The higher areas in the far left where you can find the Locust + Preacher, accessible from a long ladder in the swamp. + + * "Monument": The area around the purging monument, which can only be accessed + by solving the "Show Your Humanity" puzzle. + +* **Swamp:** The wet area past the city streets. "Left" and "right" are relative + to heading out from the Ringed City Streets bonfire, and "near" and "far" are + relative to that bonfire as well. + +* **Lower cliff:** The cliffside path leading from the swamp into the shared + grave, where Midir breathes fire. + +* **Grave:** The cylindrical chamber with spiral stairs around the edges, + connecting the two cliffs, containing the Shared Grave bonfire. + +* **Upper cliff:** The cliffside path leading out of the grave to the lower + wall. + +* **Church path:** The sunlit path from the lower cliff up to the Church of + Filianore where you fight Halflight in vanilla. + +* **Ashes:** The final area, where you fight Gael in vanilla. + +## Detailed Location Descriptions + +These location descriptions were originally written by [Matt Gruen] for [the +static _Dark Souls III_ randomizer]. + +[Matt Gruen]: https://thefifthmatt.com/ +[the static _Dark Souls III_ randomizer]: https://www.nexusmods.com/darksouls3/mods/361 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Location nameDetailed description
AL: Aldrich Faithful - water reserves, talk to McDonnelGiven by Archdeacon McDonnel in Water Reserves.
AL: Aldrich's Ruby - dark cathedral, minibossDropped by the Deep Accursed who drops down when you open the Anor Londo Cathedral shortcut
AL: Anri's Straight Sword - Anri questDropped by Anri of Astora upon death or completing quest. In the Darkmoon Tomb with Lord of Hollows route, or given by Ludleth if summoned to defeat Aldrich.
AL: Blade of the Darkmoon - Yorshka with Darkmoon LoyaltyGiven by Yorshka after learning the Darkmoon Loyalty gesture from Sirris, or by killing her
AL: Brass Armor - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Brass Gauntlets - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Brass Helm - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Brass Leggings - tombBehind the illusory statue in the hallway leading to the Darkmoon Tomb
AL: Chameleon - tomb after marrying AnriDropped by the Stone-humped Hag assassin after Anri reaches the Church of Yorshka, either in the church or after marrying Anri
AL: Cinders of a Lord - AldrichDropped by Aldrich
AL: Crescent Moon Sword - Leonhard dropDrop by Ringfinger Leonhard upon death. Includes Soul of Rosaria if invaded in Anor Londo.
AL: Dark Stoneplate Ring - by dark stairs up from plazaAfter the Pontiff fight, in the dark hallways to the left of the area with the Giant Slaves
AL: Deep Gem - water reservesIn the open in the Water Reserves
AL: Dragonslayer Greatarrow - drop from nearest buttressDropping down from about halfway down the flying buttress closest to the entrance to the Darkmoon Tomb
AL: Dragonslayer Greatbow - drop from nearest buttressDropping down from about halfway down the flying buttress closest to the entrance to the Darkmoon Tomb
AL: Drang Twinspears - plaza, NPC dropDropped by Drang Twinspears-wielding knight on the stairs leading up to the Anor Londo Silver Knights
AL: Easterner's Ashes - below top of furthest buttressDropping down from the rightmost flying buttress, or the rightmost set of stairs
AL: Ember - plaza, furtherAfter the Pontiff fight, in the middle of the area with the Giant Slaves
AL: Ember - plaza, right sideAfter the Pontiff fight, next to one of the Giant Slaves on the right side
AL: Ember - spiral staircase, bottomNext to the lever that summons the rotating Anor Londo stairs at the bottom
AL: Estus Shard - dark cathedral, by left stairsIn a chest on the floor of the Anor Londo cathedral
AL: Giant's Coal - by giant near dark cathedralOn the Giant Blacksmith's corpse in Anor Londo
AL: Golden Ritual Spear - light cathedral, mimic upstairsDrop from a mimic in the higher levels of Pontiff's cathedral, accessible from the Deacons after the Pontiff fight
AL: Havel's Ring+2 - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Human Dregs - water reservesIn the open in the Water Reserves
AL: Large Soul of a Weary Warrior - left of dark cathedral entranceIn front of the Anor Londo cathedral, slightly to the left
AL: Large Titanite Shard - balcony by dead giantsAfter the Pontiff fight, on the balcony to the right of the area with the Giant Slaves
AL: Large Titanite Shard - bottom of the furthest buttressAt the base of the rightmost flying buttress leading up to Anor Londo
AL: Large Titanite Shard - bottom of the nearest buttressOn the tower leading back from Anor Londo to the shortcut to Irithyll, down the flying buttress closest to the Darkmoon Tomb entrance.
AL: Large Titanite Shard - right after light cathedralAfter Pontiff's cathedral, hugging the wall to the right
AL: Large Titanite Shard - walkway, side path by cathedralAfter the Pontiff fight, going back from the Deacons area to the original cathedral, before a dropdown
AL: Moonlight Arrow - dark cathedral, up right stairsIn the Anor Londo cathedral, up the stairs on the right side
AL: Painting Guardian Gloves - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian Gown - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian Hood - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian Waistcloth - prison tower, raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Painting Guardian's Curved Sword - prison tower raftersOn the rafters dropping down from Yorshka's Prison Tower to the Church of Yorshka
AL: Proof of a Concord Kept - dark cathedral, up left stairsIn the Anor Londo cathedral, halfway down the stairs on the left side next to some Deacons
AL: Reversal Ring - tomb, chest in cornerIn a chest in Darkmoon Tomb
AL: Ring of Favor - water reserves, both minibossesDropped after killing both of Sulyvahn's Beasts in the Water Reserves
AL: Ring of Favor+1 - light cathedral, upstairsIn the higher levels of Pontiff's cathedral, accessible from the Deacons after the Pontiff fight
AL: Silver Mask - Leonhard dropDrop by Ringfinger Leonhard upon death. Includes Soul of Rosaria if invaded in Anor Londo.
AL: Simple Gem - light cathedral, lizard upstairsDropped by a Crystal Lizard in the higher levels of Pontiff's cathedral, accessible from the Deacons after the Pontiff fight
AL: Soul of AldrichDropped by Aldrich
AL: Soul of Rosaria - Leonhard dropDrop by Ringfinger Leonhard upon death. Includes Soul of Rosaria if invaded in Anor Londo.
AL: Soul of a Crestfallen Knight - right of dark cathedral entranceTo the right of the Anor Londo cathedral entrance, past the red-eyed Silver Knight
AL: Soul of a Weary Warrior - plaza, nearerAfter the Pontiff fight, in the middle of the area with the Giant Slaves
AL: Sun Princess Ring - dark cathedral, after bossIn the Anor Londo cathedral after defeating Aldrich, up the elevators in Gwynevere's Chamber
AL: Titanite Scale - top of ladder up to buttressesOn the platform after the stairs leading up to Anor Londo from the Water Reserves building
AL: Twinkling Titanite - lizard after light cathedral #1Dropped a Crystal Lizard straight after the Pontiff fight
AL: Twinkling Titanite - lizard after light cathedral #2Dropped a Crystal Lizard straight after the Pontiff fight
AL: Yorshka's Chime - kill YorshkaDropped by Yorshka upon death.
AP: Ancient Dragon Greatshield - intro, on archwayAfter the Archdragon Peak bonfire, on top of the arch in front of the Ancient Wyvern fight
AP: Calamity Ring - mausoleum, gesture at altarReceived using Path of the Dragon at the Altar by the Mausoleum bonfire
AP: Covetous Gold Serpent Ring+2 - plazaIn the Nameless King boss arena after he is defeated
AP: Dragon Chaser's Ashes - summit, side pathIn the run-up to the Dragon Altar after the Belfry bonfire, in a side path to the left side
AP: Dragon Head Stone - fort, boss dropDropped by Ancient Wyvern
AP: Dragon Tooth - belfry roof, NPC dropDropped from any of the Havel Knights
AP: Dragonslayer Armor - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Gauntlets - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Helm - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Leggings - plazaIn the Nameless King boss arena after he is defeated
AP: Dragonslayer Spear - gate after mausoleumIn the gate connecting the Dragon-Kin Mausoleum area to the bridge where the Nameless King fight takes place
AP: Drakeblood Greatsword - mausoleum, NPC dropDropped by the Drakeblood Knight summoned by the Serpent-Man Summoner
AP: Dung Pie - fort, landing after second roomOn a landing going up the stairs from the Ancient Wyvern to the chainaxe Man-Serpent area
AP: Ember - belfry, below bellIn the area below the bell lever, either dropping down near the lever or going down the stairs from the open fountain area after the Belfry bonfire
AP: Ember - fort overlook #1From the right of where Ancient Wyvern first lands
AP: Ember - fort overlook #2From the right of where Ancient Wyvern first lands
AP: Ember - intro, by bonfireNext to the Archdragon Peak bonfire
AP: Great Magic Barrier - drop off belfry roofDropping down to the left from the area with the Havel Knight and the dead Wyvern
AP: Havel's Greatshield - belfry roof, NPC dropDropped from any of the Havel Knights
AP: Havel's Ring+1 - summit, after buildingJust past the building with all of the Man-Serpents on the way to the Dragon Altar, on the left side
AP: Homeward Bone - intro, path to bonfireFrom the start of the area, along the left path leading to the first bonfire
AP: Large Soul of a Crestfallen Knight - summit, by fountainIn the middle of the open fountain area after the Belfry bonfire
AP: Large Soul of a Nameless Soldier - fort, by stairs to first roomto the left of where the Ancient Wyvern lands
AP: Large Soul of a Weary Warrior - fort, centerWhere the Ancient Wyvern lands
AP: Lightning Bolt - rotundaOn top of the ruined dome found going up spiral stairs to the left before the bridge with the chainaxe Man-Serpent
AP: Lightning Clutch Ring - intro, left of boss doorTo the left of gate leading to Ancient Wyvern, past the Rock Lizard
AP: Lightning Gem - intro, side riseFrom the start of the area, up a ledge in between two forked paths toward the first bonfire
AP: Lightning Urn - fort, left of first room entranceOn the path to the left of where the Ancient Wyvern lands, left of the building entrance
AP: Ricard's Rapier - belfry, NPC dropDropped by the Richard Champion summoned by the Serpent-Man Summoner
AP: Ring of Steel Protection - fort overlook, beside stairsTo the right of the area where the Ancient Wyvern lands, dropping down onto the ledge
AP: Soul of a Crestfallen Knight - mausoleum, upstairsFrom the Mausoleum bonfire, up the second set of stairs to the right
AP: Soul of a Nameless Soldier - intro, right before archwayFrom the Archdragon Peak bonfire, going right before the arch before Ancient Wyvern
AP: Soul of a Weary Warrior - intro, first cliff edgeAt the very start of the area on the left cliff edge
AP: Soul of a Weary Warrior - walkway, building windowOn the way to the Belfry bonfire after the sagging wooden bridge, on a ledge visible in a room with a Crystal Lizard, accessible by a tricky jump or just going around the other side
AP: Soul of the Nameless KingDropped by Nameless King
AP: Stalk Dung Pie - fort overlookFrom the right of where Ancient Wyvern first lands
AP: Thunder Stoneplate Ring - walkway, up ladderAfter the long hallway after the Mausoleum bonfire, before the rope bridge, up the long ladder
AP: Titanite Chunk - fort, second room balconyAfter going left of where Ancient Wyvern lands and left again, rather than going up the stairs to the right, go to the open area to the left
AP: Titanite Chunk - intro, archway cornerFrom the Archdragon Peak bonfire, under the arch, immediately to the right
AP: Titanite Chunk - intro, behind rockAlmost at the Archdragon Peak bonfire, behind a rock in the area with many Man-Serpents
AP: Titanite Chunk - intro, left before archwayAfter the Archdragon Peak bonfire, going left before the arch before Ancient Wyvern
AP: Titanite Chunk - rotundaOn top of the ruined dome found going up spiral stairs to the left before the bridge with the chainaxe Man-Serpent
AP: Titanite Chunk - walkway, miniboss dropDropped by the second Ancient Wyvern patrolling the path up to the Belfry
AP: Titanite Scale - mausoleum, downstairs balcony #1From the Mausoleum bonfire, up the stairs to the left, past the Rock Lizard
AP: Titanite Scale - mausoleum, downstairs balcony #2From the Mausoleum bonfire, up the stairs to the left, past the Rock Lizard
AP: Titanite Scale - mausoleum, upstairs balconyFrom the Mausoleum bonfire, up the first stairs to the right, going around toward the Man-Serpent Summoner, on the balcony on the side
AP: Titanite Scale - walkway buildingIn a chest after the sagging wooden bridge on the way to the Belfry, in the building with the Crystal Lizard
AP: Titanite Scale - walkway, miniboss dropDropped by the second Ancient Wyvern patrolling the path up to the Belfry
AP: Titanite Slab - belfry roofNext to the Havel Knight by the dead Wyvern
AP: Titanite Slab - plazaIn the Nameless King boss arena after he is defeated
AP: Twinkling Dragon Torso Stone - summit, gesture at altarReceived using Path of the Dragon at the Altar after the Belfry bonfire. Hawkwood also uses the gesture there when summoned.
AP: Twinkling Titanite - belfry, by ladder to roofIn the chest before the ladder climbing up to the Havel Knight
AP: Twinkling Titanite - fort, down second room balcony ladderAfter going left of where Ancient Wyvern lands and left again, rather than going up the stairs to the right, go to the open area to the left and then down the ladder
AP: Twinkling Titanite - fort, end of raftersDropping down to the left of the Mausoleum bonfire, all the way down the wooden rafters
AP: Twinkling Titanite - walkway building, lizardDropped by Crystal Lizard in the building after the sagging wooden bridge toward the Belfry
AP: Twinkling Titanite - walkway, miniboss dropDropped by the second Ancient Wyvern patrolling the path up to the Belfry
CA: Coiled Sword - boss dropDropped by Iudex Gundyr
CA: Firebomb - down the cliff edgeAlong the cliff edge before the Iudex Gundyr fight, to the right
CA: Soul of a Deserted Corpse - right of spawnAt the very start of the game
CA: Soul of an Unknown Traveler - by minibossIn the area with the Ravenous Crystal Lizard
CA: Speckled Stoneplate Ring+1 - by minibossIn the area with the Ravenous Crystal Lizard, along the right wall
CA: Titanite Scale - miniboss dropDropped by Ravenous Crystal Lizard
CA: Titanite Shard - jump to coffinMaking a jump to a coffin after the Cemetery of Ash bonfire
CC: Black Blade - tomb, mimicDropped by the mimic before Smouldering Lake
CC: Black Bug Pellet - cavern, before bridgeIn the area where many many skeletons are before the bridge you can cut
CC: Bloodred Moss Clump - atrium lower, down more stairsTo the left before going down the main stairwell in the Catacombs, past the skeleton ambush and where Anri is standing, near the Crystal Lizard
CC: Carthus Bloodring - crypt lower, end of side hallAt the very end of the Bonewheel Skeleton area
CC: Carthus Milkring - crypt upper, among potsAfter the first Skeleton Ball, in the hallway alcove with the many dark-exploding pots
CC: Carthus Pyromancy Tome - atrium lower, jump from bridgeDown the hallway to the right before going down the main stairwell in the Catacombs and through an illusory wall on the left, or making a difficult dropdown from the top-level platform
CC: Carthus Rouge - atrium upper, left after entranceTo the right after first entering the Catacombs
CC: Carthus Rouge - crypt across, cornerMaking a difficult jump between the hallway after the first Skeleton Ball and the area at the same level on the opposite side, or going up the stairs from the main hall
CC: Dark Gem - crypt lower, skeleton ball dropDropped by second Skeleton Ball after killing its sorcerer skeleton
CC: Ember - atrium, on long stairwayOn the main stairwell in Catacombs
CC: Ember - crypt lower, shortcut to cavernIn the short hallway with the level shortcut where Knight Slayer Tsorig invades
CC: Ember - crypt upper, end of hall past holeGoing right from the Catacombs bonfire, down the hall to the left, then to the right. After a hole that drops down into the Bonewheel Skeleton area.
CC: Fire Gem - cavern, lizardDropped by a Crystal Lizard found between the Catacombs main halls and the ledge overlooking the bridge you can cut down
CC: Grave Warden Pyromancy Tome - boss arenaIn Wolnir's arena, or in the back left of the room containing his bonfire if not picked up in the arena
CC: Grave Warden's Ashes - crypt across, cornerFrom the Catacombs bonfire, down the stairs into the main hall and up the stairs to the other side, on the far left side. Stairwell past the illusory wall is most direct.
CC: Homeward Bone - Irithyll bridgeFound right before the wall blocking access to Irithyll
CC: Large Soul of a Nameless Soldier - cavern, before bridgeIn the area where many many skeletons are before the bridge you can cut
CC: Large Soul of a Nameless Soldier - tomb lowerDown the ramp from the Fire Demon, where all the skeletons are
CC: Large Soul of an Unknown Traveler - crypt upper, hall middleGoing right from the Catacombs bonfire, then down the long hallway after the hallway to the left
CC: Large Titanite Shard - crypt across, middle hallFrom the Catacombs bonfire, down the stairs into the main hall and up the stairs to the other side, in a middle hallway
CC: Large Titanite Shard - crypt upper, skeleton ball hallGoing right from the Catacombs bonfire, to the end of the hallway where second Skeleton Ball rolls
CC: Large Titanite Shard - tomb lowerDown the ramp from the Fire Demon, where all the skeletons are
CC: Old Sage's Blindfold - tomb, hall before bonfireDown the ramp from the Fire Demon, straight down the hallway past the room with the Abandoned Tomb bonfire
CC: Pontiff's Right Eye - Irithyll bridge, miniboss dropDropped by killing Sulyvahn's Beast on the bridge to Irithyll or in the lake below
CC: Ring of Steel Protection+2 - atrium upper, drop onto pillarFrom the first bridge in Catacombs where the first skeletons are encountered, parallel to the long stairwell, walk off onto a pillar on the left side.
CC: Sharp Gem - atrium lower, right before exitDown the hallway to the right before going down the main stairwell in the Catacombs
CC: Soul of High Lord WolnirDropped by High Lord Wolnir
CC: Soul of a Demon - tomb, miniboss dropDropped by the Fire Demon before Smouldering Lake
CC: Soul of a Nameless Soldier - atrium lower, down hallAll the way down the hallway to the right before going down the main stairwell in the Catacombs
CC: Soul of a Nameless Soldier - atrium upper, up more stairsFrom the room before the Catacombs main stairwell, up the two ramps and to the end of the long hallway crossing the room
CC: Thunder Stoneplate Ring+1 - crypt upper, among potsAfter the first Skeleton Ball, in the hallway alcove with the many dark-exploding pots, behind one of the pillars
CC: Titanite Shard - atrium lower, corner by stairsTo the left before going down the main stairwell in the Catacombs, behind the pensive Carthus Cursed Sword Skeleton
CC: Titanite Shard - crypt lower, left of entranceIn the main hall after the Catacombs bonfire, down the stairs and to the left
CC: Titanite Shard - crypt lower, start of side hallIn the Bonewheel Skeleton area, on the left side under a Writhing Flesh
CC: Twinkling Titanite - atrium lower, lizard down more stairsDropped by a Crystal Lizard found to the left before going down the main stairwell in the Catacombs, past the skeleton ambush and past where Anri is standing
CC: Undead Bone Shard - crypt upper, skeleton ball dropDropped by first Skeleton Ball after killing its sorcerer skeleton
CC: Witch's Ring - tomb, hall before bonfireDown the ramp from the Fire Demon, straight down the hallway past the room with the Abandoned Tomb bonfire
CC: Yellow Bug Pellet - cavern, on overlookTo the right of the Carthus Curved Sword Skeleton overlooking the pit Horace falls into
CD: Aldrich's Sapphire - side chapel, miniboss dropDropped by the Deep Accursed
CD: Arbalest - upper roofs, end of furthest buttressBefore the rafters on the way to Rosaria, up a flying buttress, past a halberd-wielding Large Hollow Soldier to the right, and down another flying buttress to the right
CD: Archdeacon Holy Garb - boss room after killing bossNear the Deacons of the Deep bonfire, found after resting at it
CD: Archdeacon Skirt - boss room after killing bossNear the Deacons of the Deep bonfire, found after resting at it
CD: Archdeacon White Crown - boss room after killing bossNear the Deacons of the Deep bonfire, found after resting at it
CD: Armor of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Astora Greatsword - graveyard, left of entranceDown one of the side paths to the left in the Reanimated Corpse area
CD: Barbed Straight Sword - Kirk dropDropped by Longfinger Kirk when he invades in the cathedral central room
CD: Black Eye Orb - Rosaria from Leonhard's questOn Rosaria's corpse after joining Rosaria's Fingers, exhausting Leonhard's dialogue there and reaching the Profaned Capital bonfire.
CD: Blessed Gem - upper roofs, raftersIn the rafters leading to Rosaria, guarded by a Cathedral Knight to the right
CD: Crest Shield - path, drop down by Cathedral of the Deep bonfireOn a grave near the Cathedral of the Deep bonfire, accessed by dropping down to the right
CD: Curse Ward Greatshield - by ladder from white tree to moatTaking a right after the Infested Corpse graveyard, before the shortcut ladder down to the Ravenous Crystal Lizard area
CD: Deep Braille Divine Tome - mimic by side chapelDropped by the Mimic before the room with the patrolling Cathedral Knight and Deep Accursed
CD: Deep Gem - down stairs by first elevatorComing from the room where you first see deacons, go down instead of continuing to the main cathedral room. Guarded by a pensive Cathedral Evangelist.
CD: Deep Ring - upper roofs, passive mob drop in first towerDropped by the passive Deacon on the way to Rosaria
CD: Drang Armor - main hall, eastIn the Giant Slave muck pit leading up to Deacons
CD: Drang Gauntlets - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Drang Hammers - main hall eastIn the Giant Slave muck pit leading up to Deacons, underneath the stairwell
CD: Drang Shoes - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Duel Charm - by first elevatorAfter opening the cathedral's backdoor, where the Deacon enemies are first seen, under a fountain that spouts poison
CD: Duel Charm - next to Patches in onion armorTo the right of the bridge leading to Rosaria, from the Deacons side. Patches will lower the bridge if you try to cross from this side.
CD: Ember - PatchesSold by Patches in Firelink Shrine
CD: Ember - by back doorPast the pair of Grave Wardens and the Cathedral backdoor against a wall, guarded by a greataxe-wielding Large Hollow Soldier
CD: Ember - edge of platform before bossOn the edge of the chapel before Deacons overlooking the Giant Slaves
CD: Ember - side chapel upstairs, up ladderUp a ladder and past the Cathedral Evangelist from the top level of the room with the patrolling Cathedral Knight and Deep Accursed
CD: Ember - side chapel, miniboss roomIn the room with the Deep Accursed
CD: Estus Shard - monument outside Cleansing ChapelRight outside of the Cleansing Chapel. Requires killing praying hollows.
CD: Executioner's Greatsword - graveyard, far endIn an open area down one of the side paths to the left in the Reanimated Corpse area
CD: Exploding Bolt - ledge above main hall southOn the ledge where the Giant Slave slams his arms down
CD: Fading Soul - graveyard, far endIn an open area down one of the side paths to the left in the Reanimated Corpse area, next to the Executioner's Greatsword
CD: Gauntlets of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Helm of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Herald Armor - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Herald Gloves - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Herald Helm - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Herald Trousers - path, by fireGuarded by the Cathedral Evangelist after the Crystal Sage fight
CD: Heysel Pick - Heysel Corpse-Grub in Rosaria's Bed ChamberDropped by the Heysel Corpse-grub in Rosaria's Bed Chamber
CD: Homeward Bone - outside main hall south doorPast the cathedral doors guarded by the Giant Slave opposite to the Deacons fight
CD: Horsehoof Ring - PatchesSold or dropped by Patches after he mentions Greirat
CD: Large Soul of an Unknown Traveler - by white tree #1In the graveyard with the White Birch and Infested Corpses
CD: Large Soul of an Unknown Traveler - by white tree #2In the graveyard with the White Birch and Infested Corpses
CD: Large Soul of an Unknown Traveler - lower roofs, semicircle balconyOn the cathedral roof after climbing up the flying buttresses, on the edge of the semicircle platform balcony
CD: Large Soul of an Unknown Traveler - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Large Soul of an Unknown Traveler - main hall south, side pathDown a side path with poison-spouting fountains in the main cathedral room, accessible from the Cleansing Chapel shortcut, patrolled by a Cathedral Knight
CD: Large Soul of an Unknown Traveler - path, against outer wallFrom the Cathedral of the Deep bonfire after the Brigand, against the wall in the area with the dogs and crossbowmen
CD: Leggings of Thorns - Rosaria's Bed Chamber after killing KirkFound in Rosaria's Bed Chamber after killing Longfinger Kirk
CD: Lloyd's Sword Ring - ledge above main hall southOn the ledge where the Giant Slave slams his arms down
CD: Maiden Gloves - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Maiden Hood - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Maiden Robe - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Maiden Skirt - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Notched Whip - Cleansing ChapelIn a corner of the Cleansing Chapel
CD: Paladin's Ashes - path, guarded by lower NPCAt the very start of the area, guarded by the Fallen Knight
CD: Pale Tongue - main hall eastIn the Giant Slave muck pit leading up to Deacons
CD: Pale Tongue - upper roofs, outdoors far endBefore the rafters on the way to Rosaria, up a flying buttress and straight right, passing a halberd-wielding Large Hollow Soldier
CD: Poisonbite Ring - moat, hall past minibossIn the pit with the Infested Corpse, accessible from the Ravenous Crystal Lizard area or from dropping down near the second Cleansing Chapel shortcut
CD: Red Bug Pellet - lower roofs, up stairs between buttressesIn the area after the cathedral roof against the wall of the cathedral, down the path from the Cathedral Evangelist.
CD: Red Bug Pellet - right of cathedral front doorsUp the stairs past the Infested Corpse graveyard and the left, toward the roof path to the right of the cathedral doors
CD: Red Sign Soapstone - passive mob drop by Rosaria's Bed ChamberDropped by passive Corpse-grub against the wall near the entrance to Rosaria's Bed Chamber
CD: Repair Powder - by white treeIn the graveyard with the White Birch and Infested Corpses
CD: Ring of Favor+2 - upper roofs, on buttressBefore the rafters on the way to Rosaria, up a flying buttress, behind a greataxe-wielding Large Hollow Soldier to the left
CD: Ring of the Evil Eye+1 - by stairs to bossBefore the stairs leading down into the Deacons fight
CD: Rosaria's Fingers - RosariaGiven by Rosaria.
CD: Rusted Coin - don't forgive PatchesGiven by Patches after not forgiving him after he lowers the bridge in Cathedral of the Deep.
CD: Rusted Coin - left of cathedral front doors, behind cratesUp the stairs past the Infested Corpse graveyard and to the left, hidden behind some crates to the left of the cathedral door
CD: Saint Bident - outside main hall south doorPast the cathedral doors guarded by the Giant Slave opposite to the Deacons fight
CD: Saint-tree Bellvine - moat, by waterIn the Infested Corpse moat beneath the Cathedral
CD: Seek Guidance - side chapel upstairsAbove the room with the patrolling Cathedral Knight and Deep Accursed, below a writhing flesh on the ceiling.
CD: Shotel - PatchesSold by Patches
CD: Small Doll - boss dropDropped by Deacons of the Deep
CD: Soul of a Nameless Soldier - ledge above main hall southOn the ledge where the Giant Slave slams his arms down
CD: Soul of a Nameless Soldier - lower roofs, side roomComing from the cathedral roof, past the three crossbowmen to the path patrolled by the halberd-wielding Large Hollow Soldier, in a room to the left with many thralls.
CD: Soul of a Nameless Soldier - main hall southIn the muck pit with the Giant Slave that can attack with his arms
CD: Soul of the Deacons of the DeepDropped by Deacons of the Deep
CD: Spider Shield - NPC drop on pathDropped by the brigand at the start of Cathedral of the Deep
CD: Spiked Shield - Kirk dropDropped by Longfinger Kirk when he invades in the cathedral central room
CD: Titanite Scale - moat, miniboss dropDropped by the Ravenous Crystal Lizard outside of the Cathedral
CD: Titanite Shard - Cleansing Chapel windowsill, by minibossOn the ledge dropping back down into Cleansing Chapel from the area with the Ravenous Crystal Lizard
CD: Titanite Shard - moat, far endBehind the cathedral near the Infested Corpse moat, going from the Ravenous Crystal Lizard
CD: Titanite Shard - moat, up a slopeUp one of the slopes in the Ravenous Crystal Lizard area
CD: Titanite Shard - outside building by white treePast the Infested Corpse graveyard to the left, hidden along the left wall of the building with the shortcut ladder and Curse Ward Greatshield
CD: Titanite Shard - path, side path by Cathedral of the Deep bonfireUp a path to the left after the Cathedral of the Deep bonfire, after the Fallen Knight and before the Brigand
CD: Twinkling Titanite - moat, lizard #1Dropped by the Crystal Lizard behind the cathedral near the Infested Corpse moat, going from the Ravenous Crystal Lizard
CD: Twinkling Titanite - moat, lizard #2Dropped by the Crystal Lizard under the cathedral near the Infested Corpse moat, going from the Ravenous Crystal Lizard
CD: Twinkling Titanite - path, lizard #1Dropped by the first Crystal Lizard after the Crystal Sage fight
CD: Twinkling Titanite - path, lizard #2Dropped by the second Crystal Lizard after the Crystal Sage fight
CD: Undead Bone Shard - gravestone by white treeIn the graveyard with the Infested Corpses, on a coffin partly hanging off of the ledge
CD: Undead Hunter Charm - lower roofs, up stairs between buttressesIn the area after the cathedral roof guarded by a Cathedral Evangelist. Can be jumped to from a flying buttress or by going around and back
CD: Winged Spear - kill PatchesDropped by Patches when killed in his own armor.
CD: Xanthous Crown - Heysel Corpse-Grub in Rosaria's Bed ChamberDropped by the Heysel Corpse-grub in Rosaria's Bed Chamber
CD: Young White Branch - by white tree #1By the White Birch tree in the Infested Corpse graveyard
CD: Young White Branch - by white tree #2By the White Birch tree in the Infested Corpse graveyard
CKG: Black Firebomb - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Claw - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Dark Gem - under lone stairwayFollowing the left wall, behind the standalone set of stairs
CKG: Dragonscale Ring - shortcut, leave halfway down liftFrom the middle level of the second elevator, toward the Oceiros boss fight
CKG: Drakeblood Armor - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Drakeblood Gauntlets - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPCOn the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner
CKG: Estus Shard - balconyFrom the middle level of the first Consumed King's Gardens elevator, out the balcony and to the right
CKG: Human Pine Resin - by lone stairway bottomOn the right side of the garden, following the wall past the entrance to the shortcut elevator building, in a toxic pool
CKG: Human Pine Resin - toxic pool, past rotundaIn between two platforms near the middle of the garden, by a tree in a toxic pool
CKG: Magic Stoneplate Ring - mob drop before bossDropped by the Cathedral Knight closest to the Oceiros fog gate
CKG: Ring of Sacrifice - under balconyAlong the right wall of the garden, next to the first elevator building
CKG: Sage Ring+2 - balcony, drop onto rubble, jump backFrom the middle platform of the first elevator in the target, going out and dropping off to the left, and then running off onto the ruined arch behind.
CKG: Shadow Garb - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Shadow Gauntlets - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Shadow Leggings - under rotundaUnder the platform in the middle of the garden, in the toxic pool
CKG: Shadow Mask - under center platformUnder the platform in the middle of the garden, in the toxic pool
CKG: Soul of Consumed OceirosDropped by Consumed King Oceiros
CKG: Soul of a Weary Warrior - before first liftOn the path leading to the first elevator from Lothric Castle
CKG: Titanite Chunk - balcony, drop onto rubbleFrom the middle platform of the first elevator, dropping down to the left
CKG: Titanite Chunk - right of shortcut lift bottomOn the right side of the garden, following the wall past the entrance to the shortcut elevator building, all the way to the end
CKG: Titanite Chunk - shortcutRight inside of the shortcut door leading to Oceiros from Lothric/Dancer bonfire
CKG: Titanite Chunk - up lone stairwayFollowing the left wall of the garden, in and up the standalone set of stairs
CKG: Titanite Scale - shortcutIn the room leading to the Oceiros shortcut elevator from Lothric/Dancer, in the first floor alcove.
CKG: Titanite Scale - tomb, chest #1Chest after Oceiros fight
CKG: Titanite Scale - tomb, chest #2Chest after Oceiros fight
CKG: Wood Grain Ring+1 - by first elevator bottomBehind the first elevator going down into the garden, in the toxic pool
DH: Aquamarine Dagger - castle, up stairsUp the second flight of stairs to the left of the starting area with the murkmen, before the long drop
DH: Black Firebomb - ruins, up windmill from bonfireTo the left of the Earthen Peak Ruins bonfire, past the ruined windmill, next to many Poisonhorn bugs.
DH: Covetous Silver Serpent Ring+3 - pantry upstairs, drop downAfter exiting the building with the Lothric Knights where the front crumbles, to the last room of the building to the right, up stairs past an illusory wall to the left, then dropping down after exiting the building from the last room.
DH: Desert Pyromancer Garb - ruins, by shack near cliffBehind a shack near the edge of the cliff of the area targeted by the second angel.
DH: Desert Pyromancer Gloves - swamp, far rightAfter dropping down in the poison swamp area, against the wall straight to the right.
DH: Desert Pyromancer Hood - swamp upper, tunnel endAt the end of the tunnel with Desert Pyromancy Zoey, to the right of the final branches.
DH: Desert Pyromancer Skirt - swamp right, by rootsIn the poison swamp, against a tree guarded by a few Poisonhorn bugs in the front right.
DH: Divine Blessing - library, after dropAfter the dropdown where an angel first targets you, behind you
DH: Divine Blessing - shopSold by Stone-humped Hag, or in her ashes
DH: Divine Blessing - swamp upper, building roofOn a rooftop of one of the buildings bordering the poison swamp. Can be reached by dropping down from the final tree branch and accessing the roof to the right.
DH: Ember - castle, behind spireAt the start of the area, behind a spire to the right of first drop down
DH: Ember - pantry, behind crates just before upstairsAfter exiting the building with the Lothric Knights where the front crumbles, to the last room end of the building to the right, up stairs past an illusory wall to the left, in the second-to-last room of the sequence, behind some crates to the left.
DH: Ember - ruins, alcove before swampIn an alcove providing cover from the second angel's projectiles, before dropping down in the poison swamp area.
DH: Ember - ruins, alcove on cliffIn the area with the pilgrim responsible for the second angel, below the Within Earthen Peak Ruins bonfire. Can be accessed by dropping down from a cliff edge, dropping down to the right of the bonfire.
DH: Ember - shopSold by Stone-humped Hag, or in her ashes
DH: Flame Fan - swamp upper, NPC dropDropped by Desert Pyromancer Zoey
DH: Giant Door Shield - ruins, path below far shackDescending down a path from the edge of the cliff of the area targeted by the second angel, to the very end of the cliff.
DH: Great Soul Dregs - pantry upstairsAfter exiting the building with the Lothric Knights where the front crumbles, to the last room of the building to the right, up stairs past an illusory wall to the left, then all the way to the end of the last room.
DH: Harald Curved Greatsword - swamp left, under rootIn the back leftmost area of the poison swamp, underneath the tree branch leading up and out, guarded by a stationary Harald Legion Knight.
DH: Hidden Blessing - shopSold by Stone-humped Hag, or in her ashes
DH: Homeward Bone - end of path from churchImmediately before dropping into the area with the Earthen Peak Ruins bonfire, next to Gael's flag.
DH: Homeward Bone - swamp left, on rootAll the way to the end of a short path in the back leftmost area of the poison swamp, where you can plunge attack the stationary Harald Legion Knight.
DH: Large Soul of a Weary Warrior - parapets, hallAfter crossing the spire bridge that crashes into the building with the Lothric Knights, past Lapp's initial location, dropping down behind the murkman and dropping down again, in a corner to the left.
DH: Large Soul of a Weary Warrior - swamp centerIn the middle of the poison swamp.
DH: Large Soul of a Weary Warrior - swamp, under overhangIn the cavern adjacent to the poison swamp, surrounded by a few Poisonhorn bugs.
DH: Lightning Urn - wall outside churchAfter the dropdown where an angel first targets you, against the wall on the left.
DH: Loincloth - swamp, left edgeIn the leftmost edge of the poison swamp after dropping down, guarded by 6 Poisonhorn bugs.
DH: Lothric War Banner - parapets, end of hallAfter crossing the spire bridge that crashes into the building with the Lothric Knights, past Lapp's initial location, dropping down behind the murkman and dropping down again, at the end of the hallway to the right.
DH: Murky Hand Scythe - library, behind bookshelvesAfter the first long drop into the building which looks like Grand Archives, to the left up the bookshelf stairs and behind the bookshelves
DH: Murky Longstaff - pantry, last roomAfter exiting the building with the Lothric Knights where the front crumbles, in the third-furthest room in the building to the right.
DH: Prism Stone - swamp upper, tunnel startNear the start of the tunnel with Desert Pyromancer Zoey.
DH: Projected Heal - parapets balconyAfter crossing the spire bridge that crashes into the building with the Lothric Knights, past Lapp's initial location, dropping down behind the murkman, against a wall in the area with the Lothric War Banner Knight and many murkmen.
DH: Purple Moss Clump - swamp shackIn the ruined shack with Poisonhorn bugs straight ahead of the dropdown into the poison swamp area.
DH: Ring of Favor+3 - swamp right, up rootUp the long branch close to the dropdown into the poison swamp area, in front of the cavern.
DH: Ring of Steel Protection+3 - ledge before churchAfter the dropdown where an angel first targets you, on an exposed edge to the left. Difficult to get without killing the angel.
DH: Rusted Coin - behind fountain after churchAfter exiting the building with the Lothric Knights where the front crumbles, behind the fountain on the right side.
DH: Rusted Gold Coin - shopSold by Stone-humped Hag, or in her ashes
DH: Siegbräu - LappGiven by Lapp after collecting the Titanite Slab in Earthen Peak Ruins, or left after Demon Princes fight, or dropped upon death if not given.
DH: Small Envoy Banner - boss dropFound in the small room after beating Demon Prince.
DH: Soul of a Crestfallen Knight - church, altarIn the building where the front crumbles, guarded by the two Lothric Knights at the front of the chapel.
DH: Soul of a Weary Warrior - castle overhangThe bait item at the start of the area which falls down with you into the ruined building below.
DH: Soul of the Demon PrinceDropped by Demon Prince
DH: Splitleaf Greatsword - shopSold by Stone-humped Hag, or in her ashes
DH: Titanite Chunk - castle, up stairsUp first flight of stairs to the left of the starting area with the murkmen, before the long drop
DH: Titanite Chunk - pantry, first roomAfter exiting the building with the Lothric Knights where the front crumbles, on a ledge in the first room of the building to the right.
DH: Titanite Chunk - path from church, by pillarBefore dropping into the area with the Earthen Peak Ruins bonfire, behind a pillar in front of a murkman pool.
DH: Titanite Chunk - ruins, by far shackIn front of a shack at the far edge of the cliff of the area targeted by the second angel. There is a shortcut dropdown to the left of the building.
DH: Titanite Chunk - ruins, path from bonfireAt the Earthen Peak Ruins bonfire, straight a bit then all the way left, near the edge of the cliff in the area targeted by the second angel.
DH: Titanite Chunk - swamp right, drop partway up rootPartway up the long branch close to the dropdown into the poison swamp area, in front of the cavern, dropping down to a branch to the left.
DH: Titanite Chunk - swamp, along buildingsAfter dropping down into the poison swamp, along the buildings on the left side.
DH: Titanite Chunk - swamp, path to upperPartway up the branch that leads out of the poison swamp, on a very exposed branch jutting out to the left.
DH: Titanite Scale - library, back of roomAfter the first long drop into the building which looks like Grand Archives, behind you at the back of the room
DH: Titanite Scale - swamp upper, drop and jump into towerAt the very end of the last tree branch before dropping down toward the Within Earthen Peak Ruins bonfire, drop down to the left instead. Make a jump into the interior of the overturned tower to the left.
DH: Titanite Slab - swamp, path under overhangDeep within the cavern adjacent to the poison swamp, to the back and then left. Alternatively, given by Lapp after exhausting dialogue near the bonfire and dying, or left after he moves on, or dropped upon death if not given.
DH: Twinkling Titanite - library, chandelierAfter the first long drop into the building which looks like Grand Archives, straight ahead hanging from a chandelier on the ground
DH: Twinkling Titanite - path after church, mob dropDropped the pilgrim responsible for the first angel encountered, below the spire bridge that forms by crashing into the building.
DH: Twinkling Titanite - ruins, alcove on cliff, mob dropDropped by the pilgrim responsible for the second angel, below the Within Earthen Peak Ruins bonfire. Can be accessed by dropping down from a cliff edge, or dropping down to the right of the bonfire.
DH: Twinkling Titanite - ruins, root near bonfireTreasure visible straight ahead of the Earthen Peak Ruins bonfire on a branch. Can be accessed by following the right wall from the bonfire until a point of access onto the branch is found.
DH: Twinkling Titanite - swamp upper, drop onto rootOn the final tree branches before dropping down toward the Within Earthen Peak Ruins bonfire, drop down on a smaller branch to the right. This loops back to the original branch.
DH: Twinkling Titanite - swamp upper, mob drop on roofDropped by the pilgrim responsible for the third angel in the swamp. Rather than heading left into the tunnel with Desert Pyromancy Zoey, go right onto a shack roof. Drop down onto a tree branch at the end, then drop down to another roof.
FK: Antiquated Dress - hidden caveIn a chest in the cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Antiquated Gloves - hidden caveIn a chest in the cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Antiquated Skirt - hidden caveIn a chest in the cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Atonement - perimeter, drop down into swampDropping down from the Farron Keep Perimeter building, to the right past the bonfire, before the stairs going up
FK: Black Bow of Pharis - miniboss drop, by keep ruins near wallDropped the Elder Ghru on the left side of the group of three to the left of the Keep Ruins bonfire, as approached from the ritual fire.
FK: Black Bug Pellet - perimeter, hill by boss doorOn the small hill to the right of the Abyss Watchers entrance, guarded by a spear-wielding Ghru Grunt
FK: Cinders of a Lord - Abyss WatcherDropped by Abyss Watchers
FK: Crown of Dusk - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Dark Stoneplate Ring+2 - keep ruins ritual island, behind wallHidden behind the right wall of the ritual fire before Keep Ruins
FK: Dragon Crest Shield - upper keep, far side of the wallUp the elevator from Old Wolf of Farron bonfire, and dropping down to Crystal Lizard area, in the open.
FK: Dreamchaser's Ashes - keep proper, illusory wallNear the Old Wolf of Farron bonfire, behind an illusory wall near the Crystal Lizard
FK: Ember - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Ember - perimeter, path to bossGuarded by a spear-wielding Ghru Grunt to the right of the main path leading up to Abyss Watchers
FK: Ember - upper keep, by miniboss #1Guarded by Stray Demon, up from the Old Wolf of Farron bonfire
FK: Ember - upper keep, by miniboss #2Guarded by Stray Demon, up from the Old Wolf of Farron bonfire
FK: Estus Shard - between Farron Keep bonfire and left islandStraight ahead from the Farron Keep bonfire to the ritual fire stairs, guarded by a slug
FK: Gold Pine Bundle - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Golden Scroll - hidden caveIn a cave found along the keep wall in the basilisk area, with the Elizabeth corpse
FK: Great Magic Weapon - perimeter, by door to Road of SacrificesNext to the shortcut leading from Farron Keep Perimeter back into Crucifixion Woods, past the Ravenous Crystal Lizard
FK: Greataxe - upper keep, by minibossGuarded by Stray Demon, up from the Old Wolf of Farron bonfire
FK: Greatsword - ramp by keep ruins ritual islandIn the middle of the swamp, on the pair of long ramps furthest from the Farron Keep bonfire, going out forward and slightly right from the bonfire.
FK: Havel's Armor - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Havel's Gauntlets - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Havel's Helm - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Havel's Leggings - upper keep, after killing AP belfry roof NPCAppears by Stray Demon, up from the Old Wolf of Farron bonfire, after the Havel Knight guarding the Titanite Slab in Archdragon Peak has been killed
FK: Heavy Gem - upper keep, lizard on stairsDropped by the Crystal Lizard that scurries up the stairs in the area dropping down from near Stray Demon, up from Old Wolf of Farron bonfire
FK: Hollow Gem - perimeter, drop down into swampDropping down from the Farron Keep Perimeter building, to the right past the bonfire, before the stairs going up
FK: Homeward Bone - right island, behind fireBehind the ritual fire with stairs guarded by Elder Ghrus/basilisks
FK: Iron Flesh - Farron Keep bonfire, right after exitIn the open in the swamp, heading straight right from Farron Keep bonfire
FK: Large Soul of a Nameless Soldier - corner of keep and right islandHidden in a corner to the right of the stairs leading up to the ritual fire from the basilisk area
FK: Large Soul of a Nameless Soldier - near wall by right islandTo the left of the stairs leading up to the ritual fire from the Basilisk area, by the keep wall
FK: Large Soul of an Unknown Traveler - by white treeOn a tree close to the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Large Titanite Shard - upper keep, lizard by wyvernDropped by the farther Crystal Lizard in the area dropping down from near Stray Demon, up from Old Wolf of Farron bonfire
FK: Large Titanite Shard - upper keep, lizard in openDropped by the closer Crystal Lizard in the area dropping down from near Stray Demon, up from Old Wolf of Farron bonfire
FK: Lightning Spear - upper keep, far side of the wallUp the elevator from Old Wolf of Farron bonfire, and dropping down to Crystal Lizard area, in the open.
FK: Lingering Dragoncrest Ring - by white tree, miniboss dropDropped by the Greater Crab patrolling the birch tree where the Giant shoots arrows
FK: Magic Stoneplate Ring+1 - between right island and wallBehind a tree in the basilisk area, heading directly right from Farron Keep bonfire
FK: Manikin Claws - Londor Pale Shade dropDropped by Londor Pale Shade when he invades near the basilisks, if Yoel or Yuria have been betrayed
FK: Nameless Knight Armor - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Nameless Knight Gauntlets - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Nameless Knight Helm - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Nameless Knight Leggings - corner of keep and right islandFrom the Keep Ruins bonfire to the ritual fire stairs patrolled by Elder Ghrus, along the edge of the ritual fire hill
FK: Pharis's Hat - miniboss drop, by keep ruins near wallDropped the Elder Ghru in the back of the group of three to the left of the Keep Ruins bonfire, as approached from the ritual fire.
FK: Poison Gem - near wall by keep ruins bridgeFrom the left of the bridge leading from the ritual fire to the Keep Ruins bonfire, guarded by the three Elder Ghru
FK: Prism Stone - by left island stairsOn an island to the left of the stairs leading up to the ritual fire straight ahead of the Farron Keep bonfire
FK: Purple Moss Clump - Farron Keep bonfire, around right cornerAlong the inner wall of the keep, making an immediate right from Farron Keep bonfire
FK: Purple Moss Clump - keep ruins, ritual islandClose to the ritual fire before the Keep Ruins bonfire
FK: Purple Moss Clump - ramp directly in front of Farron Keep bonfireIn the middle of the swamp, on the pair of long ramps closest to the Farron Keep bonfire, going out forward and slightly right from the bonfire.
FK: Ragged Mask - Farron Keep bonfire, around left cornerAlong the inner wall of the keep, making an immediate left from Farron Keep bonfire, guarded by slugs
FK: Repair Powder - outside hidden caveAlong the keep wall in the basilisk area, outside of the cave with the Elizabeth corpse and Golden Scroll
FK: Rotten Pine Resin - left island, behind fireIn the area behind the ritual fire which is straight ahead of the Farron Keep bonfire
FK: Rotten Pine Resin - outside pavilion by left islandFrom the Farron Keep bonfire straight ahead to the pavilion guarded by the Darkwraith, just to the left of the ritual fire stairs
FK: Rusted Gold Coin - right island, behind wallHidden behind the right wall of the ritual fire with stairs guarded by Elder Ghrus/basilisks
FK: Sage's Coal - pavilion by left islandIn the pavilion guarded by a Darkwraith, straight ahead from the Farron Keep bonfire to the left of the ritual fire stairs
FK: Sage's Scroll - near wall by keep ruins bonfire islandAlong the keep inner wall, heading left from the stone doors past the crab area, surrounded by many Ghru enemies
FK: Shriving Stone - perimeter, just past stone doorsPast the stone doors, on the path leading up to Abyss Watchers by the Corvians
FK: Soul of a Nameless Soldier - by white treeNear the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Soul of a Stray Demon - upper keep, miniboss dropDropped by Stray Demon on the bridge above Farron Keep
FK: Soul of the Blood of the WolfDropped by Abyss Watchers
FK: Stone Parma - near wall by left islandAlong the inner wall of the keep, making a left from Farron Keep bonfire but before the area with the Darkwraith, guarded by a slug
FK: Sunlight Talisman - estus soup island, by ladder to keep properBy the pot of estus soup to the left of the stairs leading up to Old Wolf of Farron
FK: Titanite Scale - perimeter, miniboss dropDropped by Ravenous Crystal Lizard near the shortcut from Farron Keep back to Road of Sacrifices
FK: Titanite Shard - Farron Keep bonfire, left after exitAlong the inner wall of the keep, making a left from Farron Keep bonfire, by the second group of four slugs
FK: Titanite Shard - between left island and keep ruinsIn the swamp area with the Ghru Leaper between the Keep Ruins ritual fire and ritual fire straight ahead of Farron Keep bonfire, opposite from the keep wall
FK: Titanite Shard - by keep ruins ritual island stairsBy the stairs leading up to the Keep Ruins ritual fire from the middle of the swamp
FK: Titanite Shard - by ladder to keep properIn the swamp area close to the foot of the ladder leading to Old Wolf of Farron bonfire
FK: Titanite Shard - by left island stairsIn front of the stairs leading up to the ritual fire straight ahead of the Farron Keep bonfire
FK: Titanite Shard - keep ruins bonfire island, under rampUnder the ramp leading down from the Keep Ruins bonfire
FK: Titanite Shard - swamp by right islandBehind a tree patrolled by an Elder Ghru close to the ritual fire stairs
FK: Twinkling Dragon Head Stone - Hawkwood dropDropped by Hawkwood after killing him in the Abyss Watchers arena, after running up to the altar in Archdragon Peak. Twinkling Dragon Torso Stone needs to be acquired first.
FK: Twinkling Titanite - keep proper, lizardDropped by the Crystal Lizard on the balcony behind the Old Wolf of Farron bonfire
FK: Undead Bone Shard - pavilion by keep ruins bonfire islandIn a standalone pavilion down the ramp from Keep Ruins bonfire and to the right
FK: Watchdogs of Farron - Old WolfGiven by Old Wolf of Farron.
FK: Wolf Ring+1 - keep ruins bonfire island, outside buildingTo the right of the building with the Keep Ruins bonfire, when approached from the ritual fire
FK: Wolf's Blood Swordgrass - by ladder to keep properTo the left of the ladder leading up to the Old Wolf of Farron bonfire
FK: Young White Branch - by white tree #1Near the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FK: Young White Branch - by white tree #2Near the swamp birch tree patrolled by the greater crab, where the Giant shoots arrows
FS: Acid Surge - Cornyx for Carthus TomeSold by Cornyx after giving him the Carthus Pyromancy Tome
FS: Affinity - KarlaSold by Karla after recruiting her, or in her ashes
FS: Alluring Skull - Mortician's AshesSold by Handmaid after giving Mortician's Ashes
FS: Arstor's Spear - Ludleth for GreatwoodBoss weapon for Curse-Rotted Greatwood
FS: Aural Decoy - OrbeckSold by Orbeck
FS: Billed Mask - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Dress - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Fire Orb - Karla for Grave Warden TomeSold by Karla after giving her the Grave Warden Pyromancy Tome
FS: Black Flame - Karla for Grave Warden TomeSold by Karla after giving her the Grave Warden Pyromancy Tome
FS: Black Gauntlets - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Iron Armor - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Iron Gauntlets - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Iron Helm - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Iron Leggings - shop after killing TsorigSold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake
FS: Black Leggings - Yuria after killing KFF bossDropped by Yuria upon death or quest completion.
FS: Black Serpent - Ludleth for WolnirBoss weapon for High Lord Wolnir
FS: Blessed Weapon - Irina for Tome of LothricSold by Irina after giving her the Braille Divine Tome of Lothric
FS: Blue Tearstone Ring - GreiratGiven by Greirat upon rescuing him from the High Wall cell
FS: Boulder Heave - Ludleth for Stray DemonBoss weapon for Stray Demon
FS: Bountiful Light - Irina for Tome of LothricSold by Irina after giving her the Braille Divine Tome of Lothric
FS: Bountiful Sunlight - Ludleth for RosariaBoss weapon for Rosaria, available after Leonhard is killed
FS: Broken Straight Sword - gravestone after bossNear the grave after Iudex Gundyr fight
FS: Budding Green Blossom - shop after killing Creighton and AL bossSold by Handmaid after receiving Silvercat Ring item lot from Sirris and defeating Aldrich
FS: Bursting Fireball - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Caressing Tears - IrinaSold by Irina after recruiting her, or in her ashes
FS: Carthus Beacon - Cornyx for Carthus TomeSold by Cornyx after giving him the Carthus Pyromancy Tome
FS: Carthus Flame Arc - Cornyx for Carthus TomeSold by Cornyx after giving him the Carthus Pyromancy Tome
FS: Cast Light - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Chaos Bed Vestiges - Ludleth for Old Demon KingBoss weapon for Old Demon King
FS: Chaos Storm - Cornyx for Izalith TomeSold by Cornyx after giving him Izalith Pyromancy Tome
FS: Clandestine Coat - shop with Orbeck's AshesSold by Handmaid after giving Orbeck's Ashes and reloading
FS: Cleric's Candlestick - Ludleth for DeaconsBoss weapon for Deacons of the Deep
FS: Cracked Red Eye Orb - LeonhardGiven by Ringfinger Leonhard in Firelink Shrine after reaching Tower on the Wall bonfire
FS: Crystal Hail - Ludleth for SageBoss weapon for Crystal Sage
FS: Crystal Magic Weapon - Orbeck for Crystal ScrollSold by Orbeck after giving him the Crystal Scroll
FS: Crystal Sage's Rapier - Ludleth for SageBoss weapon for Crystal Sage
FS: Crystal Soul Spear - Orbeck for Crystal ScrollSold by Orbeck after giving him the Crystal Scroll
FS: Dancer's Armor - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dancer's Crown - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dancer's Enchanted Swords - Ludleth for DancerBoss weapon for Dancer of the Boreal Valley
FS: Dancer's Gauntlets - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dancer's Leggings - shop after killing LC entry bossSold by Handmaid after defeating Dancer of the Boreal Valley
FS: Dark Blade - Karla for Londor TomeSold by Irina or Karla after giving one the Londor Braille Divine Tome
FS: Dark Edge - KarlaSold by Karla after recruiting her, or in her ashes
FS: Dark Hand - Yoel/YuriaSold by Yuria
FS: Darkdrift - Yoel/YuriaDropped by Yuria upon death or quest completion.
FS: Darkmoon Longbow - Ludleth for AldrichBoss weapon for Aldrich
FS: Dead Again - Karla for Londor TomeSold by Irina or Karla after giving one the Londor Braille Divine Tome
FS: Deep Protection - Karla for Deep Braille TomeSold by Irina or Karla after giving one the Deep Braille Divine Tome
FS: Deep Soul - Ludleth for DeaconsBoss weapon for Deacons of the Deep
FS: Demon's Fist - Ludleth for Fire DemonBoss weapon for Fire Demon
FS: Demon's Greataxe - Ludleth for Fire DemonBoss weapon for Fire Demon
FS: Demon's Scar - Ludleth for Demon PrinceBoss weapon for Demon Prince
FS: Divine Blessing - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Divine Blessing - Greirat from USSold by Greirat after pillaging Undead Settlement
FS: Dragonscale Armor - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Dragonscale Waistcloth - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Dragonslayer Greataxe - Ludleth for DragonslayerBoss weapon for Dragonslayer Armour
FS: Dragonslayer Greatshield - Ludleth for DragonslayerBoss weapon for Dragonslayer Armour
FS: Dragonslayer Swordspear - Ludleth for NamelessBoss weapon for Nameless King
FS: Dried Finger - shopSold by both Shrine Handmaid and Untended Graves Handmaid
FS: East-West Shield - tree by shrine entranceIn a tree to the left of the Firelink Shrine entrance
FS: Eastern Armor - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Eastern Gauntlets - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Eastern Helm - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Eastern Leggings - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Elite Knight Armor - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Elite Knight Gauntlets - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Elite Knight Helm - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Elite Knight Leggings - shop after Anri questSold by Handmaid after completing Anri's questline or killing Anri
FS: Ember - Dragon Chaser's AshesSold by Handmaid after giving Dragon Chaser's Ashes
FS: Ember - Grave Warden's AshesSold by Handmaid after giving Grave Warden's Ashes
FS: Ember - GreiratSold by Greirat after recruiting him, or in his ashes
FS: Ember - Greirat from USSold by Greirat after pillaging Undead Settlement
FS: Ember - Mortician's AshesSold by Handmaid after giving Mortician's Ashes
FS: Ember - above shrine entranceAbove the Firelink Shrine entrance, up the stairs/slope from either left or right of the entrance
FS: Ember - path right of Firelink entranceOn a cliffside to the right of the main path leading up to Firelink Shrine, guarded by a dog
FS: Ember - shopSold by Handmaid
FS: Ember - shop for Greirat's AshesSold by Handmaid after Greirat pillages Lothric Castle and handing in ashes
FS: Embraced Armor of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Executioner Armor - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Executioner Gauntlets - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Executioner Helm - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Executioner Leggings - shop after killing HoraceSold by Handmaid after killing Horace the Hushed
FS: Exile Armor - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Exile Gauntlets - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Exile Leggings - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Exile Mask - shop after killing NPCs in RSSold by Handmaid after killing the exiles just before Farron Keep
FS: Faraam Helm - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
FS: Farron Dart - OrbeckSold by Orbeck
FS: Farron Dart - shopSold by Handmaid
FS: Farron Flashsword - OrbeckSold by Orbeck
FS: Farron Greatsword - Ludleth for Abyss WatchersBoss weapon for Abyss Watchers
FS: Farron Hail - Orbeck for Sage's ScrollSold by Orbeck after giving him the Sage's Scroll
FS: Farron Ring - HawkwoodGiven by Hawkwood, or dropped upon death, after defeating Abyss Watchers.
FS: Fire Orb - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Fire Surge - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Fire Whip - Karla for Quelana TomeSold by Karla after giving her the Quelana Pyromancy Tome
FS: Fireball - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Firelink Armor - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firelink Gauntlets - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firelink Greatsword - Ludleth for CinderBoss weapon for Soul of Cinder
FS: Firelink Helm - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firelink Leggings - shop after placing all CindersSold by Handmaid after defeating Soul of Cinder
FS: Firestorm - Karla for Quelana TomeSold by Karla after giving her the Quelana Pyromancy Tome
FS: Flash Sweat - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Force - Irina for Tome of CarimSold by Irina after giving her the Braille Divine Tome of Carim
FS: Frayed Blade - Ludleth for MidirBoss weapon for Darkeater Midir
FS: Friede's Great Scythe - Ludleth for FriedeBoss weapon for Sister Friede
FS: Gael's Greatsword - Ludleth for GaelBoss weapon for Slave Knight Gael
FS: Gauntlets of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Gnaw - Karla for Deep Braille TomeSold by Irina or Karla after giving one the Deep Braille Divine Tome
FS: Golden Bracelets - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Golden Crown - shop after killing AP bossSold by Handmaid after defeating Nameless King
FS: Grave Key - Mortician's AshesSold by Handmaid after giving Mortician's Ashes
FS: Great Chaos Fire Orb - Cornyx for Izalith TomeSold by Cornyx after giving him Izalith Pyromancy Tome
FS: Great Combustion - CornyxSold by Cornyx after recruiting him, or in his ashes
FS: Great Farron Dart - Orbeck for Sage's ScrollSold by Orbeck after giving him the Sage's Scroll
FS: Great Heavy Soul Arrow - OrbeckSold by Orbeck
FS: Great Soul Arrow - OrbeckSold by Orbeck
FS: Greatsword of Judgment - Ludleth for PontiffBoss weapon for Pontiff Sulyvahn
FS: Gundyr's Armor - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Gundyr's Gauntlets - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Gundyr's Halberd - Ludleth for ChampionBoss weapon for Champion Gundyr
FS: Gundyr's Helm - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Gundyr's Leggings - shop after killing UG bossSold by Handmaid after defeating Champion Gundyr
FS: Havel's Ring - Ludleth for Stray DemonBoss weapon for Stray Demon
FS: Hawkwood's Shield - gravestone after Hawkwood leavesLeft by Hawkwood after defeating Abyss Watchers, Curse-Rotted Greatwood, Deacons of the Deep, and Crystal Sage
FS: Hawkwood's Swordgrass - Andre after gesture in AP summitGiven by Andre after praying at the Dragon Altar in Archdragon Peak, after acquiring Twinkling Dragon Torso Stone.
FS: Heal - IrinaSold by Irina after recruiting her, or in her ashes
FS: Heal Aid - shopSold by Handmaid
FS: Heavy Soul Arrow - OrbeckSold by Orbeck
FS: Heavy Soul Arrow - Yoel/YuriaSold by Yoel/Yuria
FS: Helm of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Hidden Blessing - Dreamchaser's AshesSold by Greirat after pillaging Irithyll
FS: Hidden Blessing - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Hidden Blessing - Patches after searching GASold by Handmaid after giving Dreamchaser's Ashes, saying where they were found
FS: Hidden Body - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Hidden Weapon - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Hollowslayer Greatsword - Ludleth for GreatwoodBoss weapon for Curse-Rotted Greatwood
FS: Homeward - IrinaSold by Irina after recruiting her, or in her ashes
FS: Homeward Bone - cliff edge after bossAlong the cliff edge straight ahead of the Iudex Gundyr fight
FS: Homeward Bone - path above shrine entranceTo the right of the Firelink Shrine entrance, up a slope and before the ledge on top of a coffin
FS: Homing Crystal Soulmass - Orbeck for Crystal ScrollSold by Orbeck after giving him the Crystal Scroll
FS: Homing Soulmass - Orbeck for Logan's ScrollSold by Orbeck after giving him Logan's Scroll
FS: Karla's Coat - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Coat - kill KarlaDropped from Karla upon death
FS: Karla's Gloves - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Gloves - kill KarlaDropped from Karla upon death
FS: Karla's Pointed Hat - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Pointed Hat - kill KarlaDropped from Karla upon death
FS: Karla's Trousers - Prisoner Chief's AshesSold by Handmaid after giving Prisoner Chief's Ashes
FS: Karla's Trousers - kill KarlaDropped from Karla upon death
FS: Leggings of Favor - shop after killing water reserve minibossesSold by Handmaid after killing Sulyvahn's Beasts in Water Reserve
FS: Leonhard's Garb - shop after killing LeonhardSold by Handmaid after killing Leonhard
FS: Leonhard's Gauntlets - shop after killing LeonhardSold by Handmaid after killing Leonhard
FS: Leonhard's Trousers - shop after killing LeonhardSold by Handmaid after killing Leonhard
FS: Life Ring - Dreamchaser's AshesSold by Handmaid after giving Dreamchaser's Ashes
FS: Lifehunt Scythe - Ludleth for AldrichBoss weapon for Aldrich
FS: Lift Chamber Key - LeonhardGiven by Ringfinger Leonhard after acquiring a Pale Tongue.
FS: Lightning Storm - Ludleth for NamelessBoss weapon for Nameless King
FS: Lloyd's Shield Ring - Paladin's AshesSold by Handmaid after giving Paladin's Ashes
FS: Londor Braille Divine Tome - Yoel/YuriaSold by Yuria
FS: Lorian's Armor - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lorian's Gauntlets - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lorian's Greatsword - Ludleth for PrincesBoss weapon for Twin Princes
FS: Lorian's Helm - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lorian's Leggings - shop after killing GA bossSold by Handmaid after defeating Lothric, Younger Prince
FS: Lothric's Holy Sword - Ludleth for PrincesBoss weapon for Twin Princes
FS: Magic Barrier - Irina for Tome of LothricSold by Irina after giving her the Braille Divine Tome of Lothric
FS: Magic Shield - OrbeckSold by Orbeck
FS: Magic Shield - Yoel/YuriaSold by Yoel/Yuria
FS: Magic Weapon - OrbeckSold by Orbeck
FS: Magic Weapon - Yoel/YuriaSold by Yoel/Yuria
FS: Mail Breaker - Sirris for killing CreightonGiven by Sirris talking to her in Firelink Shrine after invading and vanquishing Creighton.
FS: Master's Attire - NPC dropDropped by Sword Master
FS: Master's Gloves - NPC dropDropped by Sword Master
FS: Med Heal - Irina for Tome of CarimSold by Irina after giving her the Braille Divine Tome of Carim
FS: Millwood Knight Armor - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Millwood Knight Gauntlets - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Millwood Knight Helm - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Millwood Knight Leggings - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Moaning Shield - EygonDropped by Eygon of Carim
FS: Moonlight Greatsword - Ludleth for OceirosBoss weapon for Oceiros, the Consumed King
FS: Morion Blade - Yuria for Orbeck's AshesGiven by Yuria after giving Orbeck's Ashes after she asks you to assassinate him, after he moves to Firelink Shrine. Can be done without killing Orbeck, by completing his questline.
FS: Morne's Armor - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Morne's Gauntlets - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Morne's Great Hammer - EygonDropped by Eygon of Carim
FS: Morne's Helm - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Morne's Leggings - shop after killing Eygon or LC bossSold by Handmaid after killing Eygon of Carim or defeating Dragonslayer Armour
FS: Old King's Great Hammer - Ludleth for Old Demon KingBoss weapon for Old Demon King
FS: Old Moonlight - Ludleth for MidirBoss weapon for Darkeater Midir
FS: Ordained Dress - shop after killing PW2 bossSold by Handmaid after defeating Sister Friede
FS: Ordained Hood - shop after killing PW2 bossSold by Handmaid after defeating Sister Friede
FS: Ordained Trousers - shop after killing PW2 bossSold by Handmaid after defeating Sister Friede
FS: Pale Shade Gloves - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Pale Shade Robe - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Pale Shade Trousers - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Pestilent Mist - Orbeck for any scrollSold by Orbeck after giving him any scroll
FS: Poison Mist - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Pontiff's Left Eye - Ludleth for VordtBoss weapon for Vordt of the Boreal Valley
FS: Prisoner's Chain - Ludleth for ChampionBoss weapon for Champion Gundyr
FS: Profaned Greatsword - Ludleth for PontiffBoss weapon for Pontiff Sulyvahn
FS: Profuse Sweat - Cornyx for Great Swamp TomeSold by Cornyx after giving him the Great Swamp Pyromancy Tome
FS: Rapport - Karla for Quelana TomeSold by Karla after giving her the Quelana Pyromancy Tome
FS: Refined Gem - Captain's AshesSold by Handmaid after giving Captain's Ashes
FS: Repair - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Repeating Crossbow - Ludleth for GaelBoss weapon for Slave Knight Gael
FS: Replenishment - IrinaSold by Irina after recruiting her, or in her ashes
FS: Ring of Sacrifice - Yuria shopSold by Yuria, or by Handmaid after giving Hollow's Ashes
FS: Rose of Ariandel - Ludleth for FriedeBoss weapon for Sister Friede
FS: Rusted Gold Coin - don't forgive PatchesGiven by Patches after not forgiving him after he locks you in the Bell Tower.
FS: Sage's Big Hat - shop after killing RS bossSold by Handmaid after defeating Crystal Sage
FS: Saint's Ring - IrinaSold by Irina after recruiting her, or in her ashes
FS: Seething Chaos - Ludleth for Demon PrinceBoss weapon for Demon Prince
FS: Silvercat Ring - Sirris for killing CreightonGiven by Sirris talking to her in Firelink Shrine after invading and vanquishing Creighton.
FS: Skull Ring - kill LudlethDropped by Ludleth upon death, including after placing all cinders. Note that if killed before giving Transposing Kiln, transposition is not possible.
FS: Slumbering Dragoncrest Ring - Orbeck for buying four specific spellsGiven by Orbeck after purchasing the shop items corresponding to Aural Decoy, Farron Flashsword, Spook (starting items), and Pestilent Mist (after giving one scroll).
FS: Smough's Armor - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Smough's Gauntlets - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Smough's Helm - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Smough's Leggings - shop after killing AL bossSold by Handmaid after defeating Alrich, Devourer of Gods
FS: Sneering Mask - Yoel's room, kill Londor Pale Shade twiceIn Yoel/Yuria's area after defeating both Londor Pale Shade invasions
FS: Soothing Sunlight - Ludleth for DancerBoss weapon for Dancer of the Boreal Valley
FS: Soul Arrow - OrbeckSold by Orbeck
FS: Soul Arrow - Yoel/YuriaSold by Yoel/Yuria
FS: Soul Arrow - shopSold by Handmaid
FS: Soul Greatsword - OrbeckSold by Orbeck
FS: Soul Greatsword - Yoel/YuriaSold by Yoel/Yuria after using Draw Out True Strength
FS: Soul Spear - Orbeck for Logan's ScrollSold by Orbeck after giving him Logan's Scroll
FS: Soul of a Deserted Corpse - bell tower doorNext to the door requiring the Tower Key
FS: Spook - OrbeckSold by Orbeck
FS: Storm Curved Sword - Ludleth for NamelessBoss weapon for Nameless King
FS: Sunless Armor - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunless Gauntlets - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunless Leggings - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunless Talisman - Sirris, kill GA bossDropped by Sirris on death or quest completion.
FS: Sunless Veil - shop, Sirris quest, kill GA bossSold by Handmaid after completing Sirris' questline
FS: Sunlight Spear - Ludleth for CinderBoss weapon for Soul of Cinder
FS: Sunset Shield - by grave after killing Hodrick w/SirrisLeft by Sirris upon quest completion.
FS: Tears of Denial - Irina for Tome of CarimSold by Irina after giving her the Braille Divine Tome of Carim
FS: Titanite Scale - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Titanite Slab - shop after placing all CindersSold by Handmaid after placing all Cinders of a Lord on their thrones
FS: Tower Key - shopSold by both Shrine Handmaid and Untended Graves Handmaid
FS: Twinkling Titanite - Greirat from IBVSold by Greirat after pillaging Irithyll
FS: Twisted Wall of Light - Orbeck for Golden ScrollSold by Orbeck after giving him the Golden Scroll
FS: Uchigatana - NPC dropDropped by Sword Master
FS: Undead Legion Armor - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Undead Legion Gauntlet - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Undead Legion Helm - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Undead Legion Leggings - shop after killing FK bossSold by Handmaid after defeating Abyss Watchers
FS: Untrue Dark Ring - Yoel/YuriaSold by Yuria
FS: Untrue White Ring - Yoel/YuriaSold by Yuria
FS: Vordt's Great Hammer - Ludleth for VordtBoss weapon for Vordt of the Boreal Valley
FS: Vow of Silence - Karla for Londor TomeSold by Irina or Karla after giving one the Londor Braille Divine Tome
FS: Washing Pole - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: White Dragon Breath - Ludleth for OceirosBoss weapon for Oceiros, the Consumed King
FS: White Sign Soapstone - shopSold by both Shrine Handmaid and Untended Graves Handmaid
FS: Wolf Knight's Greatsword - Ludleth for Abyss WatchersBoss weapon for Abyss Watchers
FS: Wolf Ring+2 - left of boss room exitAfter Iudex Gundyr on the left
FS: Wolnir's Crown - shop after killing CC bossSold by Handmaid after defeating High Lord Wolnir
FS: Wolnir's Holy Sword - Ludleth for WolnirBoss weapon for High Lord Wolnir
FS: Wood Grain Ring - Easterner's AshesSold by Handmaid after giving Easterner's Ashes
FS: Xanthous Gloves - Xanthous AshesSold by Handmaid after giving Xanthous Ashes
FS: Xanthous Overcoat - Xanthous AshesSold by Handmaid after giving Xanthous Ashes
FS: Xanthous Trousers - Xanthous AshesSold by Handmaid after giving Xanthous Ashes
FS: Yhorm's Great Machete - Ludleth for YhormBoss weapon for Yhorm the Giant
FS: Yhorm's Greatshield - Ludleth for YhormBoss weapon for Yhorm the Giant
FS: Young Dragon Ring - Orbeck for one scroll and buying three spellsGiven by Orbeck after purchasing four sorceries from him, and giving him one scroll, as a non-sorcerer.
FSBT: Armor of the Sun - crow for SiegbräuTrade Siegbräu with crow
FSBT: Blessed Gem - crow for Moaning ShieldTrade Moaning Shield with crow
FSBT: Covetous Silver Serpent Ring - illusory wall past raftersFrom the Firelink Shrine roof, past the rafters and an illusory wall
FSBT: Estus Ring - tower baseDropping down from the Bell Tower to where Irina eventually resides
FSBT: Estus Shard - raftersIn the Firelink Shrine rafters, accessible from the roof
FSBT: Fire Keeper Gloves - partway down towerDropping down to the left after entering the Bell Tower. Align with the center of the closest floor tile row and run off the edge at full speed, aiming slightly left.
FSBT: Fire Keeper Robe - partway down towerDropping down to the left after entering the Bell Tower. Align with the center of the closest floor tile row and run off the edge at full speed, aiming slightly left.
FSBT: Fire Keeper Skirt - partway down towerDropping down to the left after entering the Bell Tower. Align with the center of the closest floor tile row and run off the edge at full speed, aiming slightly left.
FSBT: Fire Keeper Soul - tower topAt the top of the Bell Tower
FSBT: Hello Carving - crow for Alluring SkullTrade Alluring Skull with crow
FSBT: Help me! Carving - crow for any sacred chimeTrade any Sacred Chime with crow
FSBT: Hollow Gem - crow for EleonoraTrade Eleonora with crow
FSBT: Homeward Bone - roofOn Firelink Shrine roof
FSBT: I'm sorry Carving - crow for Shriving StoneTrade Shriving Stone with crow
FSBT: Iron Bracelets - crow for Homeward BoneTrade Homeward Bone with crow
FSBT: Iron Helm - crow for Lightning UrnTrade Lightning Urn with crow
FSBT: Iron Leggings - crow for Seed of a Giant TreeTrade Seed of a Giant Tree with crow
FSBT: Large Titanite Shard - crow for FirebombTrade Firebomb or Rope Firebomb with crow
FSBT: Lightning Gem - crow for Xanthous CrownTrade Xanthous Crown with crow
FSBT: Lucatiel's Mask - crow for Vertebra ShackleTrade Vertebra Shackle with crow
FSBT: Porcine Shield - crow for Undead Bone ShardTrade Undead Bone Shard with crow
FSBT: Ring of Sacrifice - crow for Loretta's BoneTrade Loretta's Bone with crow
FSBT: Sunlight Shield - crow for Mendicant's StaffTrade Mendicant's Staff with crow
FSBT: Thank you Carving - crow for Hidden BlessingTrade Hidden Blessing with crow
FSBT: Titanite Chunk - crow for Black FirebombTrade Black Firebomb or Rope Black Firebomb with crow
FSBT: Titanite Scale - crow for Blacksmith HammerTrade Blacksmith Hammer with crow
FSBT: Titanite Slab - crow for Coiled Sword FragmentTrade Coiled Sword Fragment with crow
FSBT: Twinkling Titanite - crow for Large Leather ShieldTrade Large Leather Shield with crow
FSBT: Twinkling Titanite - crow for Prism StoneTrade Prism Stone with crow
FSBT: Twinkling Titanite - lizard behind FirelinkDropped by the Crystal Lizard behind Firelink Shrine. Can be accessed with tree jump by going all the way around the roof, left of the entrance to the rafters, or alternatively dropping down from the Bell Tower.
FSBT: Very good! Carving - crow for Divine BlessingTrade Divine Blessing with crow
GA: Avelyn - 1F, drop from 3F onto bookshelvesOn top of a bookshelf on the Archive first floor, accessible by going halfway up the stairs to the third floor, dropping down past the Grand Archives Scholar, and then dropping down again
GA: Black Hand Armor - shop after killing GA NPCSold by Handmaid after killing Black Hand Kumai
GA: Black Hand Hat - shop after killing GA NPCSold by Handmaid after killing Black Hand Kumai
GA: Blessed Gem - raftersOn the rafters high above the Archives, can be accessed by dropping down from the Winged Knight roof area
GA: Chaos Gem - dark room, lizardDropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool
GA: Cinders of a Lord - Lothric PrinceDropped by Twin Princes
GA: Crystal Chime - 1F, path from wax poolOn the Archives first floor, in the room with the Lothric Knight, to the right
GA: Crystal Gem - 1F, lizard by dropDropped by the Crystal Lizard on the Archives first floor along the left wall
GA: Crystal Scroll - 2F late, miniboss dropDropped by the Grand Archives Crystal Sage
GA: Divine Blessing - rafters, down lower level ladderIn a chest reachable after dropping down from the Archives rafters and down a ladder near the Corpse-grub
GA: Divine Pillars of Light - cage above raftersIn a cage above the rafters high above the Archives, can be accessed by dropping down from the Winged Knight roof area
GA: Ember - 5F, by entranceOn a balcony high in the Archives overlooking the area with the Grand Archives Scholars with a shortcut ladder, on the opposite side from the wax pool
GA: Estus Shard - dome, far balconyOn the Archives roof near the three Winged Knights, in a side area overlooking the ocean.
GA: Faraam Armor - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
GA: Faraam Boots - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
GA: Faraam Gauntlets - shop after killing GA NPCSold by Handmaid after killing Lion Knight Albert
GA: Fleshbite Ring - up stairs from 4FFrom the first shortcut elevator with the movable bookshelf, past the Scholars right before going outside onto the roof, in an alcove to the right with many Clawed Curse bookshelves
GA: Golden Wing Crest Shield - outside 5F, NPC dropDropped by Lion Knight Albert before the stairs leading up to Twin Princes
GA: Heavy Gem - rooftops, lizardDropped by one of the pair of Crystal Lizards, on the right side, found going up a slope past the gargoyle on the Archives roof
GA: Hollow Gem - rooftops lower, in hallGoing onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, in a tunnel underneath the ledge
GA: Homeward Bone - 2F early balconyOn the Archives second floor, on the balcony with the ladder going up to the Crystal Sage
GA: Hunter's Ring - dome, very topAt the top of the ladder in roof the area with the Winged Knights
GA: Large Soul of a Crestfallen Knight - 4F, backIn the back of a Clawed Curse-heavy corridor of bookshelves, in the area with the Grand Archives Scholars and dropdown ladder, after the first shortcut elevator with the movable bookshelf
GA: Large Soul of a Crestfallen Knight - outside 5FIn the middle of the area with the three human NPCs attacking you, before the Grand Archives bonfire shortcut elevator
GA: Lingering Dragoncrest Ring+2 - dome, room behind spireNear the tower with the Winged Knights, up the stairs on the opposite side from the ladder leading up to the Hunter's Ring
GA: Onikiri and Ubadachi - outside 5F, NPC dropDropped by Black Hand Kamui before the stairs leading up to Twin Princes
GA: Outrider Knight Armor - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Outrider Knight Gauntlets - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Outrider Knight Helm - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Outrider Knight Leggings - 3F, behind illusory wall, miniboss dropDropped by an Outrider Knight past the Crystal Sage's third floor location and an illusory wall
GA: Power Within - dark room, behind retractable bookshelfBehind a bookshelf in the dark room with the Crystal Lizards, moved by a lever in the same room
GA: Refined Gem - up stairs from 4F, lizardDropped by a Crystal Lizard found heading from the first elevator shortcut with the movable bookshelf, on the right side up the stairs before exiting to the roof
GA: Sage Ring+1 - rafters, second level downOn the rafters high above the Grand Archives, dropping down from the cage to the high rafters to the rafters below with the Corpse-grub
GA: Sage's Crystal Staff - outside 5F, NPC dropDropped by Daughter of Crystal Kriemhild before the stairs leading up to Twin Princes
GA: Scholar Ring - 2F, between late and earlyOn the corpse of a sitting Archives Scholar between two bookshelves, accessible by activating a lever before crossing the bridge that is the Crystal Sage's final location
GA: Sharp Gem - rooftops, lizardDropped by one of the pair of Crystal Lizards, on the left side, found going up a slope past the gargoyle on the Archives roof
GA: Shriving Stone - 2F late, by ladder from 3FGoing from the Crystal Sage's location on the third floor to its location on the bridge, after descending the ladder
GA: Soul Stream - 3F, behind illusory wallPast the Crystal Sage's third floor location, an illusory wall, and an Outrider Knight, on the corpse of a sitting Archives Scholar
GA: Soul of a Crestfallen Knight - 1F, loop left after dropOn the Archives first floor, hugging the left wall, on a ledge that loops back around to the left wall
GA: Soul of a Crestfallen Knight - path to domeOn balcony of the building with the second shortcut elevator down to the bonfire, accessible by going up the spiral stairs to the left
GA: Soul of a Nameless Soldier - dark roomOn the Archives first floor, after the wax pool, against a Clawed Curse bookshelf
GA: Soul of a Weary Warrior - rooftops, by lizardsOn the Archives roof, going up the first rooftop slope where a Gargoyle always attacks you
GA: Soul of the Twin PrincesDropped by Twin Princes
GA: Titanite Chunk - 1F, balconyOn the Archives first floor, on balcony overlooking the entrance opposite from the Grand Archives Scholars wax pool
GA: Titanite Chunk - 1F, path from wax poolOn the Archives first floor, toward the Lothric Knight, turning right to a ledge leading back to the entrance area
GA: Titanite Chunk - 1F, up right stairsGoing right after entering the Archives entrance and up the short flight of stairs
GA: Titanite Chunk - 2F, by wax poolUp the stairs from the Archives second floor on the right side from the entrance, in a corner near the small wax pool
GA: Titanite Chunk - 2F, right after dark roomExiting from the dark room with the Crystal Lizards on the first floor onto the second floor main room, then taking an immediate right
GA: Titanite Chunk - 5F, far balconyOn a balcony outside where Lothric Knight stands on the top floor of the Archives, accessing by going right from the final wax pool or by dropping down from the gargoyle area
GA: Titanite Chunk - rooftops, balconyGoing onto the roof and down the first ladder, all the way down the ledge facing the ocean to the right
GA: Titanite Chunk - rooftops lower, ledge by buttressGoing onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, on a roof ledge to the right
GA: Titanite Chunk - rooftops, just before 5FOn the Archives roof, after a short dropdown, in the small area where the two Gargoyles attack you
GA: Titanite Scale - 1F, drop from 2F late onto bookshelves, lizardDropped by a Crystal Lizard on first floor bookshelves. Can be accessed by dropping down to the left at the end of the bridge which is the Crystal Sage's final location
GA: Titanite Scale - 1F, up stairs on bookshelfOn the Archives first floor, up a movable set of stairs near the large wax pool, on top of a bookshelf
GA: Titanite Scale - 2F, titanite scale atop bookshelfOn top of a bookshelf on the Archive second floor, accessible by going halfway up the stairs to the third floor and dropping down near a Grand Archives Scholar
GA: Titanite Scale - 3F, by ladder to 2F lateGoing from the Crystal Sage's location on the third floor to its location on the bridge, on the left side of the ladder you descend, behind a table
GA: Titanite Scale - 3F, corner up stairsFrom the Grand Archives third floor up past the thralls, in a corner with bookshelves to the left
GA: Titanite Scale - 5F, chest by exitIn a chest after the first elevator shortcut with the movable bookshelf, in the area with the Grand Archives Scholars, to the left of the stairwell leading up to the roof
GA: Titanite Scale - dark room, upstairsRight after going up the stairs to the Archives second floor, on the left guarded by a Grand Archives Scholar and a sequence of Clawed Curse bookshelves
GA: Titanite Scale - rooftops lower, path to 2FGoing onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, then going past the corvians all the way to the left and making a jump
GA: Titanite Slab - 1F, after pulling 2F switchIn a chest on the Archives first floor, behind a bookshelf moved by pulling a lever in the middle of the second floor between two cursed bookshelves
GA: Titanite Slab - dome, kill all mobsDropped by killing all three Winged Knights on top of the Archives
GA: Titanite Slab - final elevator secretAt the bottom of the shortcut elevator right outside the Twin Princes fight. Requires sending the elevator up to the top from the middle, and then riding the lower elevator down.
GA: Twinkling Titanite - 1F, lizard by dropDropped by the Crystal Lizard on the Archives first floor along the left wall
GA: Twinkling Titanite - 2F, lizard by entranceDropped by the Crystal Lizard on the Archives second floor, going toward the stairs/balcony
GA: Twinkling Titanite - dark room, lizard #1Dropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool
GA: Twinkling Titanite - dark room, lizard #2Dropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool
GA: Twinkling Titanite - rafters, down lower level ladderIn a chest reachable after dropping down from the Archives rafters and down a ladder near the Corpse-grub
GA: Twinkling Titanite - rooftops, lizard #1Dropped by one of the pair of Crystal Lizards, on the right side, found going up a slope past the gargoyle on the Archives roof
GA: Twinkling Titanite - rooftops, lizard #2Dropped by one of the pair of Crystal Lizards, on the left side, found going up a slope past the gargoyle on the Archives roof
GA: Twinkling Titanite - up stairs from 4F, lizardDropped by a Crystal Lizard found heading from the first elevator shortcut with the movable bookshelf, on the right side up the stairs before exiting to the roof
GA: Undead Bone Shard - 5F, by entranceOn the corpse of a sitting Archives Scholar on a balcony high in the Archives overlooking the area with the Grand Archives Scholars with a shortcut ladder, near the final wax pool
GA: Witch's Locks - dark room, behind retractable bookshelfBehind a bookshelf in the dark room with the Crystal Lizards, moved by a lever in the same room
HWL: Astora Straight Sword - fort walkway, drop downIn the building with the Pus of Man on the roof, past the Lothric Knight down a hallway obscured by a wooden wheel, dropping down past the edge
HWL: Basin of Vows - EmmaDropped by Emma upon killing her. This is possible to do at any time
HWL: Battle Axe - flame tower, mimicDropped by mimic in the building guarded by the fire-breathing wyvern
HWL: Binoculars - corpse tower, upper platformIn the area with the dead wyvern, at the top of a set of stairs past a Hollow Soldier
HWL: Black Firebomb - small roof over fountainAfter roof with Pus of Man, on the edge of another rooftop to the left where you can drop down into Winged Knight area
HWL: Broadsword - fort, room off walkwayIn the building with the Pus of Man on the roof, past the Lothric Knight in an alcove to the left
HWL: Cell Key - fort ground, down stairsIn the basement of the building with Pus of Man on the roof, down the stairs guarded by a dog
HWL: Claymore - flame plazaIn the area where the wyvern breathes fire, farthest away from the door
HWL: Club - flame plazaIn the area where the wyvern breathes fire, in the open
HWL: Ember - back tower, transforming hollowDropped by the Pus of Man on the tower to the right of the High Wall bonfire after transformation
HWL: Ember - flame plazaIn the area where the wyvern breathes fire, in the open
HWL: Ember - fort roof, transforming hollowDropped by the Pus of Man on the roof after the Tower on the Wall bonfire after transformation
HWL: Ember - fountain #1In the area with the Winged Knight
HWL: Ember - fountain #2In the area with the Winged Knight
HWL: Estus Shard - fort ground, on anvilIn the basement of the building with the Pus of Man on the roof, on the blacksmith anvil
HWL: Firebomb - corpse tower, under tableIn the building near the dead wyvern, behind a table near the ladder you descend
HWL: Firebomb - fort roofNext to the Pus of Man on the roof
HWL: Firebomb - top of ladder to fountainBy the long ladder leading down to the area with the Winged Knight
HWL: Firebomb - wall tower, beamIn the building with the Tower on the Wall bonfire, on a wooden beam overhanging the lower levels
HWL: Fleshbite Ring+1 - fort roof, jump to other roofJumping from the roof with the Pus of Man to a nearby building with a fenced roof
HWL: Gold Pine Resin - corpse tower, dropDropping past the dead wyvern, down the left path from the High Wall bonfire
HWL: Green Blossom - fort walkway, hall behind wheelIn the building with the Pus of Man on the roof, past the Lothric Knight down a hallway obscured by a wooden wheel
HWL: Green Blossom - shortcut, lower courtyardIn the courtyard at the bottom of the shortcut elevator
HWL: Large Soul of a Deserted Corpse - flame plazaIn the area where the wyvern breathes fire, behind one of the praying statues
HWL: Large Soul of a Deserted Corpse - fort roofOn the edge of the roof with the Pus of Man
HWL: Large Soul of a Deserted Corpse - platform by fountainComing from the elevator shortcut, on a side path to the left (toward Winged Knight area)
HWL: Longbow - back towerDown the path from the right of the High Wall bonfire, where the Pus of Man and crossbowman are
HWL: Lucerne - promenade, side pathOn one of the side paths from the main path connecting Dancer and Vordt fights, patrolled by a Lothric Knight
HWL: Mail Breaker - wall tower, path to GreiratIn the basement of the building with the Tower on the Wall bonfire on the roof, before Greirat's cell
HWL: Rapier - fountain, cornerIn a corner in the area with the Winged Knight
HWL: Raw Gem - fort roof, lizardDropped by the Crystal Lizard on the rooftop after the Tower on the Wall bonfire
HWL: Red Eye Orb - wall tower, minibossDropped by the Darkwraith past the Lift Chamber Key
HWL: Refined Gem - promenade minibossDropped by the red-eyed Lothric Knight to the left of the Dancer's room entrance
HWL: Ring of Sacrifice - awning by fountainComing from the elevator shortcut, on a side path to the left (toward Winged Knight area), jumping onto a wooden support
HWL: Ring of the Evil Eye+2 - fort ground, far wallIn the basement of the building with the Pus of Man on the roof, on the far wall past the stairwell, behind some barrels
HWL: Silver Eagle Kite Shield - fort mezzanineIn the chest on the balcony overlooking the basement of the building with the Pus of Man on the roof
HWL: Small Lothric Banner - EmmaGiven by Emma, or dropped upon death
HWL: Soul of Boreal Valley VordtDropped by Vordt of the Boreal Valley
HWL: Soul of a Deserted Corpse - by wall tower doorRight before the entrance to the building with the Tower on the Wall bonfire
HWL: Soul of a Deserted Corpse - corpse tower, bottom floorDown the ladder of the building near the dead wyvern, on the way to the living wyvern
HWL: Soul of a Deserted Corpse - fort entry, cornerIn the corner of the room with a Lothric Knight, with the Pus of Man on the roof
HWL: Soul of a Deserted Corpse - fountain, path to promenadeIn between the Winged Knight area and the Dancer/Vordt corridor
HWL: Soul of a Deserted Corpse - path to back tower, by lift doorWhere the Greataxe Hollow Soldier patrols outside of the elevator shortcut entrance
HWL: Soul of a Deserted Corpse - path to corpse towerAt the very start, heading left from the High Wall bonfire
HWL: Soul of a Deserted Corpse - wall tower, right of exitExiting the building with the Tower on the Wall bonfire on the roof, immediately to the right
HWL: Soul of the DancerDropped by Dancer of the Boreal Valley
HWL: Standard Arrow - back towerDown the path from the right of the High Wall bonfire, where the Pus of Man and crossbowman are
HWL: Throwing Knife - shortcut, lift topAt the top of the elevator shortcut, opposite from the one-way door
HWL: Throwing Knife - wall tower, path to GreiratIn the basement of the building with the Tower on the Wall bonfire, in the room with the explosive barrels
HWL: Titanite Shard - back tower, transforming hollowDropped by the Pus of Man on the tower to the right of the High Wall bonfire after transformation
HWL: Titanite Shard - fort ground behind cratesBehind some wooden crates in the basement of the building with the Pus of Man on the roof
HWL: Titanite Shard - fort roof, transforming hollowDropped by the Pus of Man on the roof after the Tower on the Wall bonfire after transformation
HWL: Titanite Shard - fort, room off entryIn the building with the Pus of Man on the roof, in a room to the left and up the short stairs
HWL: Titanite Shard - wall tower, corner by bonfireOn the balcony with the Tower on the Wall bonfire
HWL: Undead Hunter Charm - fort, room off entry, in potIn the building with the Pus of Man on the roof, in a room to the left, in a pot you have to break
HWL: Way of Blue - EmmaGiven by Emma or dropped upon death.
IBV: Blood Gem - descent, platform before lakeIn front of the tree in the courtyard before going down the stairs to the lake leading to the Distant Manor bonfire
IBV: Blue Bug Pellet - ascent, in last buildingIn the final building before Pontiff's cathedral, coming from the sewer, on the first floor
IBV: Blue Bug Pellet - descent, dark roomIn the dark area with the Irithyllian slaves, to the left of the staircase
IBV: Budding Green Blossom - central, by second fountainNext to the fountain up the stairs from the Central Irithyll bonfire
IBV: Chloranthy Ring+1 - plaza, behind altarIn the area before and below Pontiff's cathedral, behind the central structure
IBV: Covetous Gold Serpent Ring+1 - descent, drop after dark roomAfter the dark area with the Irithyllian slaves, drop down to the right
IBV: Creighton's Steel Mask - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Divine Blessing - great hall, chestIn a chest up the stairs in the room with the Silver Knight staring at the painting
IBV: Divine Blessing - great hall, mob dropOne-time drop from the Silver Knight staring at the painting in Irithyll
IBV: Dorhys' Gnawing - Dorhys dropDropped by Cathedral Evangelist Dorhys, past an illusory railing past the Central Irithyll Fire Witches and to the left
IBV: Dragonslayer's Axe - Creighton dropFollowing Sirris' questline, dropped by Creighton the Wanderer when he invades in the graveyard after the Church of Yorshka.
IBV: Dung Pie - sewer #1In the area with the sewer centipedes
IBV: Dung Pie - sewer #2In the area with the sewer centipedes
IBV: Ember - shortcut from church to cathedralAfter the gate shortcut from Church of Yorshka to Pontiff's cathedral
IBV: Emit Force - SiegwardGiven by Siegward meeting him in the Irithyll kitchen after the Sewer Centipedes.
IBV: Excrement-covered Ashes - sewer, by stairsIn the area with the sewer centipedes, before going up the stairs to the kitchen
IBV: Fading Soul - descent, cliff edge #1In the graveyard down the stairs from the Church of Yorshka, at the cliff edge
IBV: Fading Soul - descent, cliff edge #2In the graveyard down the stairs from the Church of Yorshka, at the cliff edge
IBV: Great Heal - lake, dead Corpse-GrubOn the Corpse-grub at the edge of the lake leading to the Distant Manor bonfire
IBV: Green Blossom - lake wallOn the wall of the lake leading to the Distant Manor bonfire
IBV: Green Blossom - lake, by Distant ManorIn the lake close to the Distant Manor bonfire
IBV: Green Blossom - lake, by stairs from descentGoing down the stairs into the lake leading to the Distant Manor bonfire
IBV: Homeward Bone - descent, before gravestoneIn the graveyard down the stairs from the Church of Yorshka, in front of the grave with the Corvian
IBV: Kukri - descent, side pathDown the stairs from the graveyard after Church of Yorshka, before the group of dogs in the left path
IBV: Large Soul of a Nameless Soldier - ascent, after great hallBy the tree near the stairs from the sewer leading up to Pontiff's cathedral, where the first dogs attack you
IBV: Large Soul of a Nameless Soldier - central, by bonfireBy the Central Irithyll bonfire
IBV: Large Soul of a Nameless Soldier - central, by second fountainNext to the fountain up the stairs from the Central Irithyll bonfire
IBV: Large Soul of a Nameless Soldier - lake islandOn an island in the lake leading to the Distant Manor bonfire
IBV: Large Soul of a Nameless Soldier - stairs to plazaOn the path from Central Irithyll bonfire, before making the left toward Church of Yorshka
IBV: Large Titanite Shard - Distant Manor, under overhangUnder overhang next to second set of stairs leading from Distant Manor bonfire
IBV: Large Titanite Shard - ascent, by elevator doorOn the path from the sewer leading up to Pontiff's cathedral, to the right of the statue surrounded by dogs
IBV: Large Titanite Shard - ascent, down ladder in last buildingOutside the final building before Pontiff's cathedral, coming from the sewer, dropping down to the left before the entrance
IBV: Large Titanite Shard - central, balcony just before plazaFrom the Central Irithyll bonfire, on the balcony with the second Fire Witch.
IBV: Large Titanite Shard - central, side path after first fountainUp the stairs from the Central Irithyll bonfire, on a railing to the right
IBV: Large Titanite Shard - great hall, main floor mob dropOne-time drop from the Silver Knight staring at the painting in Irithyll
IBV: Large Titanite Shard - great hall, upstairs mob drop #1One-time drop from the Silver Knight on the balcony of the room with the painting
IBV: Large Titanite Shard - great hall, upstairs mob drop #2One-time drop from the Silver Knight on the balcony of the room with the painting
IBV: Large Titanite Shard - path to DorhysBefore the area with Cathedral Evangelist Dorhys, past an illusory railing past the Central Irithyll Fire Witches
IBV: Large Titanite Shard - plaza, balcony overlooking ascentOn the path from Central Irithyll bonfire, instead of going left toward the Church of Yorshka, going right, on the balcony
IBV: Large Titanite Shard - plaza, by stairs to churchTo the left of the stairs leading up to the Church of Yorshka from Central Irithyll
IBV: Leo Ring - great hall, chestIn a chest up the stairs in the room with the Silver Knight staring at the painting
IBV: Lightning Gem - plaza centerIn the area before and below Pontiff's cathedral, in the center guarded by the enemies
IBV: Magic Clutch Ring - plaza, illusory wallIn the area before and below Pontiff's cathedral, behind an illusory wall to the right
IBV: Mirrah Chain Gloves - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Mirrah Chain Leggings - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Mirrah Chain Mail - bridge after killing CreightonFollowing Sirris' questline, found on the bridge to Irithyll after being invaded by Creighton the Wanderer in the graveyard after the Church of Yorshka.
IBV: Proof of a Concord Kept - Church of Yorshka altarAt the altar in the Church of Yorshka
IBV: Rime-blue Moss Clump - central, by bonfireBy the Central Irithyll bonfire
IBV: Rime-blue Moss Clump - central, past second fountainFrom the Central Irithyll bonfire, to the left before the first Fire Witch.
IBV: Ring of Sacrifice - lake, right of stairs from descentNear the sewer centipede at the start of the lake leading to the Distant Manor bonfire
IBV: Ring of the Evil Eye - AnriGiven by Anri of Astora in the Church of Yorshka, or if told of Horace's whereabouts in the Catacombs
IBV: Ring of the Sun's First Born - fall from in front of cathedralDropping down from in front of Pontiff Sulyvahn's church toward the Church of Yorshka
IBV: Roster of Knights - descent, first landingOn the landing going down the stairs from Church of Yorshka to the graveyard
IBV: Rusted Gold Coin - Distant Manor, drop after stairsDropping down after the first set of stairs leading from Distant Manor bonfire
IBV: Rusted Gold Coin - descent, side pathDown the stairs from the graveyard after Church of Yorshka, guarded by the group of dogs in the left path
IBV: Shriving Stone - descent, dark room raftersOn the rafters in the dark area with the Irithyllian slaves
IBV: Siegbräu - SiegwardGiven by Siegward meeting him in the Irithyll kitchen after the Sewer Centipedes.
IBV: Smough's Great Hammer - great hall, chestIn a chest up the stairs in the room with the Silver Knight staring at the painting
IBV: Soul of Pontiff SulyvahnDropped by Pontiff Sulyvahn
IBV: Soul of a Weary Warrior - ascent, by final staircaseToward the end of the path from the sewer leading up to Pontiff's cathedral, to the left of the final staircase
IBV: Soul of a Weary Warrior - central, by first fountainBy the Central Irithyll bonfire
IBV: Soul of a Weary Warrior - central, railing by first fountainOn the railing overlooking the Central Irithyll bonfire, at the very start
IBV: Soul of a Weary Warrior - plaza, side room lowerDropping down from the path from Church of Yorshka to Pontiff, guarded by the pensive Fire Witch
IBV: Soul of a Weary Warrior - plaza, side room upperIn the path from Church of Yorshka to Pontiff's cathedral, at the broken ledge you can drop down onto the Fire Witch
IBV: Twinkling Titanite - central, lizard before plazaDropped by a Crystal Lizard past the Central Irithyll Fire Witches and to the left
IBV: Twinkling Titanite - descent, lizard behind illusory wallDropped by a Crystal Lizard behind an illusory wall before going down the stairs to the lake leading to the Distant Manor bonfire
IBV: Undead Bone Shard - descent, behind gravestoneIn the graveyard down the stairs from the Church of Yorshka, behind the grave with the Corvian
IBV: Witchtree Branch - by DorhysIn the area with Cathedral Evangelist Dorhys, past an illusory railing past the Central Irithyll Fire Witches
IBV: Wood Grain Ring+2 - ascent, right after great hallLeaving the building with the Silver Knight staring at the painting, instead of going left up the stairs, go right
IBV: Yorshka's Spear - descent, dark room rafters chestIn a chest in the rafters of the dark area with the Irithyllian slaves
ID: Alva Armor - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Alva Gauntlets - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Alva Helm - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Alva Leggings - B3 near, by Karla's cell, after killing AlvaIn the main Jailer cell block on the floor close to Karla's cell, if the invading Alva is killed
ID: Bellowing Dragoncrest Ring - drop from B1 towards pitDropping down from the Jailbreaker's Key shortcut at the end of the top corridor on the bonfire side in Irithyll Dungeon
ID: Covetous Gold Serpent Ring - Siegward's cellIn the Old Cell where Siegward is rescued
ID: Covetous Silver Serpent Ring+1 - pit lift, middle platformOn one of the platforms in elevator shaft of the shortcut elevator from the Giant Slave area to the Irithyll Dungeon bonfire
ID: Dark Clutch Ring - stairs between pit and B3, mimicDropped by the mimic found going past the Giant Slave to the sewer with the rats and the basilisks, up the first flight of stairs, on the left side
ID: Dragon Torso Stone - B3, outside liftOn the balcony corpse in the Path of the Dragon pose
ID: Dragonslayer Lightning Arrow - pit, mimic in hallDropped by the mimic in the side corridor from where the Giant Slave is standing, before the long ladder
ID: Dung Pie - B3, by path from pitIn the room with the Giant Hound Rats
ID: Dung Pie - pit, miniboss dropDrop from the Giant Slave
ID: Dusk Crown Ring - B3 far, right cellIn the cell in the main Jailer cell block to the left of the Profaned Capital exit
ID: Ember - B3 centerAt the center pillar in the main Jailer cell block
ID: Ember - B3 far rightIn the main Jailer cell block, on the left side coming from the Profaned Capital
ID: Estus Shard - mimic on path from B2 to pitDropped by the mimic in the room after the outside area of Irithyll Dungeon overlooking Profaned Capital
ID: Fading Soul - B1 near, main hallOn the top corridor on the bonfire side in Irithyll Dungeon, close to the first Jailer
ID: Great Magic Shield - B2 near, mob drop in far left cellOne-time drop from the Infested Corpse in the bottom corridor on the bonfire side of Irithyll Dungeon, in the closest cell
ID: Homeward Bone - path from B2 to pitIn the part of Irithyll Dungeon overlooking the Profaned Capital, after exiting the last jail cell corridor
ID: Jailbreaker's Key - B1 far, cell after gateIn the cell of the top corridor opposite to the bonfire in Irithyll Dungeon
ID: Large Soul of a Nameless Soldier - B2 far, by liftTaking the elevator up from the area you can use Path of the Dragon, before the one-way door
ID: Large Soul of a Nameless Soldier - B2, hall by stairsAt the end of the bottom corridor on the bonfire side in Irithyll Dungeon
ID: Large Soul of a Weary Warrior - just before Profaned CapitalIn the open area before the bridge leading into Profaned Capital from Irithyll Dungeon
ID: Large Titanite Shard - B1 far, rightmost cellIn a cell on the far end of the top corridor opposite to the bonfire in Irithyll Dungeon, nearby the Jailer
ID: Large Titanite Shard - B1 near, by doorAt the end of the top corridor on the bonfire side in Irithyll Dungeon, before the Jailbreaker's Key door
ID: Large Titanite Shard - B3 near, right cornerIn the main Jailer cell block, to the left of the hallway leading to the Path of the Dragon area
ID: Large Titanite Shard - after bonfire, second cell on rightIn the second cell on the right after Irithyll Dungeon bonfire
ID: Large Titanite Shard - pit #1On the floor where the Giant Slave is standing
ID: Large Titanite Shard - pit #2On the floor where the Giant Slave is standing
ID: Lightning Blade - B3 lift, middle platformOn the middle platform riding the elevator up from the Path of the Dragon area
ID: Lightning Bolt - awning over pitOn the wooden overhangs above the Giant Slave. Can be reached by dropping down after climbing the long ladder around the area where the Giant stands.
ID: Murakumo - Alva dropDropped by Alva, Seeker of the Spurned when he invades in the cliffside path to Irithyll Dungeon
ID: Old Cell Key - stairs between pit and B3In a chest found going past the Giant Slave to the sewer with the rats and the basilisks, up the stairs to the end, on the right side
ID: Old Sorcerer Boots - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Old Sorcerer Coat - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Old Sorcerer Gauntlets - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Old Sorcerer Hat - B2 near, middle cellIn one of the cells on the bottom corridor on the bonfire side in Irithyll Dungeon, close to the bonfire, with many Infested Corpses
ID: Pale Pine Resin - B1 far, cell with broken wallIn the jail cell with the broken wall in the top corridor opposite to the bonfire in Irithyll Dungeon, near the passive Wretch on the wall
ID: Pickaxe - path from pit to B3Passing by the Giant Slave, before the tunnel with the rats and basilisks
ID: Prisoner Chief's Ashes - B2 near, locked cell by stairsIn the cell at the far end of the bottom corridor on the bonfire side in Irithyll Dungeon
ID: Profaned Coal - B3 far, left cellIn the room with the Wretches next to the main Jailer cell block, guarded by a Wretch
ID: Profaned Flame - pitOn the floor where the Giant Slave is standing
ID: Rusted Coin - after bonfire, first cell on leftIn the first cell on the left from the Irithyll dungeon bonfire
ID: Rusted Gold Coin - after bonfire, last cell on rightIn the third cell on the right from the Irithyll Dungeon bonfire
ID: Simple Gem - B2 far, cell by stairsIn the cell near the bottom corridor opposite to the bonfire in Irithyll Dungeon, adjacent to the room with three Jailers and Cage Spiders
ID: Soul of a Crestfallen Knight - balcony above pitUnder whether the Giant Slave is resting his head
ID: Soul of a Weary Warrior - by drop to pitAt the end of the room with many peasant hollows after the Estus Shard mimic
ID: Soul of a Weary Warrior - stairs between pit and B3Going past the Giant Slave to the sewer with the rats and the basilisks, up the first flight of stairs
ID: Titanite Chunk - balcony above pit, lizardDropped by the Crystal Lizard where the Giant Slave is resting his head
ID: Titanite Chunk - pit, miniboss dropDrop from the Giant Slave
ID: Titanite Scale - B2 far, lizardDropped by the Crystal Lizard on the bottom corridor opposite from the bonfire in Irithyll Dungeon where a Wretch attacks you
ID: Titanite Scale - B3 far, mimic in hallDropped by the mimic in the main Jailer cell block
ID: Titanite Slab - SiegwardGiven by Siegward after unlocking Old Cell or on quest completion
ID: Xanthous Ashes - B3 far, right cellIn the cell in the main Jailer cell block to the left of the Profaned Capital exit
KFF: Soul of the LordsDropped by Soul of Cinder
LC: Black Firebomb - dark room lowerIn the room with the firebomb-throwing hollows, against the wall on the lowest level
LC: Braille Divine Tome of Lothric - wyvern roomIn the room next to the second Pus of Man wyvern
LC: Caitha's Chime - chapel, drop onto roofDropping down from the chapel balcony where the Red Tearstone Ring is found, and then dropping down again towards the Lothric knights
LC: Dark Stoneplate Ring+1 - wyvern room, balconyThrough the room next to the second Pus of Man wyvern, on the balcony outside
LC: Ember - by Dragon Barracks bonfireNear the Dragon Barracks bonfire
LC: Ember - dark room mid, pus of man mob dropDropped by the first Pus of Man wyvern
LC: Ember - main hall, left of stairsTo the left of the stairs past the Dragon Barracks grate
LC: Ember - plaza centerIn the area where the Pus of Man wyverns breathe fire
LC: Ember - plaza, by gateOn the railing near the area where the Pus of Man wyverns breathe fire, before the gate
LC: Ember - wyvern room, wyvern foot mob dropDropped by the second Pus of Man wyvern
LC: Gotthard Twinswords - by Grand Archives door, after PC and AL bossesBefore the door to the Grand Archives after Aldrich and Yhorm are killed
LC: Grand Archives Key - by Grand Archives door, after PC and AL bossesBefore the door to the Grand Archives after Aldrich and Yhorm are killed
LC: Greatlance - overlooking Dragon Barracks bonfireGuarded by a pensive Lothric Knight after the Dragon Barracks bonfire and continuing up the stairs
LC: Hood of PrayerIn a chest right after the Lothric Castle bonfire
LC: Irithyll Rapier - basement, miniboss dropDropped by the Boreal Outrider Knight in the basement
LC: Knight's Ring - altarClimbing the ladder to the rooftop outside the Dragonslayer Armour fight, past the Large Hollow Soldier, down into the room with the tables
LC: Large Soul of a Nameless Soldier - dark room midIn the room with the firebomb-throwing hollows, up the ladder
LC: Large Soul of a Nameless Soldier - moat, right pathFound on the ledge after dropping into the area with the Pus of Man transforming hollows and making the entire loop
LC: Large Soul of a Nameless Soldier - plaza left, by pillarIn the building to the left of the area where the Pus of Man wyverns breathe fire, against a pillar
LC: Large Soul of a Weary Warrior - ascent, last turretRather than going up the stairs to the Dragon Barracks bonfire, continue straight down the stairs and forwards
LC: Large Soul of a Weary Warrior - main hall, by leverOn a ledge to the right of the lever opening the grate
LC: Life Ring+2 - dark room mid, out door opposite wyvern, drop downPast the room with the firebomb-throwing hollows and Pus of Man wyvern, around to the front, dropping down past where the Titanite Chunk is
LC: Lightning Urn - moat, right path, first roomStarting the loop from where the Pus of Man hollows transform, behind some crates in the first room
LC: Lightning Urn - plazaIn the area where the Pus of Man wyverns breathe fire
LC: Pale Pine Resin - dark room upper, by mimicIn the room with the firebomb-throwing hollows, next to the mimic in the far back left
LC: Raw Gem - plaza leftOn a balcony to the left of the area where the Pus of Man wyverns breathe fire, where the Hollow Soldier throws Undead Hunter Charms
LC: Red Tearstone Ring - chapel, drop onto roofFrom the chapel to the right of the Dragonslayer Armour fight, on the balcony to the left
LC: Refined Gem - plazaIn the area where the Pus of Man wyverns breathe fire
LC: Robe of Prayer - ascent, chest at beginningIn a chest right after the Lothric Castle bonfire
LC: Rusted Coin - chapelIn the chapel to the right of the Dragonslayer Armour fight
LC: Sacred Bloom Shield - ascent, behind illusory wallUp the ladder where the Winged Knight is waiting, past an illusory wall
LC: Skirt of Prayer - ascent, chest at beginningIn a chest right after the Lothric Castle bonfire
LC: Sniper Bolt - moat, right path endHanging from the arch passed under on the way to the Dragon Barracks bonfire. Can be accessed by dropping into the area with the Pus of Man transforming hollows and making the entire loop, but going left at the end
LC: Sniper Crossbow - moat, right path endHanging from the arch passed under on the way to the Dragon Barracks bonfire. Can be accessed by dropping into the area with the Pus of Man transforming hollows and making the entire loop, but going left at the end
LC: Soul of Dragonslayer ArmourDropped by Dragonslayer Armour
LC: Soul of a Crestfallen Knight - by lift bottomGuarded by a buffed Lothric Knight straight from the Dancer bonfire
LC: Soul of a Crestfallen Knight - wyvern room, balconyOn a ledge accessible after the second Pus of Man wyvern is defeated
LC: Spirit Tree Crest Shield - basement, chestIn a chest in the basement with the Outrider Knight
LC: Sunlight Medal - by lift topNext to the shortcut elevator outside of the Dragonslayer Armour fight that goes down to the start of the area
LC: Sunlight Straight Sword - wyvern room, mimicDropped by the mimic in the room next to the second Pus of Man wyvern
LC: Thunder Stoneplate Ring+2 - chapel, drop onto roofDropping down from the chapel balcony where the Red Tearstone Ring is found, out on the edge
LC: Titanite Chunk - altar roofClimbing the ladder to the rooftop outside the Dragonslayer Armour fight, overlooking the tree
LC: Titanite Chunk - ascent, final turretRather than going up the stairs to the Dragon Barracks bonfire, continue straight down the stairs, then right
LC: Titanite Chunk - ascent, first balconyRight after the Lothric Castle bonfire, out on the balcony
LC: Titanite Chunk - ascent, turret before barricadesFrom the Lothric Castle bonfire, up the stairs, straight, and then down the stairs behind the barricade
LC: Titanite Chunk - dark room mid, out door opposite wyvernFrom the room with the firebomb-throwing hollows, past the Pus of Man Wyvern and back around the front, before the Crystal Lizard
LC: Titanite Chunk - dark room mid, pus of man mob dropDropped by the first Pus of Man wyvern
LC: Titanite Chunk - down stairs after bossDown the stairs to the right after Dragonslayer Armour
LC: Titanite Chunk - moat #1In the center of the area where the Pus of Man hollows transform
LC: Titanite Chunk - moat #2In the center of the area where the Pus of Man hollows transform
LC: Titanite Chunk - moat, near ledgeDropping down from the bridge where the Pus of Man wyverns breathe fire on the near side to the bonfire
LC: Titanite Chunk - wyvern room, wyvern foot mob dropDropped by the second Pus of Man wyvern
LC: Titanite Scale - altarIn a chest climbing the ladder to the rooftop outside the Dragonslayer Armour fight, continuing the loop past the Red-Eyed Lothric Knight
LC: Titanite Scale - basement, chestIn a chest in the basement with the Outrider Knight
LC: Titanite Scale - chapel, chestIn a chest in the chapel to the right of the Dragonslayer Armour fight
LC: Titanite Scale - dark room mid, out door opposite wyvernPassing through the room with the firebomb-throwing hollows and the Pus of Man wyvern around to the front, overlooking the area where the wyverns breathe fire
LC: Titanite Scale - dark room, upper balconyIn the room with the firebomb-throwing hollows, at the very top on a balcony to the right
LC: Titanite Scale - dark room, upper, mimicDropped by the crawling mimic at the top of the room with the firebomb-throwing hollows
LC: Twinkling Titanite - ascent, side roomIn the room where the Winged Knight drops down
LC: Twinkling Titanite - basement, chest #1In a chest in the basement with the Outrider Knight
LC: Twinkling Titanite - basement, chest #2In a chest in the basement with the Outrider Knight
LC: Twinkling Titanite - dark room mid, out door opposite wyvern, lizardDropped by the Crystal Lizard after the room with the firebomb-throwing hollows around the front
LC: Twinkling Titanite - moat, left sideBehind one of the Pus of Man transforming hollows, to the left of the bridge to the wyvern fire-breathing area
LC: Twinkling Titanite - moat, right path, lizardDropped by the Crystal Lizard near the thieves after dropping down to the area with the Pus of Man transforming hollows
LC: Undead Bone Shard - moat, far ledgeDropping down from the bridge where the Pus of Man wyverns breathe fire on the far side from the bonfire
LC: Winged Knight Armor - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
LC: Winged Knight Gauntlets - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
LC: Winged Knight Helm - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
LC: Winged Knight Leggings - ascent, behind illusory wallIn the area where the Winged Knight drops down, up the ladder and past the illusory wall
PC: Blooming Purple Moss Clump - walkway above swampAt the right end of the plank before dropping down into the Profaned Capital toxic pool
PC: Cinders of a Lord - Yhorm the GiantDropped by Yhorm the Giant
PC: Court Sorcerer Gloves - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer Hood - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer Robe - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer Trousers - chapel, second floorOn the second floor of the Monstrosity of Sin building in front of the Monstrosity of Sin
PC: Court Sorcerer's Staff - chapel, mimic on second floorDropped by the mimic on the second floor of the Monstrosity of Sin building
PC: Cursebite Ring - swamp, below hallsIn the inner cave of the Profaned Capital toxic pool
PC: Eleonora - chapel ground floor, kill mobDropped by the Monstrosity of Sin on the first floor, furthest away from the door
PC: Ember - palace, far roomTo the right of the Profaned Flame, in the room with the many Jailers looking at the mimics
PC: Flame Stoneplate Ring+1 - chapel, drop from roof towards entranceDropping down from the roof connected to the second floor of the Monstrosity of Sin building, above the main entrance to the building
PC: Greatshield of Glory - palace, mimic in far roomDropped by the left mimic surrounded by the Jailers to the right of the Profaned Flame
PC: Jailer's Key Ring - hall past chapelPast the Profaned Capital Court Sorcerer, in the corridor overlooking the Irithyll Dungeon Giant Slave area
PC: Large Soul of a Weary Warrior - bridge, far endOn the way from the Profaned Capital bonfire toward the Profaned Flame, crossing the bridge without dropping down
PC: Logan's Scroll - chapel roof, NPC dropDropped by the court sorcerer above the toxic pool
PC: Magic Stoneplate Ring+2 - tower baseAt the base of the Profaned Capital structure, going all the way around the outside wall clockwise
PC: Onislayer Greatarrow - bridgeItem on the bridge descending from the Profaned Capital bonfire into the Profaned Flame building
PC: Onislayer Greatbow - drop from bridgeFrom the bridge leading from the Profaned Capital bonfire to Yhorm, onto the ruined pillars shortcut to the right, behind you after the first dropdown.
PC: Pierce Shield - SiegwardDropped by Siegward upon death or quest completion, and sold by Patches while Siegward is in the well.
PC: Poison Arrow - chapel roofAt the far end of the roof with the Court Sorcerer
PC: Poison Gem - swamp, below hallsIn the inner cave of the Profaned Capital toxic pool
PC: Purging Stone - chapel ground floorAt the back of the room with the three Monstrosities of Sin on the first floor
PC: Purging Stone - swamp, by chapel ladderIn the middle of the Profaned Capital toxic pool, near the ladder to the Court Sorcerer
PC: Rubbish - chapel, down stairs from second floorHanging corpse visible from Profaned Capital accessible from the second floor of the building with the Monstrosities of Sin, in the back right
PC: Rusted Coin - below bridge #1Among the rubble before the steps leading up to the Profaned Flame
PC: Rusted Coin - below bridge #2Among the rubble before the steps leading up to the Profaned Flame
PC: Rusted Coin - tower exteriorTreasure visible on a ledge in the Profaned Capital bonfire. Can be accessed by climbing a ladder outside the main structure.
PC: Rusted Gold Coin - halls above swampIn the corridors leading to the Profaned Capital toxic pool
PC: Rusted Gold Coin - palace, mimic in far roomDropped by the right mimic surrounded by the Jailers to the right of the Profaned Flame
PC: Shriving Stone - swamp, by chapel doorAt the far end of the Profaned Capital toxic pool, to the left of the door leading to the Monstrosities of Sin
PC: Siegbräu - Siegward after killing bossGiven by Siegward after helping him defeat Yhorm the Giant. You must talk to him before Emma teleports you.
PC: Soul of Yhorm the GiantDropped by Yhorm the Giant
PC: Storm Ruler - SiegwardDropped by Siegward upon death or quest completion.
PC: Storm Ruler - boss roomTo the right of Yhorm's throne
PC: Twinkling Titanite - halls above swamp, lizard #1Dropped by the second Crystal Lizard in the corridors before the Profaned Capital toxic pool
PC: Twinkling Titanite - halls above swamp, lizard #2Dropped by the first Crystal Lizard in the corridors before the Profaned Capital toxic pool
PC: Undead Bone Shard - by bonfireOn the corpse of Laddersmith Gilligan next to the Profaned Capital bonfire
PC: Wrath of the Gods - chapel, drop from roofDropping down from the roof of the Monstrosity of Sin building where the Court Sorcerer is
PW1: Black Firebomb - snowfield lower, path to bonfireDropping down after the first snow overhang and following the wall on the left, past the rotting bed descending toward the second bonfire
PW1: Blessed Gem - snowfield, behind towerBehind the Millwood Knight tower in the first area, approach from the right side
PW1: Budding Green Blossom - settlement courtyard, ledgeAfter sliding down the slope on the way to Corvian Settlement, dropping down hugging the left wall
PW1: Captain's Ashes - snowfield tower, 6FAt the very top of the Millwood Knight tower after climbing up the second ladder
PW1: Chillbite Ring - FriedeGiven by Sister Friede while she is sitting in the Ariandel Chapel, or on the stool after she moves.
PW1: Contraption Key - library, NPC dropDropped by Sir Vilhelm
PW1: Crow Quills - settlement loop, jump into courtyardCrossing the bridge after Corvian Settlement bonfire, follow the left edge past another bridge until a dropdown point looping back to the bonfire. Go right and jump past some barrels onto the central platform.
PW1: Crow Talons - settlement roofs, near bonfireAfter climbing the ladder onto Corvian Settlement rooftops, dropping down on a bridge to the left, into the building, then looping around onto its roof.
PW1: Dark Gem - settlement back, egg buildingDropping down to the right of the gate guarded by a Corvian Knight in Corvian Settlement, inside of the last building on the right
PW1: Ember - roots above depthsIn the tree branch area after climbing down the rope bridge, hugging a right wall past a Follower Javelin wielder
PW1: Ember - settlement main, left building after bridgeCrossing the bridge after Corvian Settlement bonfire, in the building to the left.
PW1: Ember - settlement, building near bonfireIn the first building in Corvian Settlement next to the bonfire building
PW1: Ethereal Oak Shield - snowfield tower, 3FIn the Millwood Knight tower on a Millwood Knight corpse, after climbing the first ladder, then going down the staircase
PW1: Follower Javelin - snowfield lower, path back upDropping down after the first snow overhang, follow the right wall around and up a slope, past the Followers
PW1: Follower Sabre - roots above depthsOn a tree branch after climbing down the rope bridge. Rather than hugging a right wall toward a Follower Javelin wielder, drop off to the left.
PW1: Frozen Weapon - snowfield lower, egg zoneDropping down after the first snow overhang, in the rotting bed along the left side
PW1: Heavy Gem - snowfield villageBefore the Millwood Knight tower, on the far side of one of the ruined walls targeted by the archer
PW1: Hollow Gem - beside chapelTo the right of the entrance to the Ariandel
PW1: Homeward Bone - depths, up hillIn the Depths of the Painting, up a hill next to the giant crabs.
PW1: Homeward Bone - snowfield village, outcroppingDropping down after the first snow overhang and following the cliff on the right, making a sharp right after a ruined wall segment before approaching the Millwood Knight tower
PW1: Large Soul of a Weary Warrior - settlement hall roofOn top of the chapel with the Corvian Knight to the left of Vilhelm's building
PW1: Large Soul of a Weary Warrior - snowfield tower, 6FAt the very top of the Millwood Knight tower after climbing up the second ladder, on a Millwood Knight corpse
PW1: Large Soul of an Unknown Traveler - below snowfield village overhangUp the slope to the left of the Millwood Knight tower, dropping down after a snow overhang, then several more ledges.
PW1: Large Soul of an Unknown Traveler - settlement backIn Corvian Settlement, on the ground before the ladder climbing onto the rooftops
PW1: Large Soul of an Unknown Traveler - settlement courtyard, cliffAfter sliding down the slope on the way to Corvian Settlement, on a cliff to the right and behind
PW1: Large Soul of an Unknown Traveler - settlement loop, by bonfireCrossing the bridge after Corvian Settlement bonfire, follow the left edge past another bridge until a dropdown point looping back to the bonfire. On the corpse in a hole in the wall leading back to the bonfire.
PW1: Large Soul of an Unknown Traveler - settlement roofs, balconyAfter climbing the ladder onto Corvian Settlement rooftops, dropping down on a bridge to the left, on the other side of the bridge.
PW1: Large Soul of an Unknown Traveler - settlement, by ladder to bonfireTo the right of the ladder leading up to Corvian Settlement bonfire.
PW1: Large Soul of an Unknown Traveler - snowfield lower, by cliffDropping down after the first snow overhang, between the forest and the cliff edge, before where the large wolf drops down
PW1: Large Soul of an Unknown Traveler - snowfield lower, path back upDropping down after the first snow overhang, follow the right wall around and up a slope, past the Followers
PW1: Large Soul of an Unknown Traveler - snowfield lower, path to villageDropping down after the first snow overhang and following the cliff on the right, on a tree past where the large wolf jumps down
PW1: Large Soul of an Unknown Traveler - snowfield upperGoing straight after the first bonfire, to the left of the caving snow overhand
PW1: Large Titanite Shard - lizard under bridge nearDropped by a Crystal Lizard after the Rope Bridge Cave on the way to Corvian Settlement
PW1: Large Titanite Shard - settlement loop, lizardCrossing the bridge after Corvian Settlement bonfire, follow the left edge past another bridge until a dropdown point looping back to the bonfire. Hug the bonfire building's outer wall along the right side.
PW1: Large Titanite Shard - snowfield lower, left from fallDropping down after the first snow overhang, guarded by a Tree Woman overlooking the rotting bed along the left wall
PW1: Millwood Battle Axe - snowfield tower, 5FIn the Milkwood Knight tower, either dropping down from rafters after climbing the second ladder or making a risky jump
PW1: Millwood Greatarrow - snowfield village, loop back to lowerDropping down after the first snow overhang and following the cliff on the right, making the full loop around, up the slope leading towards where the large wolf drops down
PW1: Millwood Greatbow - snowfield village, loop back to lowerDropping down after the first snow overhang and following the cliff on the right, making the full loop around, up the slope leading towards where the large wolf drops down
PW1: Onyx Blade - library, NPC dropDropped by Sir Vilhelm
PW1: Poison Gem - snowfield upper, forward from bonfireFollowing the left wall from the start, guarded by a Giant Fly
PW1: Rime-blue Moss Clump - below bridge farIn a small alcove to the right after climbing down the rope bridge
PW1: Rime-blue Moss Clump - snowfield upper, overhangOn the first snow overhang at the start. It drops down at the same time you do.
PW1: Rime-blue Moss Clump - snowfield upper, starting caveIn the starting cave
PW1: Rusted Coin - right of libraryTo the right of Vilhelm's building
PW1: Rusted Coin - snowfield lower, straight from fallDropping down after the first snow overhang, shortly straight ahead
PW1: Rusted Gold Coin - settlement roofs, roof near second ladderAfter climbing the second ladder on the Corvian Settlement rooftops, immediately dropping off the bridge to the right, on a rooftop
PW1: Shriving Stone - below bridge nearAfter the Rope Bridge Cave bonfire, dropping down before the bridge, following the ledge all the way to the right
PW1: Simple Gem - settlement, lowest level, behind gateCrossing the bridge after Corvian Settlement bonfire, follow the left edge until a bridge, then drop down on the right side. Guarded by a Sewer Centipede.
PW1: Slave Knight Armor - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Slave Knight Gauntlets - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Slave Knight Hood - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Slave Knight Leggings - settlement roofs, drop by ladderIn Corvian Settlement, rather than climbing up a ladder leading to a bridge to the roof of the chapel with the Corvian Knight, dropping down a hole to the left of the ladder into the building below.
PW1: Snap Freeze - depths, far end, mob dropIn the Depths of the Painting, past the giant crabs, guarded by a special Tree Woman. Killing her drops down a very long nearby ladder.
PW1: Soul of a Crestfallen Knight - settlement hall, raftersIn the rafters of the chapel with the Corvian Knight to the left of Vilhelm's building. Can drop down from the windows exposed to the roof.
PW1: Soul of a Weary Warrior - snowfield tower, 1FAt the bottom of the Millwood Knight tower on a Millwood Knight corpse
PW1: Titanite Slab - CorvianGiven by the Corvian NPC in the building next to Corvian Settlement bonfire.
PW1: Titanite Slab - depths, up secret ladderIn the Depths of the Painting, past the giant crabs, killing a special Tree Woman drops down a very long nearby ladder. Climb the ladder and also the ladder after that one.
PW1: Twinkling Titanite - roots, lizardDropped by a Crystal Lizard in the tree branch area after climbing down the rope bridge, before the ledge with the Follower Javelin wielder
PW1: Twinkling Titanite - settlement roofs, lizard before hallDropped by a Crystal Lizard on a bridge in Corvian Settlement before the rooftop of the chapel with the Corvian Knight inside.
PW1: Twinkling Titanite - snowfield tower, 3F lizardDropped by a Crystal Lizard in the Millwood Knight tower, climbing up the first ladder and descending the stairs down
PW1: Valorheart - boss dropDropped by Champion's Gravetender
PW1: Way of White Corona - settlement hall, by altarIn the chapel with the Corvian Knight to the left of Vilhelm's building, in front of the altar.
PW1: Young White Branch - right of libraryTo the right of Vilhelm's building
PW2: Blood Gem - B2, centerOn the lower level of the Ariandel Chapel basement, in the middle
PW2: Dung Pie - B1On the higher level of the Ariandel Chapel basement, on a wooden beam overlooking the lower level
PW2: Earth Seeker - pit caveIn the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, in the cave
PW2: Ember - pass, central alcoveAfter the Snowy Mountain Pass bonfire, going left of the bell stuck in the ground, in a small alcove along the left wall
PW2: Floating Chaos - NPC dropDropped by Livid Pyromancer Dunnel when he invades while embered, whether boss is defeated or not. On the second level of Priscilla's building above the Gravetender fight, accessed from the lowest level of the Ariandel Chapel basement, past an illusory wall nearly straight left of the mechanism that moves the statue, then carefully dropping down tree branches.
PW2: Follower Shield - pass, far cliffsideAfter the Snowy Mountain Pass bonfire, going left of the bell stuck in the ground, on the cliff ledge past the open area, to the left
PW2: Follower Torch - pass, far side pathOn the way to the Ariandel Chapel basement, where the first wolf enemies reappear, going all the way down the slope on the edge of the map. Guarded by a Follower
PW2: Homeward Bone - rotundaOn the second level of Priscilla's building above the Gravetender fight. Can be accessed from the lowest level of the Ariandel Chapel basement, past an illusory wall nearly straight left of the mechanism that moves the statue, then carefully dropping down tree branches.
PW2: Large Soul of a Crestfallen Knight - pit, by treeIn the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, by the tree
PW2: Large Titanite Shard - pass, far side pathOn the way to the Ariandel Chapel basement, where the first wolf enemies reappear, going partway down the slope on the edge of the map
PW2: Large Titanite Shard - pass, just before B1On the way to Ariandel Chapel basement, past the Millwood Knights and before the first rotten tree that can be knocked down
PW2: Prism Stone - pass, tree by beginningUp the slope and to the left after the Snowy Mountain Pass, straight ahead by a tree
PW2: Pyromancer's Parting Flame - rotundaOn the second level of Priscilla's building above the Gravetender fight. Can be accessed from the lowest level of the Ariandel Chapel basement, past an illusory wall nearly straight left of the mechanism that moves the statue, then carefully dropping down tree branches.
PW2: Quakestone Hammer - pass, side path near B1On the way to Ariandel Chapel basement, rather than going right past the two Millwood Knights, go left, guarded by a very strong Millwood Knight
PW2: Soul of Sister FriedeDropped by Sister Friede
PW2: Soul of a Crestfallen Knight - pit edge #1In the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, along the edge
PW2: Soul of a Crestfallen Knight - pit edge #2In the area after Snowy Mountain Pass with the giant tree and Earth Seeker Millwood Knight, along the edge
PW2: Titanite Chunk - pass, by kickable treeAfter the Snowy Mountain Pass bonfire, on a ledge to the right of the slope with the bell stuck in the ground, behind a tree
PW2: Titanite Chunk - pass, cliff overlooking bonfireOn a cliff overlooking the Snowy Mountain Pass bonfire. Requires following the left wall
PW2: Titanite Slab - boss dropOne-time drop after killing Father Ariandel and Friede (phase 2) for the first time.
PW2: Twinkling Titanite - B3, lizard #1Dropped by a Crystal Lizard past an illusory wall nearly straight left of the mechanism that moves the statue in the lowest level of the Ariandel Chapel basement
PW2: Twinkling Titanite - B3, lizard #2Dropped by a Crystal Lizard past an illusory wall nearly straight left of the mechanism that moves the statue in the lowest level of the Ariandel Chapel basement
PW2: Vilhelm's Armor - B2, along wallOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
PW2: Vilhelm's Gauntlets - B2, along wallOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
PW2: Vilhelm's HelmOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
PW2: Vilhelm's Leggings - B2, along wallOn the lower level of the Ariandel Chapel basement, along a wall to the left of the contraption that turns the statue
RC: Antiquated Plain Garb - wall hidden, before bossIn the chapel before the Midir fight in the Ringed Inner Wall building.
RC: Black Witch Garb - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Black Witch Hat - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Black Witch Trousers - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Black Witch Veil - swamp near right, by sunken churchTo the left of the submerged building with 4 Ringed Knights, near a spear-wielding knight.
RC: Black Witch Wrappings - streets gardenGuarded by Alva (invades whether embered or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Blessed Gem - grave, down lowest stairsIn Shared Grave, after dropping down near Gael's flag and dropping down again, behind you. Or from the bonfire, go back through the side tunnel with the skeletons and down the stairs after that.
RC: Blindfold Mask - grave, NPC dropDropped by Moaning Knight (invades whether embered or not, or boss defeated or not) in Shared Grave.
RC: Blood of the Dark Soul - end boss dropDropped by Slave Knight Gael
RC: Budding Green Blossom - church pathOn the way to the Halflight building.
RC: Budding Green Blossom - wall top, flowers by stairsIn a patch of flowers to the right of the stairs leading up to the first Judicator along the left wall of the courtyard are Mausoleum Lookout.
RC: Budding Green Blossom - wall top, in flower clusterAlong the left wall of the courtyard after Mausoleum Lookout, in a patch of flowers.
RC: Chloranthy Ring+3 - wall hidden, drop onto statueFrom the mid level of the Ringed Inner Wall elevator that leads to the Midir fight, dropping back down toward the way to Filianore, onto a platform with a Gwyn statue. Try to land on the platform rather than the statue.
RC: Church Guardian Shiv - swamp far left, in buildingInside of the building at the remote end of the muck pit surrounded by praying Hollow Clerics.
RC: Covetous Gold Serpent Ring+3 - streets, by LappGoing up the very long ladder from the muck pit, then up some stairs, to the left, and across the bridge, in a building past the Ringed Knights. Also where Lapp can be found to tell him of the Purging Monument.
RC: Crucifix of the Mad King - ashes, NPC dropDropped by Shira, who invades you (ember not required) in the far-future version of her room
RC: Dark Gem - swamp near, by stairsIn the middle of the muck pit, close to the long stairs.
RC: Divine Blessing - streets monument, mob dropDropped by the Judicator near the Purging Monument area. Requires solving "Show Your Humanity" puzzle.
RC: Divine Blessing - wall top, mob dropDropped by the Judicator after the Mausoleum Lookup bonfire.
RC: Dragonhead Greatshield - lower cliff, under bridgeDown a slope to the right of the bridge where Midir first assaults you, past a sword-wielding Ringed Knight, under the bridge.
RC: Dragonhead Shield - streets monument, across bridgeFound in Purging Monument area, across the bridge from the monument. Requires solving "Show Your Humanity" puzzle.
RC: Ember - wall hidden, statue roomFrom the mid level of the Ringed Inner Wall elevator that leads to the Midir fight, in the room with the illusory statue.
RC: Ember - wall top, by statueAlong the left wall of the courtyard after Mausoleum Lookout, in front of a tall monument.
RC: Ember - wall upper, balconyOn the balcony attached to the room with the Ringed Inner Wall bonfire.
RC: Filianore's Spear Ornament - mid boss dropDropped by Halflight, Spear of the Church
RC: Filianore's Spear Ornament - wall hidden, by ladderNext the ladder leading down to the chapel before the Midir fight in the Ringed Inner Wall building.
RC: Havel's Ring+3 - streets high, drop from building oppositeDropping down from the building where Silver Knight Ledo invades. The building is up the very long ladder from the muck pit, down the path all the way to the right.
RC: Hidden Blessing - swamp center, mob dropDropped by Judicator patrolling the muck pit.
RC: Hidden Blessing - wall top, tomb under platformIn a tomb underneath the platform with the first Judicator, accessed by approaching from Mausoleum Lookout bonfire.
RC: Hollow Gem - wall upper, path to towerHeading down the cursed stairs after Ringed Inner Wall bonfire and another short flight of stairs, hanging on a balcony.
RC: Iron Dragonslayer Armor - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Iron Dragonslayer Gauntlets - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Iron Dragonslayer Helm - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Iron Dragonslayer Leggings - swamp far, miniboss dropDropped by Dragonslayer Armour at the far end of the muck pit.
RC: Lapp's Armor - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Lapp's Gauntlets - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Lapp's Helm - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Lapp's Leggings - LappLeft at Lapp's final location in Shared Grave after his quest is complete, or sold by Shrine Handmaid upon killing Lapp.
RC: Large Soul of a Crestfallen Knight - streets monument, across bridgeFound in Purging Monument area, on the other side of the bridge leading to the monument. Requires solving "Show Your Humanity" puzzle.
RC: Large Soul of a Crestfallen Knight - streets, far stairsToward the bottom of the stairs leading down to the muck pit.
RC: Large Soul of a Weary Warrior - lower cliff, endToward the end of the upper path attacked Midir's fire-breathing.
RC: Large Soul of a Weary Warrior - swamp center, by peninsulaIn the muck pit approaching where the Judicator patrols from the stairs.
RC: Large Soul of a Weary Warrior - wall lower, past two illusory wallsIn the Ringed Inner Wall building coming from Shared Grave, past two illusory walls on the right side of the ascending stairs.
RC: Large Soul of a Weary Warrior - wall top, right of small tombIn the open toward the end of the courtyard after the Mausoleum Lookout bonfire, on the right side of the small tomb.
RC: Ledo's Great Hammer - streets high, opposite building, NPC dropDropped by Silver Knight Ledo (invades whether embered or not, or boss defeated or not) in the building down the path to the right after climbing the very long ladder from the muck area.
RC: Lightning Arrow - wall lower, past three illusory wallsIn the Ringed Inner Wall building coming from Shared Grave, past three illusory walls on the right side of the ascending stairs.
RC: Lightning Gem - grave, room after first dropIn Shared Grave, in the first room encountered after falling down from the crumbling stairs and continuing upward.
RC: Mossfruit - streets near left, path to gardenPartway down the stairs from Shira, across the bridge.
RC: Mossfruit - streets, far left alcoveNear the bottom of the stairs before the muck pit, in an alcove to the left.
RC: Preacher's Right Arm - swamp near right, by towerIn the muck pit behind a crystal-covered structure, close to the Ringed City Streets shortcut entrance.
RC: Prism Stone - swamp near, railing by bonfireOn the balcony of the path leading up to Ringed City Streets bonfire from the muck pit.
RC: Purging Stone - wall top, by door to upperAt the end of the path from Mausoleum Lookup to Ringed Inner Wall, just outside the door.
RC: Ring of the Evil Eye+3 - grave, mimicDropped by mimic in Shared Grave. In one of the rooms after dropping down near Gael's flag and then dropping down again.
RC: Ringed Knight Paired Greatswords - church path, mob dropDropped by Ringed Knight with paired greatswords before Filianore building.
RC: Ringed Knight Spear - streets, down far right hallIn a courtyard guarded by a spear-wielding Ringed Knight. Can be accessed from a hallway filled with cursed clerics on the right side going down the long stairs, or by climbing up the long ladder from the muck pit and dropping down past the Locust Preacher.
RC: Ringed Knight Straight Sword - swamp near, tower on peninsulaOn a monument next to the Ringed City Streets building. Can be easily accessed after unlocking the shortcut by following the left wall inside and then outside the building.
RC: Ritual Spear Fragment - church pathTo the right of the Paired Greatswords Ringed Knight on the way to Halflight.
RC: Rubbish - lower cliff, middleIn the middle of the upper path attacked Midir's fire-breathing, after the first alcove.
RC: Rubbish - swamp far, by crystalIn the remote end of the muck pit, next to a massive crystal structure between a giant tree and the building with praying Hollow Clerics, guarded by several Locust Preachers.
RC: Ruin Armor - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Ruin Gauntlets - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Ruin Helm - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Ruin Leggings - wall top, under stairs to bonfireUnderneath the stairs leading down from Mausoleum Lookout.
RC: Sacred Chime of Filianore - ashes, NPC dropGiven by Shira after accepting her request to kill Midir, or dropped by her in post-Filianore Ringed City.
RC: Shira's Armor - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shira's Crown - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shira's Gloves - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shira's Trousers - Shira's room after killing ashes NPCFound in Shira's room in Ringed City after killing her in post-Filianore Ringed City.
RC: Shriving Stone - wall tower, bottom floor centerIn the cylindrical building before the long stairs with many Harald Legion Knights, in the center structure on the first floor.
RC: Siegbräu - LappGiven by Lapp within the Ringed Inner Wall.
RC: Simple Gem - grave, up stairs after first dropIn Shared Grave, following the path after falling down from the crumbling stairs and continuing upward.
RC: Soul of Darkeater MidirDropped by Darkeater Midir
RC: Soul of Slave Knight GaelDropped by Slave Knight Gael
RC: Soul of a Crestfallen Knight - swamp far, behind crystalBehind a crystal structure at the far end of the muck pit, close to the building with the praying Hollow Clerics before Dragonslayer Armour.
RC: Soul of a Crestfallen Knight - swamp near left, nookIn the muck pit behind all of the Hollow Clerics near the very long ladder.
RC: Soul of a Crestfallen Knight - wall top, under dropAfter dropping down onto the side path on the right side of the Mausoleum Lookout courtyard to where the Crystal Lizard is, behind you.
RC: Soul of a Weary Warrior - lower cliff, by first alcoveIn front of the first alcove providing shelter from Midir's fire-breathing on the way to Shared Grave.
RC: Soul of a Weary Warrior - swamp centerIn the middle of the muck pit where the Judicator is patrolling.
RC: Soul of a Weary Warrior - swamp right, by sunken churchIn between where the Judicator patrols in the muck pit and the submerged building with the 4 Ringed Knights. Provides some shelter from his arrows.
RC: Spears of the Church - hidden boss dropDropped by Darkeater Midir
RC: Titanite Chunk - streets high, building oppositeDown a path past the room where Silver Knight Ledo invades. The building is up the very long ladder from the muck pit, down the path all the way to the right.
RC: Titanite Chunk - streets, near left dropNear the top of the stairs by Shira, dropping down in an alcove to the left.
RC: Titanite Chunk - swamp center, peninsula edgeAlong the edge of the muck pit close to where the Judicator patrols.
RC: Titanite Chunk - swamp far left, up hillUp a hill at the edge of the muck pit with the Hollow Clerics.
RC: Titanite Chunk - swamp near left, by spire topAt the edge of the muck pit, on the opposite side of the wall from the very long ladder.
RC: Titanite Chunk - swamp near right, behind rockAt the very edge of the muck pit, to the left of the submerged building with 4 Ringed Knights.
RC: Titanite Chunk - wall top, among gravesAlong the right edge of the courtyard after Mausoleum Lookout in a cluster of graves.
RC: Titanite Chunk - wall upper, courtyard alcoveIn the courtyard where the first Ringed Knight is seen, along the right wall into an alcove.
RC: Titanite Scale - grave, lizard past first dropDropped by the Crystal Lizard right after the crumbling stairs in Shared Grave.
RC: Titanite Scale - lower cliff, first alcoveIn the first alcove providing shelter from Midir's fire-breathing on the way to Shared Grave.
RC: Titanite Scale - lower cliff, lower pathAfter dropping down from the upper path attacked by Midir's fire-breathing to the lower path.
RC: Titanite Scale - lower cliff, path under bridgePartway down a slope to the right of the bridge where Midir first assaults you.
RC: Titanite Scale - swamp far, by minibossIn the area at the far end of the muck pit with the Dragonslayer Armour.
RC: Titanite Scale - swamp far, lagoon entranceIn the area at the far end of the muck pit with the Dragonslayer Armour.
RC: Titanite Scale - upper cliff, bridgeOn the final bridge where Midir attacks before you knock him off.
RC: Titanite Scale - wall lower, lizardDropped by the Crystal Lizard on the stairs going up from Shared Grave to Ringed Inner Wall elevator.
RC: Titanite Scale - wall top, behind spawnBehind you at the very start of the level.
RC: Titanite Slab - ashes, NPC dropGiven by Shira after defeating Midir, or dropped by her in post-Filianore Ringed City.
RC: Titanite Slab - ashes, mob dropDropped by the Ringed Knight wandering around near Gael's arena
RC: Titanite Slab - mid boss dropDropped by Halflight, Spear of the Church
RC: Twinkling Titanite - church path, left of boss doorDropping down to the left of the door leading to Halflight.
RC: Twinkling Titanite - grave, lizard past first dropDropped by the Crystal Lizard right after the crumbling stairs in Shared Grave.
RC: Twinkling Titanite - streets high, lizardDropped by the Crystal Lizard which runs across the bridge after climbing the very long ladder up from the muck pit.
RC: Twinkling Titanite - swamp near leftAt the left edge of the muck pit coming from the stairs, guarded by a Preacher Locust.
RC: Twinkling Titanite - swamp near right, on sunken churchFollowing the sloped roof of the submerged building with the 4 Ringed Knights, along the back wall
RC: Twinkling Titanite - wall top, lizard on side pathDropped by the first Crystal Lizard on the side path on the right side of the Mausoleum Lookout courtyard
RC: Twinkling Titanite - wall tower, jump from chandelierIn the cylindrical building before the long stairs with many Harald Legion Knights. Carefully drop down to the chandelier in the center, then jump to the second floor. The item is on a ledge.
RC: Violet Wrappings - wall hidden, before bossIn the chapel before the Midir fight in the Ringed Inner Wall building.
RC: White Birch Bow - swamp far left, up hillUp a hill at the edge of the muck pit with the Hollow Clerics.
RC: White Preacher Head - swamp near, nook right of stairsPast the balcony to the right of the Ringed City Streets bonfire room entrance. Can be accessed by dropping down straight after from the bonfire, then around to the left.
RC: Wolf Ring+3 - street gardens, NPC dropDropped by Alva (invades whether embered or not, or boss defeated or not), partway down the stairs from Shira, across the bridge, and past the Ringed Knight.
RC: Young White Branch - swamp far left, by white tree #1Next to a small birch tree at the edge of the muck pit, between the hill with the aggressive Hollow Clerics and the building with the praying Hollow Clerics outside.
RC: Young White Branch - swamp far left, by white tree #2Next to a small birch tree at the edge of the muck pit, between the hill with the aggressive Hollow Clerics and the building with the praying Hollow Clerics outside.
RC: Young White Branch - swamp far left, by white tree #3Next to a small birch tree at the edge of the muck pit, between the hill with the aggressive Hollow Clerics and the building with the praying Hollow Clerics outside.
RS: Blue Bug Pellet - broken stairs by OrbeckOn the broken stairs leading down from Orbeck's area, on the opposite side from Orbeck
RS: Blue Sentinels - HoraceGiven by Horace the Hushed by first "talking" to him, or upon death.
RS: Braille Divine Tome of Carim - drop from bridge to Halfway FortressDropping down before the bridge leading up to Halfway Fortress from Road of Sacrifices, guarded by the maggot belly dog
RS: Brigand Armor - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Axe - beneath roadAt the start of the path leading down to the Madwoman in Road of Sacrifices
RS: Brigand Gauntlets - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Hood - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Trousers - beneath roadIn the middle of the path where the Madwoman waits in Road of Sacrifices
RS: Brigand Twindaggers - beneath roadAt the end of the path guarded by the Madwoman in Road of Sacrifices
RS: Butcher Knife - NPC drop beneath roadDropped by the Butcher Knife-wielding madwoman near the start of Road of Sacrifices
RS: Chloranthy Ring+2 - road, drop across from carriageFound dropping down from the first Storyteller Corvian on the left side rather than the right side. You can then further drop down to where the madwoman is, after healing.
RS: Conjurator Boots - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Conjurator Hood - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Conjurator Manchettes - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Conjurator Robe - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Crystal Gem - stronghold, lizardDropped by the Crystal Lizard in the building before Crystal Sage
RS: Ember - right of Halfway Fortress entranceOn the ledge with the Corvian with the Storyteller Staff, to the right of the Halfway Fortress entrance
RS: Ember - right of fire behind stronghold left roomBehind the building before Crystal Sage, approached from Crucifixion Woods bonfire. Can drop down on left side or go under bridge on right side
RS: Estus Shard - left of fire behind stronghold left roomBehind the building leading to Crystal Sage, approached from Crucifixion Woods bonfire. Can drop down on left side of go under bridge on right side
RS: Exile Greatsword - NPC drop by Farron KeepDropped by the greatsword-wielding Exile Knight before the ladder down to Farron Keep
RS: Fading Soul - woods by Crucifixion Woods bonfireDropping down from the Crucifixion Woods bonfire toward the Halfway Fortress, guarded by dogs
RS: Fallen Knight Armor - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Fallen Knight Gauntlets - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Fallen Knight Helm - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Fallen Knight Trousers - water's edge by Farron KeepOn the edge of the water surrounding the building where you descend into Farron Keep
RS: Farron Coal - keep perimeterAt the end of the Farron Keep Perimeter building on Crucifixion Woods side, behind the Black Knight
RS: Golden Falcon Shield - path from stronghold right room to Farron KeepHalfway up the stairs to the sorcerer in the building before Crystal Sage, entering from the stairs leading up from the crab area, go straight and follow the path down
RS: Grass Crest Shield - water by Crucifixion Woods bonfireDropping down into the crab area from Crucifixion Woods, on the other side of a tree from the greater crab
RS: Great Club - NPC drop by Farron KeepDropped by the club-wielding Exile Knight before the ladder down to Farron Keep
RS: Great Swamp Pyromancy Tome - deep waterIn the deep water part of the Crucifixion Woods crab area, between a large tree and the keep wall
RS: Great Swamp Ring - miniboss drop, by Farron KeepDropped by Greater Crab in Crucifixion Woods close to the Farron Keep outer wall
RS: Green Blossom - by deep waterIn the Crucifixion Woods crab area out in the open, close to the edge of the deep water area
RS: Green Blossom - water beneath strongholdIn the Crucifixion Woods crab area close to the Crucifixion Woods bonfire, along the left wall of the water area, to the right of the entrance to the building before Crystal Sage
RS: Heretic's Staff - stronghold left roomIn the building before Crystal Sage, entering from near Crucifixion Woods, in a corner under the first stairwell and balcony
RS: Heysel Pick - Heysel dropDropped by Heysel when she invades in Road of Sacrifices
RS: Homeward Bone - balcony by Farron KeepAt the far end of the building where you descend into Farron Keep, by the balcony
RS: Large Soul of an Unknown Traveler - left of stairs to Farron KeepIn the area before you descend into Farron Keep, before the stairs to the far left
RS: Lingering Dragoncrest Ring+1 - waterOn a tree by the greater crab near the Crucifixion Woods bonfire, after the Grass Crest Shield tree
RS: Morne's Ring - drop from bridge to Halfway FortressDropping down before the bridge leading up to Halfway Fortress from Road of Sacrifices, guarded by the maggot belly dog
RS: Ring of Sacrifice - stronghold, drop from right room balconyDrop down from the platform behind the sorcerer in the building before Crystal Sage, entering from the stairs leading up from the crab area
RS: Sage Ring - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sellsword Armor - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Gauntlet - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Helm - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Trousers - keep perimeter balconyIn the Farron Keep Perimeter building on Crucifixion Woods side, on the balcony on the right side overlooking the Black Knight
RS: Sellsword Twinblades - keep perimeterIn the Farron Keep Perimeter building on Crucifixion Woods side, behind and to the right of the Black Knight
RS: Shriving Stone - road, by startDropping down to the left of the first Corvian enemy in Road of Sacrifices
RS: Sorcerer Gloves - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sorcerer Hood - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sorcerer Robe - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Sorcerer Trousers - water beneath strongholdIn an alcove under the building before Crystal Sage, guarded by a Lycanthrope, accessible from the swamp or from dropping down
RS: Soul of a Crystal SageDropped by Crystal Sage
RS: Soul of an Unknown Traveler - drop along wall from Halfway FortressFrom Halfway Fortress, hug the right wall and drop down twice on the way to the crab area
RS: Soul of an Unknown Traveler - right of door to stronghold leftOut in the open to the right of the building before Crystal Sage, as entered from Crucifixion Woods bonfire
RS: Soul of an Unknown Traveler - road, by wagonTo the right of the overturned wagon descending from the Road of Sacrifices bonfire
RS: Titanite Shard - road, on bridge after you go underCrossing the bridge you go under after the first Road of Sacrifices bonfire, after a sleeping Corvian and another Corvian guarding the pickup
RS: Titanite Shard - water by Halfway FortressDropping down into the Crucifixion Woods crab area right after Halfway Fortress, on the left wall heading toward the Black Knight building, guarded by dog
RS: Titanite Shard - woods, left of path from Halfway FortressHugging the left wall from Halfway Fortress to Crystal Sage, behind you after the first dropdown
RS: Titanite Shard - woods, surrounded by enemiesHugging the left wall from Halfway Fortress to the Crystal Sage bonfire, after a dropdown surrounded by seven Poisonhorn bugs
RS: Twin Dragon Greatshield - woods by Crucifixion Woods bonfireIn the middle of the area with the Poisonhorn bugs and Lycanthrope Hunters, following the wall where the bugs guard a Titanite Shard
RS: Xanthous Crown - Heysel dropDropped by Heysel when she invades in Road of Sacrifices
SL: Black Iron Greatshield - ruins basement, NPC dropDropped by Knight Slayer Tsorig in Smouldering Lake
SL: Black Knight Sword - ruins main lower, illusory wall in far hallOn the far exit of the Demon Ruins main hall, past an illusory wall, guarded by a Black Knight
SL: Bloodbite Ring+1 - behind ballistaBehind the ballista, overlooking Smouldering Lake
SL: Chaos Gem - antechamber, lizard at end of long hallDropped by the Crystal Lizard found from the Antechamber bonfire, toward the Demon Cleric and to the right, then all the way down
SL: Chaos Gem - lake, far end by mobIn Smouldering Lake along the wall underneath the ballista, all the way to the left past two crabs
SL: Dragonrider Bow - by ladder from ruins basement to ballistaAfter climbing up the ladder after the Black Knight in Demon Ruins, falling back down to a ledge
SL: Ember - ruins basement, in lavaIn the lava pit under the Black Knight, by Knight Slayer Tsorig
SL: Ember - ruins main lower, path to antechamberGoing down the stairs from the Antechamber bonfire, to the right, at the end of the short hallway to the next right
SL: Ember - ruins main upper, hall end by holeIn the Demon Ruins, hugging the right wall from the Demon Ruins bonfire, or making a jump from the illusory hall corridor from Antechamber bonfire
SL: Ember - ruins main upper, just after entranceBehind the first Demon Cleric from the Demon Ruins bonfire
SL: Estus Shard - antechamber, illusory wallBehind an illusory wall and Smouldering Writhing Flesh-filled corridor from Antechamber bonfire
SL: Flame Stoneplate Ring+2 - ruins main lower, illusory wall in far hallOn the far exit of the Demon Ruins main hall, past an illusory wall, past the Black Knight, hidden in a corner
SL: Fume Ultra Greatsword - ruins basement, NPC dropDropped by Knight Slayer Tsorig in Smouldering Lake
SL: Homeward Bone - path to ballistaIn the area targeted by the ballista after the long ladder guarded by the Black Knight, before the Bonewheel Skeletons
SL: Izalith Pyromancy Tome - antechamber, room near bonfireIn the room straight down from the Antechamber bonfire, past a Demon Cleric, surrounded by many Ghrus.
SL: Izalith Staff - ruins basement, second illusory wall behind chestPast an illusory wall to the left of the Large Hound Rat in Demon Ruins, and then past another illusory wall, before the basilisk area
SL: Knight Slayer's Ring - ruins basement, NPC dropDropped by Knight Slayer Tsorig after invading in the Catacombs
SL: Large Titanite Shard - lake, by entranceIn the middle of Smouldering Lake, close to the Abandoned Tomb
SL: Large Titanite Shard - lake, by minibossIn the middle of Smouldering Lake, under the Carthus Sandworm
SL: Large Titanite Shard - lake, by tree #1In the middle of Smouldering Lake, by a tree before the hallway to the pit
SL: Large Titanite Shard - lake, by tree #2In the middle of Smouldering Lake, by a tree before the hallway to the pit
SL: Large Titanite Shard - lake, straight from entranceIn the middle of Smouldering Lake, in between Abandoned Tomb and Demon Ruins
SL: Large Titanite Shard - ledge by Demon Ruins bonfireOn a corpse hanging off the ledge outside the Demon Ruins bonfire
SL: Large Titanite Shard - ruins basement, illusory wall in upper hallIn a chest past an illusory wall to the left of the Large Hound Rat in Demon Ruins, before the basilisk area
SL: Large Titanite Shard - side lake #1In the Smouldering Lake pit where Horace can be found, following the right wall from Abandoned Tomb
SL: Large Titanite Shard - side lake #2In the Smouldering Lake pit where Horace can be found, following the right wall from Abandoned Tomb
SL: Lightning Stake - lake, miniboss dropDropped by the giant Carthus Sandworm
SL: Llewellyn Shield - Horace dropDropped by Horace the Hushed upon death or quest completion.
SL: Quelana Pyromancy Tome - ruins main lower, illusory wall in grey roomAt the far end of the Demon Ruins main hall to the right, where the rats are, then another right and past the illusory wall
SL: Sacred Flame - ruins basement, in lavaIn the lava pit under the Black Knight, by Knight Slayer Tsorig
SL: Shield of Want - lake, by minibossIn the middle of Smouldering Lake, under the Carthus Sandworm
SL: Soul of a Crestfallen Knight - ruins basement, above lavaNext to the Black Knight in Demon Ruins
SL: Soul of the Old Demon KingDropped by Old Demon King in Smouldering Lake
SL: Speckled Stoneplate Ring - lake, ballista breaks bricksBehind a destructible wall in Smouldering Lake which the ballista has to destroy
SL: Titanite Chunk - path to side lake, lizardDropped by the second Crystal Lizard in the cave leading to the pit where Horace can be found in Smouldering Lake
SL: Titanite Scale - ruins basement, path to lavaIn the area with Basilisks on the way to the ballista
SL: Toxic Mist - ruins main lower, in lavaAt the far end of the Demon Ruins main hall to the right, where the rats are, then another right and past the illusory wall, in the middle of the lava pit.
SL: Twinkling Titanite - path to side lake, lizardDropped by the first Crystal Lizard in the cave leading to the pit where Horace can be found in Smouldering Lake
SL: Undead Bone Shard - lake, miniboss dropDropped by the giant Carthus Sandworm
SL: Undead Bone Shard - ruins main lower, left after stairsIn the close end of the Demon Ruins main hall, right below a Smouldering Writhing Flesh
SL: White Hair Talisman - ruins main lower, in lavaAt the far end of the Demon Ruins main hall to the right, where the rats are, then another right and past the illusory wall, at the far end of the lava pit.
SL: Yellow Bug Pellet - side lakeIn the Smouldering Lake pit where Horace can be found, following the right wall from Abandoned Tomb
UG: Ashen Estus Ring - swamp, path opposite bonfireIn the coffin similar to your initial spawn location, guarded by Corvians
UG: Black Knight Glaive - boss arenaIn the Champion Gundyr boss area
UG: Blacksmith Hammer - shrine, Andre's roomWhere Andre sits in Firelink Shrine
UG: Chaos Blade - environs, left of shrineWhere Sword Master is in Firelink Shrine
UG: Coiled Sword Fragment - shrine, dead bonfireIn the dead Firelink Shrine bonfire
UG: Ember - shopSold by Untended Graves Handmaid
UG: Eyes of a Fire Keeper - shrine, Irina's roomBehind an illusory wall, in the same location Irina sits in Firelink Shrine
UG: Hidden Blessing - cemetery, behind coffinBehind the coffin that had a Titanite Shard in Cemetery of Ash
UG: Hornet Ring - environs, right of main path after killing FK bossOn a cliffside to the right of the main path leading up to dark Firelink Shrine, after Abyss Watchers is defeated.
UG: Life Ring+3 - shrine, behind big throneBehind Prince Lothric's throne
UG: Priestess Ring - shopSold or dropped by Untended Graves Handmaid. Killing her is not recommended
UG: Ring of Steel Protection+1 - environs, behind bell towerBehind Bell Tower to the right
UG: Shriving Stone - swamp, by bonfireAt the very start of the area
UG: Soul of Champion GundyrDropped by Champion Gundyr
UG: Soul of a Crestfallen Knight - environs, above shrine entranceAbove the Firelink Shrine entrance, up the stairs/slope from either left or right of the entrance
UG: Soul of a Crestfallen Knight - swamp, centerClose to where Ashen Estus Flask was in Cemetery of Ash
UG: Titanite Chunk - swamp, left path by fountainIn a path to the left of where Ashen Estus Flask was in Cemetery of Ash
UG: Titanite Chunk - swamp, right path by fountainIn a path to the right of where Ashen Estus Flask was in Cemetery of Ash
UG: Wolf Knight Armor - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
UG: Wolf Knight Gauntlets - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
UG: Wolf Knight Helm - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
UG: Wolf Knight Leggings - shop after killing FK bossSold by Untended Graves Handmaid after defeating Abyss Watchers
US: Alluring Skull - foot, behind carriageGuarded by two dogs after the Foot of the High Wall bonfire
US: Alluring Skull - on the way to tower, behind buildingAfter the ravine bridge leading to Eygon and the Giant's tower, wrapping around the building to the right.
US: Alluring Skull - tower village building, upstairsUp the stairs of the building with Cage Spiders after the Fire Demon, before the dogs
US: Bloodbite Ring - miniboss in sewerDropped by the large rat in the sewers with grave access
US: Blue Wooden Shield - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Caduceus Round Shield - right after stable exitAfter exiting the building across the bridge to the right of the first Undead Settlement building, to the left
US: Caestus - sewerIn the tunnel with the Giant Hound Rat and Grave Key door, from the ravine bridge toward Dilapidated Bridge bonfire
US: Charcoal Pine Bundle - first building, bottom floorDown the stairs in the first building
US: Charcoal Pine Bundle - first building, middle floorOn the bottom floor of the first building
US: Charcoal Pine Resin - hanging corpse roomIn the building after the burning tree and Cathedral Evangelist, in the room with the many hanging corpses
US: Chloranthy Ring - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Cleric Blue Robe - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cleric Gloves - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cleric Hat - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cleric Trousers - graveyard by white treeAfter Dilapidated Bridge bonfire, in the back of the Giant's arrow area. Guarded by a flamberge-wielding thrall.
US: Cornyx's Garb - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Cornyx's Garb - kill CornyxDropped by Cornyx
US: Cornyx's Skirt - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Cornyx's Skirt - kill CornyxDropped by Cornyx
US: Cornyx's Wrap - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Cornyx's Wrap - kill CornyxDropped by Cornyx
US: Covetous Silver Serpent Ring+2 - tower village, drop down from roofAt the back of a roof near the end of the Fire Demon loop, dropping down past where Flynn's Ring is
US: Ember - behind burning treeBehind the burning tree with the Cathedral Evangelist
US: Ember - bridge on the way to towerOn the ravine bridge leading toward Eygon and the Giant's tower
US: Ember - by stairs to bossNext to the stairs leading up to Curse-Rotted Greatwood fight, near a tree guarded by a dog
US: Ember - by white treeNear the Birch Tree where giant shoots arrows
US: Ember - tower basement, minibossIn the room with the Outrider Knight
US: Estus Shard - under burning treeIn front of the burning tree guarded by the Cathedral Evangelist
US: Fading Soul - by white treeNear the Birch Tree where giant shoots arrows
US: Fading Soul - outside stableIn the thrall area to the right of the bridge to the right of the burning tree with the Cathedral Evangelist
US: Fire Clutch Ring - wooden walkway past stableFrom the area bombarded by firebombs above the Cliff Underside bonfire
US: Fire Gem - tower village, miniboss dropDropped by the Fire Demon you fight with Siegward
US: Firebomb - stable roofIn the thrall area across the bridge from the first Undead Settlement building, on a rooftop overlooking the Cliff Underside area.
US: Flame Stoneplate Ring - hanging corpse by Mound-Maker transportOn a hanging corpse in the area with the Pit of Hollows cage manservant, after the thrall area, overlooking the entrance to the Giant's tower.
US: Flynn's Ring - tower village, rooftopOn the roof toward the end of the Fire Demon loop, past the Cathedral Evangelists
US: Great Scythe - building by white tree, balconyOn the balcony of the building before Curse-Rotted Greatwood, coming from Dilapidated Bridge bonfire
US: Hand Axe - by CornyxNext to Cornyx's cell
US: Hawk Ring - Giant ArcherDropped by Giant, either by killing him or collecting all of the birch tree items locations in the base game.
US: Heavy Gem - HawkwoodGiven or dropped by Hawkwood after defeating Curse-Rotted Greatwood or Crystal Sage
US: Heavy Gem - chasm, lizardDrop by Crystal Lizard in ravine accessible by Grave Key or dropping down near Eygon.
US: Homeward Bone - foot, drop overlookUnder Foot of the High Wall bonfire, around where Yoel can be first met
US: Homeward Bone - stable roofIn the thrall area across the bridge from the first Undead Settlement building, on a roof overlooking the ravine bridge.
US: Homeward Bone - tower village, jump from roofAt the end of the loop from the Siegward Demon fight, after dropping down from the roof onto the tower with Chloranthy Ring, to the right of the tower entrance
US: Homeward Bone - tower village, right at startUnder Foot of the High Wall bonfire, around where Yoel can be first met
US: Human Pine Resin - tower village building, chest upstairsIn a chest after Fire Demon. Cage Spiders activate open opening it.
US: Irithyll Straight Sword - miniboss drop, by Road of SacrificesDropped by the Boreal Outright Knight before Road of Sacrifices
US: Kukri - hanging corpse above burning treeHanging corpse high above the burning tree with the Cathedral Evangelist. Must be shot down with an arrow or projective.
US: Large Club - tower village, by minibossIn the Fire Demon area
US: Large Soul of a Deserted Corpse - across from Foot of the High WallOn the opposite tower from the Foot of the High Wall bonfire
US: Large Soul of a Deserted Corpse - around corner by Cliff UndersideAfter going up the stairs from Curse-Rotted Greatwood to Cliff Underside area, on a cliff edge to the right
US: Large Soul of a Deserted Corpse - by white treeNear the Birch Tree where giant shoots arrows
US: Large Soul of a Deserted Corpse - hanging corpse room, over stairsOn a hanging corpse in the building after the burning tree. Can be knocked down by dropping onto the stairs through the broken railing.
US: Large Soul of a Deserted Corpse - on the way to tower, by wellAfter the ravine bridge leading toward Eygon and the Giant's tower, next to the well to the right
US: Large Soul of a Deserted Corpse - stableIn the building with stables across the bridge and to the right from the first Undead Settlement building
US: Life Ring+1 - tower on the way to villageOn the wooden rafters near where Siegward is waiting for Fire Demon
US: Loincloth - by Velka statueNext to the Velka statue. Requires Grave Key or dropping down near Eygon and backtracking through the skeleton area.
US: Loretta's Bone - first building, hanging corpse on balconyOn a hanging corpse after the first building, can be knocked down by rolling into it
US: Mirrah Gloves - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Mirrah Trousers - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Mirrah Vest - tower village, jump from roofAt the end of the Fire Demon loop, in the tower where you have to drop down after the roof
US: Mortician's Ashes - graveyard by white treeIn the area past the Dilapidated Bridge bonfire, where the Giant is shooting arrows, at the close end of the graveyard
US: Mound-makers - HodrickGiven by Hodrick if accessing the Pit of Hollows before fighting Curse-Rotted Greatwood, or dropped after invading him with Sirris.
US: Northern Armor - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Northern Gloves - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Northern Helm - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Northern Trousers - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Old Sage's Blindfold - kill CornyxDropped by Cornyx
US: Pale Tongue - tower village, hanging corpseHanging corpse in the Fire Demon fight area, can be knocked down by rolling into it
US: Partizan - hanging corpse above Cliff UndersideOn a hanging corpse on the path from Cliff Underside to Cornyx's cage. Must be shot down with an arrow or projective.
US: Plank Shield - outside stable, by NPCIn the thrall area across the bridge from the first Undead Settlement building, on a cliff edge overlooking the ravine bridge.
US: Poisonbite Ring+1 - graveyard by white tree, near wellBehind the well in the back of area where the Giant shoots arrows, nearby where the flamberge-wielding thrall drops down.
US: Pyromancy Flame - CornyxGiven by Cornyx in Firelink Shrine or dropped.
US: Red Bug Pellet - tower village building, basementOn the floor of the building after the Fire Demon encounter
US: Red Hilted Halberd - chasm cryptIn the skeleton area accessible from Grave Key or dropping down from near Eygon
US: Red and White Shield - chasm, hanging corpseOn a hanging corpse in the ravine accessible with the Grave Key or dropping down near Eygon, to the entrance of Irina's prison. Must be shot down with an arrow or projective.
US: Reinforced Club - by white treeNear the Birch Tree where giant shoots arrows
US: Repair Powder - first building, balconyOn the balcony of the first Undead Settlement building
US: Rusted Coin - awning above Dilapidated BridgeOn a wooden ledge near the Dilapidated Bridge bonfire. Must be jumped to from near Cathedral Evangelist enemy
US: Saint's Talisman - chasm, by ladderFrom the ravine accessible via Grave Key or dropping near Eygon, before ladder leading up to Irina of Carim
US: Sharp Gem - lizard by Dilapidated BridgeDrop by Crystal Lizard near Dilapidated Bridge bonfire.
US: Siegbräu - SiegwardGiven by Siegward after helping him defeat the Fire Demon.
US: Small Leather Shield - first building, hanging corpse by entranceHanging corpse in the first building, to the right of the entrance
US: Soul of a Nameless Soldier - top of towerAt the top of the tower where Giant shoots arrows
US: Soul of an Unknown Traveler - back alley, past cratesAfter exiting the building after the burning tree on the way to the Dilapidated Bridge bonfire. Hidden behind some crates between two buildings on the right.
US: Soul of an Unknown Traveler - chasm cryptIn the skeleton area accessible Grave Key or dropping down from near Eygon
US: Soul of an Unknown Traveler - pillory past stableIn the area bombarded by firebombs above the Cliff Underside bonfire
US: Soul of an Unknown Traveler - portcullis by burning treeBehind a grate to the left of the burning tree and Cathedral Evangelist
US: Soul of the Rotted GreatwoodDropped by Curse Rotted Greatwood
US: Spotted Whip - by Cornyx's cage after Cuculus questAppears next to Cornyx's cage after defeating Old Demon King with Cuculus surviving
US: Sunset Armor - pit of hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Sunset Gauntlets - pit of hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Sunset Helm - Pit of Hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Sunset Leggings - pit of hollows after killing Hodrick w/SirrisFound in Pit of Hollows after completing Sirris' questline.
US: Titanite Shard - back alley, side pathOn a side path to the right of the Cathedral Evangelist before the Dilapidated Bridge bonfire
US: Titanite Shard - back alley, up ladderNext to the Cathedral Evangelist close to the Dilapidated Bridge bonfire
US: Titanite Shard - chasm #1In the ravine accessible from Grave Key or dropping down from near Eygon
US: Titanite Shard - chasm #2In the ravine accessible from Grave Key or dropping down from near Eygon
US: Titanite Shard - lower path to Cliff UndersideAt the end of the cliffside path next to Cliff Underside bonfire, guarded by a Hollow Peasant wielding a four-pronged plow.
US: Titanite Shard - porch after burning treeIn front of the building after the burning tree and Cathedral Evangelist
US: Tower Key - kill IrinaDropped by Irina of Carim
US: Transposing Kiln - boss dropDropped by Curse Rotted Greatwood
US: Undead Bone Shard - by white treeIn the area past the Dilapidated Bridge bonfire, where the Giant is shooting arrows, jumping to the floating platform on the right
US: Wargod Wooden Shield - Pit of HollowsIn the Pit of Hollows
US: Warrior of Sunlight - hanging corpse room, drop through holeDropping through a hole in the floor in the first building after the burning tree.
US: Whip - back alley, behind wooden wallIn one of the houses between building after the burning tree and the Dilapidated Bridge bonfire
US: Young White Branch - by white tree #1Near the Birch Tree where giant shoots arrows
US: Young White Branch - by white tree #2Near the Birch Tree where giant shoots arrows
+ diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index 61215dbc604..ed90289a8ba 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -7,48 +7,49 @@ ## Optional Software -- [Dark Souls III Maptracker Pack](https://github.com/Br00ty/DS3_AP_Maptracker/releases/latest), for use with [Poptracker](https://github.com/black-sliver/PopTracker/releases) +- Map tracker not yet updated for 3.0.0 -## General Concept +## Setting Up - -**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 -prompt where you can read information about your run and write any command to interact with the Archipelago server. +First, download the client from the link above. It doesn't need to go into any particular directory; +it'll automatically locate _Dark Souls III_ in your Steam installation folder. -This client has only been tested with the Official Steam version of the game at version 1.15. It does not matter which DLCs are installed. However, you will have to downpatch your Dark Souls III installation from current patch. +Version 3.0.0 of the randomizer _only_ supports the latest version of _Dark Souls III_, 1.15.2. This +is the latest version, so you don't need to do any downpatching! However, if you've already +downpatched your game to use an older version of the randomizer, you'll need to reinstall the latest +version before using this version. -## Downpatching Dark Souls III +### One-Time Setup -To downpatch DS3 for use with Archipelago, use the following instructions from the speedsouls wiki database. +Before you first connect to a multiworld, you need to generate the local data files for your world's +randomized item and (optionally) enemy locations. You only need to do this once per multiworld. -1. Launch Steam (in online mode). -2. Press the Windows Key + R. This will open the Run window. -3. Open the Steam console by typing the following string: `steam://open/console`. Steam should now open in Console Mode. -4. Insert the string of the depot you wish to download. For the AP-supported v1.15, you will want to use: `download_depot 374320 374321 4471176929659548333`. -5. Steam will now download the depot. Note: There is no progress bar for the download in Steam, but it is still downloading in the background. -6. Back up your existing game executable (`DarkSoulsIII.exe`) found in `\Steam\steamapps\common\DARK SOULS III\Game`. Easiest way to do this is to move it to another directory. If you have file extensions enabled, you can instead rename the executable to `DarkSoulsIII.exe.bak`. -7. Return to the Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like `\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX`. -8. Take the `DarkSoulsIII.exe` from that folder and place it in `\Steam\steamapps\common\DARK SOULS III\Game`. -9. Back up and delete your save file (`DS30000.sl2`) in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type `%appdata%` and hit enter. Alternatively: open File Explorer > View > Hidden Items and follow `C:\Users\\AppData\Roaming\DarkSoulsIII\`. -10. If you did all these steps correctly, you should be able to confirm your game version in the upper-left corner after launching Dark Souls III. +1. Before you first connect to a multiworld, run `randomizer\DS3Randomizer.exe`. +2. Put in your Archipelago room address (usually something like `archipelago.gg:12345`), your player + name (also known as your "slot name"), and your password if you have one. -## Installing the Archipelago mod +3. Click "Load" and wait a minute or two. -Get the `dinput8.dll` from the [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases) and -add it at the root folder of your game (e.g. `SteamLibrary\steamapps\common\DARK SOULS III\Game`) +### Running and Connecting the Game -## Joining a MultiWorld Game +To run _Dark Souls III_ in Archipelago mode: -1. Run Steam in offline mode to avoid being banned. -2. Launch Dark Souls III. -3. Type in `/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME} password:{PASSWORD}` in the "Windows Command Prompt" that opened. For example: `/connect archipelago.gg:38281 "Example Name" password:"Example Password"`. The password parameter is only necessary if your game requires one. -4. Once connected, create a new game, choose a class and wait for the others before starting. -5. You can quit and launch at anytime during a game. +1. Start Steam. **Do not run in offline mode.** The mod will make sure you don't connect to the + DS3 servers, and running Steam in offline mode will make certain scripted invaders fail to spawn. -## Where do I get a config file? +2. Run `launchmod_darksouls3.bat`. This will start _Dark Souls III_ as well as a command prompt that + you can use to interact with the Archipelago server. + +3. Type `/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME}` into the command prompt, with the + appropriate values filled in. For example: `/connect archipelago.gg:24242 PlayerName`. + +4. Start playing as normal. An "Archipelago connected" message will appear onscreen once you have + control of your character and the connection is established. + +## Frequently Asked Questions + +### Where do I get a config file? The [Player Options](/games/Dark%20Souls%20III/player-options) page on the website allows you to configure your personal options and export them into a config file. diff --git a/worlds/dark_souls_3/test/TestDarkSouls3.py b/worlds/dark_souls_3/test/TestDarkSouls3.py new file mode 100644 index 00000000000..e590cd732b4 --- /dev/null +++ b/worlds/dark_souls_3/test/TestDarkSouls3.py @@ -0,0 +1,27 @@ +from test.TestBase import WorldTestBase + +from worlds.dark_souls_3.Items import item_dictionary +from worlds.dark_souls_3.Locations import location_tables +from worlds.dark_souls_3.Bosses import all_bosses + +class DarkSouls3Test(WorldTestBase): + game = "Dark Souls III" + + def testLocationDefaultItems(self): + for locations in location_tables.values(): + for location in locations: + if location.default_item_name: + self.assertIn(location.default_item_name, item_dictionary) + + def testLocationsUnique(self): + names = set() + for locations in location_tables.values(): + for location in locations: + self.assertNotIn(location.name, names) + names.add(location.name) + + def testBossLocations(self): + all_locations = {location.name for locations in location_tables.values() for location in locations} + for boss in all_bosses: + for location in boss.locations: + self.assertIn(location, all_locations) diff --git a/worlds/dark_souls_3/test/__init__.py b/worlds/dark_souls_3/test/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py index a9dfcc5044b..b8f2aad6ff9 100644 --- a/worlds/dlcquest/__init__.py +++ b/worlds/dlcquest/__init__.py @@ -8,11 +8,15 @@ from .Options import DLCQuestOptions from .Regions import create_regions from .Rules import set_rules +from .presets import dlcq_options_presets +from .option_groups import dlcq_option_groups client_version = 0 class DLCqwebworld(WebWorld): + options_presets = dlcq_options_presets + option_groups = dlcq_option_groups setup_en = Tutorial( "Multiworld Setup Guide", "A guide to setting up the Archipelago DLCQuest game on your computer.", diff --git a/worlds/dlcquest/option_groups.py b/worlds/dlcquest/option_groups.py new file mode 100644 index 00000000000..9510c061e18 --- /dev/null +++ b/worlds/dlcquest/option_groups.py @@ -0,0 +1,27 @@ +from typing import List + +from Options import ProgressionBalancing, Accessibility, OptionGroup +from .Options import (Campaign, ItemShuffle, TimeIsMoney, EndingChoice, PermanentCoins, DoubleJumpGlitch, CoinSanity, + CoinSanityRange, DeathLink) + +dlcq_option_groups: List[OptionGroup] = [ + OptionGroup("General", [ + Campaign, + ItemShuffle, + CoinSanity, + ]), + OptionGroup("Customization", [ + EndingChoice, + PermanentCoins, + CoinSanityRange, + ]), + OptionGroup("Tedious and Grind", [ + TimeIsMoney, + DoubleJumpGlitch, + ]), + OptionGroup("Advanced Options", [ + DeathLink, + ProgressionBalancing, + Accessibility, + ]), +] diff --git a/worlds/dlcquest/presets.py b/worlds/dlcquest/presets.py new file mode 100644 index 00000000000..ccfd7939952 --- /dev/null +++ b/worlds/dlcquest/presets.py @@ -0,0 +1,68 @@ +from typing import Any, Dict + +from .Options import DoubleJumpGlitch, CoinSanity, CoinSanityRange, PermanentCoins, TimeIsMoney, EndingChoice, Campaign, ItemShuffle + +all_random_settings = { + DoubleJumpGlitch.internal_name: "random", + CoinSanity.internal_name: "random", + CoinSanityRange.internal_name: "random", + PermanentCoins.internal_name: "random", + TimeIsMoney.internal_name: "random", + EndingChoice.internal_name: "random", + Campaign.internal_name: "random", + ItemShuffle.internal_name: "random", + "death_link": "random", +} + +main_campaign_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none, + CoinSanity.internal_name: CoinSanity.option_coin, + CoinSanityRange.internal_name: 30, + PermanentCoins.internal_name: PermanentCoins.option_false, + TimeIsMoney.internal_name: TimeIsMoney.option_required, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_basic, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + +lfod_campaign_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none, + CoinSanity.internal_name: CoinSanity.option_coin, + CoinSanityRange.internal_name: 30, + PermanentCoins.internal_name: PermanentCoins.option_false, + TimeIsMoney.internal_name: TimeIsMoney.option_required, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_live_freemium_or_die, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + +easy_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none, + CoinSanity.internal_name: CoinSanity.option_none, + CoinSanityRange.internal_name: 40, + PermanentCoins.internal_name: PermanentCoins.option_true, + TimeIsMoney.internal_name: TimeIsMoney.option_required, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_both, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + +hard_settings = { + DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_simple, + CoinSanity.internal_name: CoinSanity.option_coin, + CoinSanityRange.internal_name: 30, + PermanentCoins.internal_name: PermanentCoins.option_false, + TimeIsMoney.internal_name: TimeIsMoney.option_optional, + EndingChoice.internal_name: EndingChoice.option_true, + Campaign.internal_name: Campaign.option_both, + ItemShuffle.internal_name: ItemShuffle.option_shuffled, +} + + +dlcq_options_presets: Dict[str, Dict[str, Any]] = { + "All random": all_random_settings, + "Main campaign": main_campaign_settings, + "LFOD campaign": lfod_campaign_settings, + "Both easy": easy_settings, + "Both hard": hard_settings, +} diff --git a/worlds/doom_1993/docs/setup_en.md b/worlds/doom_1993/docs/setup_en.md index 8906efac9ce..5d96e6a8056 100644 --- a/worlds/doom_1993/docs/setup_en.md +++ b/worlds/doom_1993/docs/setup_en.md @@ -2,7 +2,7 @@ ## Required Software -- [DOOM 1993 (e.g. Steam version)](https://store.steampowered.com/app/2280/DOOM_1993/) +- [DOOM 1993 (e.g. Steam version)](https://store.steampowered.com/app/2280/DOOM__DOOM_II/) - [Archipelago Crispy DOOM](https://github.com/Daivuk/apdoom/releases) ## Optional Software diff --git a/worlds/doom_ii/docs/setup_en.md b/worlds/doom_ii/docs/setup_en.md index 87054ab3078..ec6697c76da 100644 --- a/worlds/doom_ii/docs/setup_en.md +++ b/worlds/doom_ii/docs/setup_en.md @@ -2,7 +2,7 @@ ## Required Software -- [DOOM II (e.g. Steam version)](https://store.steampowered.com/app/2300/DOOM_II/) +- [DOOM II (e.g. Steam version)](https://store.steampowered.com/app/2280/DOOM__DOOM_II/) - [Archipelago Crispy DOOM](https://github.com/Daivuk/apdoom/releases) ## Optional Software diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua index 8ce0b45a5f6..ace231e12b4 100644 --- a/worlds/factorio/data/mod_template/control.lua +++ b/worlds/factorio/data/mod_template/control.lua @@ -660,11 +660,18 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi end local tech local force = game.forces["player"] + if call.parameter == nil then + game.print("ap-get-technology is only to be used by the Archipelago Factorio Client") + return + end chunks = split(call.parameter, "\t") local item_name = chunks[1] local index = chunks[2] local source = chunks[3] or "Archipelago" - if index == -1 then -- for coop sync and restoring from an older savegame + if index == nil then + game.print("ap-get-technology is only to be used by the Archipelago Factorio Client") + return + elseif index == -1 then -- for coop sync and restoring from an older savegame tech = force.technologies[item_name] if tech.researched ~= true then game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."}) diff --git a/worlds/ffmq/Client.py b/worlds/ffmq/Client.py index 7de486314c6..93688a6116f 100644 --- a/worlds/ffmq/Client.py +++ b/worlds/ffmq/Client.py @@ -71,7 +71,7 @@ async def game_watcher(self, ctx): received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1]) data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START) check_2 = await snes_read(ctx, 0xF53749, 1) - if check_1 in (b'\x00', b'\x55') or check_2 in (b'\x00', b'\x55'): + if check_1 != b'\x01' or check_2 != b'\x01': return def get_range(data_range): diff --git a/worlds/ffmq/Items.py b/worlds/ffmq/Items.py index d0898d7e81c..f1c102d34ef 100644 --- a/worlds/ffmq/Items.py +++ b/worlds/ffmq/Items.py @@ -222,10 +222,10 @@ def yaml_item(text): def create_items(self) -> None: items = [] - starting_weapon = self.multiworld.starting_weapon[self.player].current_key.title().replace("_", " ") + starting_weapon = self.options.starting_weapon.current_key.title().replace("_", " ") self.multiworld.push_precollected(self.create_item(starting_weapon)) self.multiworld.push_precollected(self.create_item("Steel Armor")) - if self.multiworld.sky_coin_mode[self.player] == "start_with": + if self.options.sky_coin_mode == "start_with": self.multiworld.push_precollected(self.create_item("Sky Coin")) precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]} @@ -233,28 +233,28 @@ def create_items(self) -> None: def add_item(item_name): if item_name in ["Steel Armor", "Sky Fragment"] or "Progressive" in item_name: return - if item_name.lower().replace(" ", "_") == self.multiworld.starting_weapon[self.player].current_key: + if item_name.lower().replace(" ", "_") == self.options.starting_weapon.current_key: return - if self.multiworld.progressive_gear[self.player]: + if self.options.progressive_gear: for item_group in prog_map: if item_name in self.item_name_groups[item_group]: item_name = prog_map[item_group] break if item_name == "Sky Coin": - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + if self.options.sky_coin_mode == "shattered_sky_coin": for _ in range(40): items.append(self.create_item("Sky Fragment")) return - elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": + elif self.options.sky_coin_mode == "save_the_crystals": items.append(self.create_filler()) return if item_name in precollected_item_names: items.append(self.create_filler()) return i = self.create_item(item_name) - if self.multiworld.logic[self.player] != "friendly" and item_name in ("Magic Mirror", "Mask"): + if self.options.logic != "friendly" and item_name in ("Magic Mirror", "Mask"): i.classification = ItemClassification.useful - if (self.multiworld.logic[self.player] == "expert" and self.multiworld.map_shuffle[self.player] == "none" and + if (self.options.logic == "expert" and self.options.map_shuffle == "none" and item_name == "Exit Book"): i.classification = ItemClassification.progression items.append(i) @@ -263,11 +263,11 @@ def add_item(item_name): for item in self.item_name_groups[item_group]: add_item(item) - if self.multiworld.brown_boxes[self.player] == "include": + if self.options.brown_boxes == "include": filler_items = [] for item, count in fillers.items(): filler_items += [self.create_item(item) for _ in range(count)] - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + if self.options.sky_coin_mode == "shattered_sky_coin": self.multiworld.random.shuffle(filler_items) filler_items = filler_items[39:] items += filler_items diff --git a/worlds/ffmq/Options.py b/worlds/ffmq/Options.py index af3625f28a9..41c397315f8 100644 --- a/worlds/ffmq/Options.py +++ b/worlds/ffmq/Options.py @@ -1,4 +1,5 @@ -from Options import Choice, FreeText, Toggle, Range +from Options import Choice, FreeText, Toggle, Range, PerGameCommonOptions +from dataclasses import dataclass class Logic(Choice): @@ -321,36 +322,36 @@ class KaelisMomFightsMinotaur(Toggle): default = 0 -option_definitions = { - "logic": Logic, - "brown_boxes": BrownBoxes, - "sky_coin_mode": SkyCoinMode, - "shattered_sky_coin_quantity": ShatteredSkyCoinQuantity, - "starting_weapon": StartingWeapon, - "progressive_gear": ProgressiveGear, - "leveling_curve": LevelingCurve, - "starting_companion": StartingCompanion, - "available_companions": AvailableCompanions, - "companions_locations": CompanionsLocations, - "kaelis_mom_fight_minotaur": KaelisMomFightsMinotaur, - "companion_leveling_type": CompanionLevelingType, - "companion_spellbook_type": CompanionSpellbookType, - "enemies_density": EnemiesDensity, - "enemies_scaling_lower": EnemiesScalingLower, - "enemies_scaling_upper": EnemiesScalingUpper, - "bosses_scaling_lower": BossesScalingLower, - "bosses_scaling_upper": BossesScalingUpper, - "enemizer_attacks": EnemizerAttacks, - "enemizer_groups": EnemizerGroups, - "shuffle_res_weak_types": ShuffleResWeakType, - "shuffle_enemies_position": ShuffleEnemiesPositions, - "progressive_formations": ProgressiveFormations, - "doom_castle_mode": DoomCastle, - "doom_castle_shortcut": DoomCastleShortcut, - "tweak_frustrating_dungeons": TweakFrustratingDungeons, - "map_shuffle": MapShuffle, - "crest_shuffle": CrestShuffle, - "shuffle_battlefield_rewards": ShuffleBattlefieldRewards, - "map_shuffle_seed": MapShuffleSeed, - "battlefields_battles_quantities": BattlefieldsBattlesQuantities, -} +@dataclass +class FFMQOptions(PerGameCommonOptions): + logic: Logic + brown_boxes: BrownBoxes + sky_coin_mode: SkyCoinMode + shattered_sky_coin_quantity: ShatteredSkyCoinQuantity + starting_weapon: StartingWeapon + progressive_gear: ProgressiveGear + leveling_curve: LevelingCurve + starting_companion: StartingCompanion + available_companions: AvailableCompanions + companions_locations: CompanionsLocations + kaelis_mom_fight_minotaur: KaelisMomFightsMinotaur + companion_leveling_type: CompanionLevelingType + companion_spellbook_type: CompanionSpellbookType + enemies_density: EnemiesDensity + enemies_scaling_lower: EnemiesScalingLower + enemies_scaling_upper: EnemiesScalingUpper + bosses_scaling_lower: BossesScalingLower + bosses_scaling_upper: BossesScalingUpper + enemizer_attacks: EnemizerAttacks + enemizer_groups: EnemizerGroups + shuffle_res_weak_types: ShuffleResWeakType + shuffle_enemies_position: ShuffleEnemiesPositions + progressive_formations: ProgressiveFormations + doom_castle_mode: DoomCastle + doom_castle_shortcut: DoomCastleShortcut + tweak_frustrating_dungeons: TweakFrustratingDungeons + map_shuffle: MapShuffle + crest_shuffle: CrestShuffle + shuffle_battlefield_rewards: ShuffleBattlefieldRewards + map_shuffle_seed: MapShuffleSeed + battlefields_battles_quantities: BattlefieldsBattlesQuantities diff --git a/worlds/ffmq/Output.py b/worlds/ffmq/Output.py index 1b17aaa98f2..1e436a90c5f 100644 --- a/worlds/ffmq/Output.py +++ b/worlds/ffmq/Output.py @@ -1,13 +1,13 @@ import yaml import os import zipfile +import Utils from copy import deepcopy from .Regions import object_id_table -from Utils import __version__ from worlds.Files import APPatch import pkgutil -settings_template = yaml.load(pkgutil.get_data(__name__, "data/settings.yaml"), yaml.Loader) +settings_template = Utils.parse_yaml(pkgutil.get_data(__name__, "data/settings.yaml")) def generate_output(self, output_directory): @@ -21,7 +21,7 @@ def output_item_name(item): item_name = "".join(item_name.split(" ")) else: if item.advancement or item.useful or (item.trap and - self.multiworld.per_slot_randoms[self.player].randint(0, 1)): + self.random.randint(0, 1)): item_name = "APItem" else: item_name = "APItemFiller" @@ -46,60 +46,60 @@ def tf(option): options = deepcopy(settings_template) options["name"] = self.multiworld.player_name[self.player] option_writes = { - "enemies_density": cc(self.multiworld.enemies_density[self.player]), + "enemies_density": cc(self.options.enemies_density), "chests_shuffle": "Include", - "shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle", + "shuffle_boxes_content": self.options.brown_boxes == "shuffle", "npcs_shuffle": "Include", "battlefields_shuffle": "Include", - "logic_options": cc(self.multiworld.logic[self.player]), - "shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]), - "enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]), - "enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]), - "bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]), - "bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]), - "enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]), - "leveling_curve": cc(self.multiworld.leveling_curve[self.player]), - "battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if - self.multiworld.battlefields_battles_quantities[self.player].value < 5 else + "logic_options": cc(self.options.logic), + "shuffle_enemies_position": tf(self.options.shuffle_enemies_position), + "enemies_scaling_lower": cc(self.options.enemies_scaling_lower), + "enemies_scaling_upper": cc(self.options.enemies_scaling_upper), + "bosses_scaling_lower": cc(self.options.bosses_scaling_lower), + "bosses_scaling_upper": cc(self.options.bosses_scaling_upper), + "enemizer_attacks": cc(self.options.enemizer_attacks), + "leveling_curve": cc(self.options.leveling_curve), + "battles_quantity": cc(self.options.battlefields_battles_quantities) if + self.options.battlefields_battles_quantities.value < 5 else "RandomLow" if - self.multiworld.battlefields_battles_quantities[self.player].value == 5 else + self.options.battlefields_battles_quantities.value == 5 else "RandomHigh", - "shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]), + "shuffle_battlefield_rewards": tf(self.options.shuffle_battlefield_rewards), "random_starting_weapon": True, - "progressive_gear": tf(self.multiworld.progressive_gear[self.player]), - "tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]), - "doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]), - "doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]), - "sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]), - "sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]), + "progressive_gear": tf(self.options.progressive_gear), + "tweaked_dungeons": tf(self.options.tweak_frustrating_dungeons), + "doom_castle_mode": cc(self.options.doom_castle_mode), + "doom_castle_shortcut": tf(self.options.doom_castle_shortcut), + "sky_coin_mode": cc(self.options.sky_coin_mode), + "sky_coin_fragments_qty": cc(self.options.shattered_sky_coin_quantity), "enable_spoilers": False, - "progressive_formations": cc(self.multiworld.progressive_formations[self.player]), - "map_shuffling": cc(self.multiworld.map_shuffle[self.player]), - "crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]), - "enemizer_groups": cc(self.multiworld.enemizer_groups[self.player]), - "shuffle_res_weak_type": tf(self.multiworld.shuffle_res_weak_types[self.player]), - "companion_leveling_type": cc(self.multiworld.companion_leveling_type[self.player]), - "companion_spellbook_type": cc(self.multiworld.companion_spellbook_type[self.player]), - "starting_companion": cc(self.multiworld.starting_companion[self.player]), + "progressive_formations": cc(self.options.progressive_formations), + "map_shuffling": cc(self.options.map_shuffle), + "crest_shuffle": tf(self.options.crest_shuffle), + "enemizer_groups": cc(self.options.enemizer_groups), + "shuffle_res_weak_type": tf(self.options.shuffle_res_weak_types), + "companion_leveling_type": cc(self.options.companion_leveling_type), + "companion_spellbook_type": cc(self.options.companion_spellbook_type), + "starting_companion": cc(self.options.starting_companion), "available_companions": ["Zero", "One", "Two", - "Three", "Four"][self.multiworld.available_companions[self.player].value], - "companions_locations": cc(self.multiworld.companions_locations[self.player]), - "kaelis_mom_fight_minotaur": tf(self.multiworld.kaelis_mom_fight_minotaur[self.player]), + "Three", "Four"][self.options.available_companions.value], + "companions_locations": cc(self.options.companions_locations), + "kaelis_mom_fight_minotaur": tf(self.options.kaelis_mom_fight_minotaur), } for option, data in option_writes.items(): options["Final Fantasy Mystic Quest"][option][data] = 1 - rom_name = f'MQ{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21] + rom_name = f'MQ{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21] self.rom_name = bytearray(rom_name, 'utf8') self.rom_name_available_event.set() setup = {"version": "1.5", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed": - hex(self.multiworld.per_slot_randoms[self.player].randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} + hex(self.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]] - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + if self.options.sky_coin_mode == "shattered_sky_coin": starting_items.append("SkyCoin") file_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.apmq") diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py index 8b83c88e72c..c1d3d619ffa 100644 --- a/worlds/ffmq/Regions.py +++ b/worlds/ffmq/Regions.py @@ -1,11 +1,9 @@ from BaseClasses import Region, MultiWorld, Entrance, Location, LocationProgressType, ItemClassification from worlds.generic.Rules import add_rule +from .data.rooms import rooms, entrances from .Items import item_groups, yaml_item -import pkgutil -import yaml -rooms = yaml.load(pkgutil.get_data(__name__, "data/rooms.yaml"), yaml.Loader) -entrance_names = {entrance["id"]: entrance["name"] for entrance in yaml.load(pkgutil.get_data(__name__, "data/entrances.yaml"), yaml.Loader)} +entrance_names = {entrance["id"]: entrance["name"] for entrance in entrances} object_id_table = {} object_type_table = {} @@ -69,7 +67,7 @@ def create_regions(self): location_table else None, object["type"], object["access"], self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for object in room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in ("BattlefieldGp", - "BattlefieldXp") and (object["type"] != "Box" or self.multiworld.brown_boxes[self.player] == "include") and + "BattlefieldXp") and (object["type"] != "Box" or self.options.brown_boxes == "include") and not (object["name"] == "Kaeli Companion" and not object["on_trigger"])], room["links"])) dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player) @@ -91,15 +89,13 @@ def create_regions(self): if "entrance" in link and link["entrance"] != -1: spoiler = False if link["entrance"] in crest_warps: - if self.multiworld.crest_shuffle[self.player]: + if self.options.crest_shuffle: spoiler = True - elif self.multiworld.map_shuffle[self.player] == "everything": + elif self.options.map_shuffle == "everything": spoiler = True - elif "Subregion" in region.name and self.multiworld.map_shuffle[self.player] not in ("dungeons", - "none"): + elif "Subregion" in region.name and self.options.map_shuffle not in ("dungeons", "none"): spoiler = True - elif "Subregion" not in region.name and self.multiworld.map_shuffle[self.player] not in ("none", - "overworld"): + elif "Subregion" not in region.name and self.options.map_shuffle not in ("none", "overworld"): spoiler = True if spoiler: @@ -111,6 +107,7 @@ def create_regions(self): connection.connect(connect_room) break + non_dead_end_crest_rooms = [ 'Libra Temple', 'Aquaria Gemini Room', "GrenadeMan's Mobius Room", 'Fireburg Gemini Room', 'Sealed Temple', 'Alive Forest', 'Kaidge Temple Upper Ledge', @@ -140,7 +137,7 @@ def hard_boss_logic(state): add_rule(self.multiworld.get_location("Gidrah", self.player), hard_boss_logic) add_rule(self.multiworld.get_location("Dullahan", self.player), hard_boss_logic) - if self.multiworld.map_shuffle[self.player]: + if self.options.map_shuffle: for boss in ("Freezer Crab", "Ice Golem", "Jinn", "Medusa", "Dualhead Hydra"): loc = self.multiworld.get_location(boss, self.player) checked_regions = {loc.parent_region} @@ -158,12 +155,12 @@ def check_foresta(region): return True check_foresta(loc.parent_region) - if self.multiworld.logic[self.player] == "friendly": + if self.options.logic == "friendly": process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player), ["MagicMirror"]) process_rules(self.multiworld.get_entrance("Overworld - Volcano", self.player), ["Mask"]) - if self.multiworld.map_shuffle[self.player] in ("none", "overworld"): + if self.options.map_shuffle in ("none", "overworld"): process_rules(self.multiworld.get_entrance("Overworld - Bone Dungeon", self.player), ["Bomb"]) process_rules(self.multiworld.get_entrance("Overworld - Wintry Cave", self.player), @@ -185,8 +182,8 @@ def check_foresta(region): process_rules(self.multiworld.get_entrance("Overworld - Mac Ship Doom", self.player), ["DragonClaw", "CaptainCap"]) - if self.multiworld.logic[self.player] == "expert": - if self.multiworld.map_shuffle[self.player] == "none" and not self.multiworld.crest_shuffle[self.player]: + if self.options.logic == "expert": + if self.options.map_shuffle == "none" and not self.options.crest_shuffle: inner_room = self.multiworld.get_region("Wintry Temple Inner Room", self.player) connection = Entrance(self.player, "Sealed Temple Exit Trick", inner_room) connection.connect(self.multiworld.get_region("Wintry Temple Outer Room", self.player)) @@ -198,14 +195,14 @@ def check_foresta(region): if entrance.connected_region.name in non_dead_end_crest_rooms: entrance.access_rule = lambda state: False - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": - logic_coins = [16, 24, 32, 32, 38][self.multiworld.shattered_sky_coin_quantity[self.player].value] + if self.options.sky_coin_mode == "shattered_sky_coin": + logic_coins = [16, 24, 32, 32, 38][self.options.shattered_sky_coin_quantity.value] self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ lambda state: state.has("Sky Fragment", self.player, logic_coins) - elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": + elif self.options.sky_coin_mode == "save_the_crystals": self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ lambda state: state.has_all(["Flamerus Rex", "Dualhead Hydra", "Ice Golem", "Pazuzu"], self.player) - elif self.multiworld.sky_coin_mode[self.player] in ("standard", "start_with"): + elif self.options.sky_coin_mode in ("standard", "start_with"): self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ lambda state: state.has("Sky Coin", self.player) @@ -213,26 +210,24 @@ def check_foresta(region): def stage_set_rules(multiworld): # If there's no enemies, there's no repeatable income sources no_enemies_players = [player for player in multiworld.get_game_players("Final Fantasy Mystic Quest") - if multiworld.enemies_density[player] == "none"] + if multiworld.worlds[player].options.enemies_density == "none"] if (len([item for item in multiworld.itempool if item.classification in (ItemClassification.filler, ItemClassification.trap)]) > len([player for player in no_enemies_players if - multiworld.accessibility[player] == "minimal"]) * 3): + multiworld.worlds[player].options.accessibility == "minimal"]) * 3): for player in no_enemies_players: for location in vendor_locations: - if multiworld.accessibility[player] == "locations": + if multiworld.worlds[player].options.accessibility == "full": multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED else: 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 + # 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 - - class FFMQLocation(Location): game = "Final Fantasy Mystic Quest" diff --git a/worlds/ffmq/__init__.py b/worlds/ffmq/__init__.py index ac3e9137093..3c58487265a 100644 --- a/worlds/ffmq/__init__.py +++ b/worlds/ffmq/__init__.py @@ -10,7 +10,7 @@ non_dead_end_crest_warps from .Items import item_table, item_groups, create_items, FFMQItem, fillers from .Output import generate_output -from .Options import option_definitions +from .Options import FFMQOptions from .Client import FFMQClient @@ -25,14 +25,25 @@ class FFMQWebWorld(WebWorld): - tutorials = [Tutorial( + setup_en = Tutorial( "Multiworld Setup Guide", "A guide to playing Final Fantasy Mystic Quest with Archipelago.", "English", "setup_en.md", "setup/en", ["Alchav"] - )] + ) + + setup_fr = Tutorial( + setup_en.tutorial_name, + setup_en.description, + "Français", + "setup_fr.md", + "setup/fr", + ["Artea"] + ) + + tutorials = [setup_en, setup_fr] class FFMQWorld(World): @@ -45,7 +56,8 @@ class FFMQWorld(World): item_name_to_id = {name: data.id for name, data in item_table.items() if data.id is not None} location_name_to_id = location_table - option_definitions = option_definitions + options_dataclass = FFMQOptions + options: FFMQOptions topology_present = True @@ -67,20 +79,14 @@ def __init__(self, world, player: int): super().__init__(world, player) def generate_early(self): - if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": - self.multiworld.brown_boxes[self.player].value = 1 - if self.multiworld.enemies_scaling_lower[self.player].value > \ - self.multiworld.enemies_scaling_upper[self.player].value: - (self.multiworld.enemies_scaling_lower[self.player].value, - self.multiworld.enemies_scaling_upper[self.player].value) =\ - (self.multiworld.enemies_scaling_upper[self.player].value, - self.multiworld.enemies_scaling_lower[self.player].value) - if self.multiworld.bosses_scaling_lower[self.player].value > \ - self.multiworld.bosses_scaling_upper[self.player].value: - (self.multiworld.bosses_scaling_lower[self.player].value, - self.multiworld.bosses_scaling_upper[self.player].value) =\ - (self.multiworld.bosses_scaling_upper[self.player].value, - self.multiworld.bosses_scaling_lower[self.player].value) + if self.options.sky_coin_mode == "shattered_sky_coin": + self.options.brown_boxes.value = 1 + if self.options.enemies_scaling_lower.value > self.options.enemies_scaling_upper.value: + self.options.enemies_scaling_lower.value, self.options.enemies_scaling_upper.value = \ + self.options.enemies_scaling_upper.value, self.options.enemies_scaling_lower.value + if self.options.bosses_scaling_lower.value > self.options.bosses_scaling_upper.value: + self.options.bosses_scaling_lower.value, self.options.bosses_scaling_upper.value = \ + self.options.bosses_scaling_upper.value, self.options.bosses_scaling_lower.value @classmethod def stage_generate_early(cls, multiworld): @@ -94,20 +100,20 @@ def stage_generate_early(cls, multiworld): rooms_data = {} for world in multiworld.get_game_worlds("Final Fantasy Mystic Quest"): - if (world.multiworld.map_shuffle[world.player] or world.multiworld.crest_shuffle[world.player] or - world.multiworld.crest_shuffle[world.player]): - if world.multiworld.map_shuffle_seed[world.player].value.isdigit(): - multiworld.random.seed(int(world.multiworld.map_shuffle_seed[world.player].value)) - elif world.multiworld.map_shuffle_seed[world.player].value != "random": - multiworld.random.seed(int(hash(world.multiworld.map_shuffle_seed[world.player].value)) - + int(world.multiworld.seed)) + if (world.options.map_shuffle or world.options.crest_shuffle or world.options.shuffle_battlefield_rewards + or world.options.companions_locations): + if world.options.map_shuffle_seed.value.isdigit(): + multiworld.random.seed(int(world.options.map_shuffle_seed.value)) + elif world.options.map_shuffle_seed.value != "random": + multiworld.random.seed(int(hash(world.options.map_shuffle_seed.value)) + + int(world.multiworld.seed)) seed = hex(multiworld.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper() - map_shuffle = multiworld.map_shuffle[world.player].value - crest_shuffle = multiworld.crest_shuffle[world.player].current_key - battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key - companion_shuffle = multiworld.companions_locations[world.player].value - kaeli_mom = multiworld.kaelis_mom_fight_minotaur[world.player].current_key + map_shuffle = world.options.map_shuffle.value + crest_shuffle = world.options.crest_shuffle.current_key + battlefield_shuffle = world.options.shuffle_battlefield_rewards.current_key + companion_shuffle = world.options.companions_locations.value + kaeli_mom = world.options.kaelis_mom_fight_minotaur.current_key query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}&cs={companion_shuffle}&km={kaeli_mom}" @@ -175,14 +181,14 @@ def get_filler_item_name(self): def extend_hint_information(self, hint_data): hint_data[self.player] = {} - if self.multiworld.map_shuffle[self.player]: + if self.options.map_shuffle: single_location_regions = ["Subregion Volcano Battlefield", "Subregion Mac's Ship", "Subregion Doom Castle"] for subregion in ["Subregion Foresta", "Subregion Aquaria", "Subregion Frozen Fields", "Subregion Fireburg", "Subregion Volcano Battlefield", "Subregion Windia", "Subregion Mac's Ship", "Subregion Doom Castle"]: region = self.multiworld.get_region(subregion, self.player) for location in region.locations: - if location.address and self.multiworld.map_shuffle[self.player] != "dungeons": + if location.address and self.options.map_shuffle != "dungeons": hint_data[self.player][location.address] = (subregion.split("Subregion ")[-1] + (" Region" if subregion not in single_location_regions else "")) @@ -202,14 +208,13 @@ def extend_hint_information(self, hint_data): for location in exit_check.connected_region.locations: if location.address: hint = [] - if self.multiworld.map_shuffle[self.player] != "dungeons": + if self.options.map_shuffle != "dungeons": hint.append((subregion.split("Subregion ")[-1] + (" Region" if subregion not in single_location_regions else ""))) - if self.multiworld.map_shuffle[self.player] != "overworld" and subregion not in \ - ("Subregion Mac's Ship", "Subregion Doom Castle"): + if self.options.map_shuffle != "overworld": hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu", "Pazuzu's")) - hint = " - ".join(hint) + hint = " - ".join(hint).replace(" - Mac Ship", "") if location.address in hint_data[self.player]: hint_data[self.player][location.address] += f"/{hint}" else: diff --git a/worlds/ffmq/data/entrances.yaml b/worlds/ffmq/data/entrances.yaml deleted file mode 100644 index 1dfef2655c3..00000000000 --- a/worlds/ffmq/data/entrances.yaml +++ /dev/null @@ -1,2450 +0,0 @@ -- name: Doom Castle - Sand Floor - To Sky Door - Sand Floor - id: 0 - area: 7 - coordinates: [24, 19] - teleporter: [0, 0] -- name: Doom Castle - Sand Floor - Main Entrance - Sand Floor - id: 1 - area: 7 - coordinates: [19, 43] - teleporter: [1, 6] -- name: Doom Castle - Aero Room - Aero Room Entrance - id: 2 - area: 7 - coordinates: [27, 39] - teleporter: [1, 0] -- name: Focus Tower B1 - Main Loop - South Entrance - id: 3 - area: 8 - coordinates: [43, 60] - teleporter: [2, 6] -- name: Focus Tower B1 - Main Loop - To Focus Tower 1F - Main Hall - id: 4 - area: 8 - coordinates: [37, 41] - teleporter: [4, 0] -- name: Focus Tower B1 - Aero Corridor - To Focus Tower 1F - Sun Coin Room - id: 5 - area: 8 - coordinates: [59, 35] - teleporter: [5, 0] -- name: Focus Tower B1 - Aero Corridor - To Sand Floor - Aero Chest - id: 6 - area: 8 - coordinates: [57, 59] - teleporter: [8, 0] -- name: Focus Tower B1 - Inner Loop - To Focus Tower 1F - Sky Door - id: 7 - area: 8 - coordinates: [51, 49] - teleporter: [6, 0] -- name: Focus Tower B1 - Inner Loop - To Doom Castle Sand Floor - id: 8 - area: 8 - coordinates: [51, 45] - teleporter: [7, 0] -- name: Focus Tower 1F - Focus Tower West Entrance - id: 9 - area: 9 - coordinates: [25, 29] - teleporter: [3, 6] -- name: Focus Tower 1F - To Focus Tower 2F - From SandCoin - id: 10 - area: 9 - coordinates: [16, 4] - teleporter: [10, 0] -- name: Focus Tower 1F - To Focus Tower B1 - Main Hall - id: 11 - area: 9 - coordinates: [4, 23] - teleporter: [11, 0] -- name: Focus Tower 1F - To Focus Tower B1 - To Aero Chest - id: 12 - area: 9 - coordinates: [26, 17] - teleporter: [12, 0] -- name: Focus Tower 1F - Sky Door - id: 13 - area: 9 - coordinates: [16, 24] - teleporter: [13, 0] -- name: Focus Tower 1F - To Focus Tower 2F - From RiverCoin - id: 14 - area: 9 - coordinates: [16, 10] - teleporter: [14, 0] -- name: Focus Tower 1F - To Focus Tower B1 - From Sky Door - id: 15 - area: 9 - coordinates: [16, 29] - teleporter: [15, 0] -- name: Focus Tower 2F - Sand Coin Passage - North Entrance - id: 16 - area: 10 - coordinates: [49, 30] - teleporter: [4, 6] -- name: Focus Tower 2F - Sand Coin Passage - To Focus Tower 1F - To SandCoin - id: 17 - area: 10 - coordinates: [47, 33] - teleporter: [17, 0] -- name: Focus Tower 2F - River Coin Passage - To Focus Tower 1F - To RiverCoin - id: 18 - area: 10 - coordinates: [47, 41] - teleporter: [18, 0] -- name: Focus Tower 2F - River Coin Passage - To Focus Tower 3F - Lower Floor - id: 19 - area: 10 - coordinates: [38, 40] - teleporter: [20, 0] -- name: Focus Tower 2F - Venus Chest Room - To Focus Tower 3F - Upper Floor - id: 20 - area: 10 - coordinates: [56, 40] - teleporter: [19, 0] -- name: Focus Tower 2F - Venus Chest Room - Pillar Script - id: 21 - area: 10 - coordinates: [48, 53] - teleporter: [13, 8] -- name: Focus Tower 3F - Lower Floor - To Fireburg Entrance - id: 22 - area: 11 - coordinates: [11, 39] - teleporter: [6, 6] -- name: Focus Tower 3F - Lower Floor - To Focus Tower 2F - Jump on Pillar - id: 23 - area: 11 - coordinates: [6, 47] - teleporter: [24, 0] -- name: Focus Tower 3F - Upper Floor - To Aquaria Entrance - id: 24 - area: 11 - coordinates: [21, 38] - teleporter: [5, 6] -- name: Focus Tower 3F - Upper Floor - To Focus Tower 2F - Venus Chest Room - id: 25 - area: 11 - coordinates: [24, 47] - teleporter: [23, 0] -- name: Level Forest - Boulder Script - id: 26 - area: 14 - coordinates: [52, 15] - teleporter: [0, 8] -- name: Level Forest - Rotten Tree Script - id: 27 - area: 14 - coordinates: [47, 6] - teleporter: [2, 8] -- name: Level Forest - Exit Level Forest 1 - id: 28 - area: 14 - coordinates: [46, 25] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 2 - id: 29 - area: 14 - coordinates: [46, 26] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 3 - id: 30 - area: 14 - coordinates: [47, 25] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 4 - id: 31 - area: 14 - coordinates: [47, 26] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 5 - id: 32 - area: 14 - coordinates: [60, 14] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 6 - id: 33 - area: 14 - coordinates: [61, 14] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 7 - id: 34 - area: 14 - coordinates: [46, 4] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 8 - id: 35 - area: 14 - coordinates: [46, 3] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest 9 - id: 36 - area: 14 - coordinates: [47, 4] - teleporter: [25, 0] -- name: Level Forest - Exit Level Forest A - id: 37 - area: 14 - coordinates: [47, 3] - teleporter: [25, 0] -- name: Foresta - Exit Foresta 1 - id: 38 - area: 15 - coordinates: [10, 25] - teleporter: [31, 0] -- name: Foresta - Exit Foresta 2 - id: 39 - area: 15 - coordinates: [10, 26] - teleporter: [31, 0] -- name: Foresta - Exit Foresta 3 - id: 40 - area: 15 - coordinates: [11, 25] - teleporter: [31, 0] -- name: Foresta - Exit Foresta 4 - id: 41 - area: 15 - coordinates: [11, 26] - teleporter: [31, 0] -- name: Foresta - Old Man House - Front Door - id: 42 - area: 15 - coordinates: [25, 17] - teleporter: [32, 4] -- name: Foresta - Old Man House - Back Door - id: 43 - area: 15 - coordinates: [25, 14] - teleporter: [33, 0] -- name: Foresta - Kaeli's House - id: 44 - area: 15 - coordinates: [7, 21] - teleporter: [0, 5] -- name: Foresta - Rest House - id: 45 - area: 15 - coordinates: [23, 23] - teleporter: [1, 5] -- name: Kaeli's House - Kaeli's House Entrance - id: 46 - area: 16 - coordinates: [11, 20] - teleporter: [86, 3] -- name: Foresta Houses - Old Man's House - Old Man Front Exit - id: 47 - area: 17 - coordinates: [35, 44] - teleporter: [34, 0] -- name: Foresta Houses - Old Man's House - Old Man Back Exit - id: 48 - area: 17 - coordinates: [35, 27] - teleporter: [35, 0] -- name: Foresta - Old Man House - Barrel Tile Script # New, use the focus tower column's script - id: 483 - area: 17 - coordinates: [0x23, 0x1E] - teleporter: [0x0D, 8] -- name: Foresta Houses - Rest House - Bed Script - id: 49 - area: 17 - coordinates: [30, 6] - teleporter: [1, 8] -- name: Foresta Houses - Rest House - Rest House Exit - id: 50 - area: 17 - coordinates: [35, 20] - teleporter: [87, 3] -- name: Foresta Houses - Libra House - Libra House Script - id: 51 - area: 17 - coordinates: [8, 49] - teleporter: [67, 8] -- name: Foresta Houses - Gemini House - Gemini House Script - id: 52 - area: 17 - coordinates: [26, 55] - teleporter: [68, 8] -- name: Foresta Houses - Mobius House - Mobius House Script - id: 53 - area: 17 - coordinates: [14, 33] - teleporter: [69, 8] -- name: Sand Temple - Sand Temple Entrance - id: 54 - area: 18 - coordinates: [56, 27] - teleporter: [36, 0] -- name: Bone Dungeon 1F - Bone Dungeon Entrance - id: 55 - area: 19 - coordinates: [13, 60] - teleporter: [37, 0] -- name: Bone Dungeon 1F - To Bone Dungeon B1 - id: 56 - area: 19 - coordinates: [13, 39] - teleporter: [2, 2] -- name: Bone Dungeon B1 - Waterway - Exit Waterway - id: 57 - area: 20 - coordinates: [27, 39] - teleporter: [3, 2] -- name: Bone Dungeon B1 - Waterway - Tristam's Script - id: 58 - area: 20 - coordinates: [27, 45] - teleporter: [3, 8] -- name: Bone Dungeon B1 - Waterway - To Bone Dungeon 1F - id: 59 - area: 20 - coordinates: [54, 61] - teleporter: [88, 3] -- name: Bone Dungeon B1 - Checker Room - Exit Checker Room - id: 60 - area: 20 - coordinates: [23, 40] - teleporter: [4, 2] -- name: Bone Dungeon B1 - Checker Room - To Waterway - id: 61 - area: 20 - coordinates: [39, 49] - teleporter: [89, 3] -- name: Bone Dungeon B1 - Hidden Room - To B2 - Exploding Skull Room - id: 62 - area: 20 - coordinates: [5, 33] - teleporter: [91, 3] -- name: Bonne Dungeon B2 - Exploding Skull Room - To Hidden Passage - id: 63 - area: 21 - coordinates: [19, 13] - teleporter: [5, 2] -- name: Bonne Dungeon B2 - Exploding Skull Room - To Two Skulls Room - id: 64 - area: 21 - coordinates: [29, 15] - teleporter: [6, 2] -- name: Bonne Dungeon B2 - Exploding Skull Room - To Checker Room - id: 65 - area: 21 - coordinates: [8, 25] - teleporter: [90, 3] -- name: Bonne Dungeon B2 - Box Room - To B2 - Two Skulls Room - id: 66 - area: 21 - coordinates: [59, 12] - teleporter: [93, 3] -- name: Bonne Dungeon B2 - Quake Room - To B2 - Two Skulls Room - id: 67 - area: 21 - coordinates: [59, 28] - teleporter: [94, 3] -- name: Bonne Dungeon B2 - Two Skulls Room - To Box Room - id: 68 - area: 21 - coordinates: [53, 7] - teleporter: [7, 2] -- name: Bonne Dungeon B2 - Two Skulls Room - To Quake Room - id: 69 - area: 21 - coordinates: [41, 3] - teleporter: [8, 2] -- name: Bonne Dungeon B2 - Two Skulls Room - To Boss Room - id: 70 - area: 21 - coordinates: [47, 57] - teleporter: [9, 2] -- name: Bonne Dungeon B2 - Two Skulls Room - To B2 - Exploding Skull Room - id: 71 - area: 21 - coordinates: [54, 23] - teleporter: [92, 3] -- name: Bone Dungeon B2 - Boss Room - Flamerus Rex Script - id: 72 - area: 22 - coordinates: [29, 19] - teleporter: [4, 8] -- name: Bone Dungeon B2 - Boss Room - Tristam Leave Script - id: 73 - area: 22 - coordinates: [29, 23] - teleporter: [75, 8] -- name: Bone Dungeon B2 - Boss Room - To B2 - Two Skulls Room - id: 74 - area: 22 - coordinates: [30, 27] - teleporter: [95, 3] -- name: Libra Temple - Entrance - id: 75 - area: 23 - coordinates: [10, 15] - teleporter: [13, 6] -- name: Libra Temple - Libra Tile Script - id: 76 - area: 23 - coordinates: [9, 8] - teleporter: [59, 8] -- name: Aquaria Winter - Winter Entrance 1 - id: 77 - area: 24 - coordinates: [25, 25] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Entrance 2 - id: 78 - area: 24 - coordinates: [25, 26] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Entrance 3 - id: 79 - area: 24 - coordinates: [26, 25] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Entrance 4 - id: 80 - area: 24 - coordinates: [26, 26] - teleporter: [8, 6] -- name: Aquaria Winter - Winter Phoebe's House Entrance Script #Modified to not be a script - id: 81 - area: 24 - coordinates: [8, 19] - teleporter: [10, 5] # original value [5, 8] -- name: Aquaria Winter - Winter Vendor House Entrance - id: 82 - area: 24 - coordinates: [8, 5] - teleporter: [44, 4] -- name: Aquaria Winter - Winter INN Entrance - id: 83 - area: 24 - coordinates: [26, 17] - teleporter: [11, 5] -- name: Aquaria Summer - Summer Entrance 1 - id: 84 - area: 25 - coordinates: [57, 25] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Entrance 2 - id: 85 - area: 25 - coordinates: [57, 26] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Entrance 3 - id: 86 - area: 25 - coordinates: [58, 25] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Entrance 4 - id: 87 - area: 25 - coordinates: [58, 26] - teleporter: [8, 6] -- name: Aquaria Summer - Summer Phoebe's House Entrance - id: 88 - area: 25 - coordinates: [40, 19] - teleporter: [10, 5] -- name: Aquaria Summer - Spencer's Place Entrance Top - id: 89 - area: 25 - coordinates: [40, 16] - teleporter: [42, 0] -- name: Aquaria Summer - Spencer's Place Entrance Side - id: 90 - area: 25 - coordinates: [41, 18] - teleporter: [43, 0] -- name: Aquaria Summer - Summer Vendor House Entrance - id: 91 - area: 25 - coordinates: [40, 5] - teleporter: [44, 4] -- name: Aquaria Summer - Summer INN Entrance - id: 92 - area: 25 - coordinates: [58, 17] - teleporter: [11, 5] -- name: Phoebe's House - Entrance # Change to a script, same as vendor house - id: 93 - area: 26 - coordinates: [29, 14] - teleporter: [5, 8] # Original Value [11,3] -- name: Aquaria Vendor House - Vendor House Entrance's Script - id: 94 - area: 27 - coordinates: [7, 10] - teleporter: [40, 8] -- name: Aquaria Vendor House - Vendor House Stairs - id: 95 - area: 27 - coordinates: [1, 4] - teleporter: [47, 0] -- name: Aquaria Gemini Room - Gemini Script - id: 96 - area: 27 - coordinates: [2, 40] - teleporter: [72, 8] -- name: Aquaria Gemini Room - Gemini Room Stairs - id: 97 - area: 27 - coordinates: [4, 39] - teleporter: [48, 0] -- name: Aquaria INN - Aquaria INN entrance # Change to a script, same as vendor house - id: 98 - area: 27 - coordinates: [51, 46] - teleporter: [75, 8] # Original value [48,3] -- name: Wintry Cave 1F - Main Entrance - id: 99 - area: 28 - coordinates: [50, 58] - teleporter: [49, 0] -- name: Wintry Cave 1F - To 3F Top - id: 100 - area: 28 - coordinates: [40, 25] - teleporter: [14, 2] -- name: Wintry Cave 1F - To 2F - id: 101 - area: 28 - coordinates: [10, 43] - teleporter: [15, 2] -- name: Wintry Cave 1F - Phoebe's Script - id: 102 - area: 28 - coordinates: [44, 37] - teleporter: [6, 8] -- name: Wintry Cave 2F - To 3F Bottom - id: 103 - area: 29 - coordinates: [58, 5] - teleporter: [50, 0] -- name: Wintry Cave 2F - To 1F - id: 104 - area: 29 - coordinates: [38, 18] - teleporter: [97, 3] -- name: Wintry Cave 3F Top - Exit from 3F Top - id: 105 - area: 30 - coordinates: [24, 6] - teleporter: [96, 3] -- name: Wintry Cave 3F Bottom - Exit to 2F - id: 106 - area: 31 - coordinates: [4, 29] - teleporter: [51, 0] -- name: Life Temple - Entrance - id: 107 - area: 32 - coordinates: [9, 60] - teleporter: [14, 6] -- name: Life Temple - Libra Tile Script - id: 108 - area: 32 - coordinates: [3, 55] - teleporter: [60, 8] -- name: Life Temple - Mysterious Man Script - id: 109 - area: 32 - coordinates: [9, 44] - teleporter: [78, 8] -- name: Fall Basin - Back Exit Script - id: 110 - area: 33 - coordinates: [17, 5] - teleporter: [9, 0] # Remove script [42, 8] for overworld teleport (but not main exit) -- name: Fall Basin - Main Exit - id: 111 - area: 33 - coordinates: [15, 26] - teleporter: [53, 0] -- name: Fall Basin - Phoebe's Script - id: 112 - area: 33 - coordinates: [17, 6] - teleporter: [9, 8] -- name: Ice Pyramid B1 Taunt Room - To Climbing Wall Room - id: 113 - area: 34 - coordinates: [43, 6] - teleporter: [55, 0] -- name: Ice Pyramid 1F Maze - Main Entrance 1 - id: 114 - area: 35 - coordinates: [18, 36] - teleporter: [56, 0] -- name: Ice Pyramid 1F Maze - Main Entrance 2 - id: 115 - area: 35 - coordinates: [19, 36] - teleporter: [56, 0] -- name: Ice Pyramid 1F Maze - West Stairs To 2F South Tiled Room - id: 116 - area: 35 - coordinates: [3, 27] - teleporter: [57, 0] -- name: Ice Pyramid 1F Maze - West Center Stairs to 2F West Room - id: 117 - area: 35 - coordinates: [11, 15] - teleporter: [58, 0] -- name: Ice Pyramid 1F Maze - East Center Stairs to 2F Center Room - id: 118 - area: 35 - coordinates: [25, 16] - teleporter: [59, 0] -- name: Ice Pyramid 1F Maze - Upper Stairs to 2F Small North Room - id: 119 - area: 35 - coordinates: [31, 1] - teleporter: [60, 0] -- name: Ice Pyramid 1F Maze - East Stairs to 2F North Corridor - id: 120 - area: 35 - coordinates: [34, 9] - teleporter: [61, 0] -- name: Ice Pyramid 1F Maze - Statue's Script - id: 121 - area: 35 - coordinates: [21, 32] - teleporter: [77, 8] -- name: Ice Pyramid 2F South Tiled Room - To 1F - id: 122 - area: 36 - coordinates: [4, 26] - teleporter: [62, 0] -- name: Ice Pyramid 2F South Tiled Room - To 3F Two Boxes Room - id: 123 - area: 36 - coordinates: [22, 17] - teleporter: [67, 0] -- name: Ice Pyramid 2F West Room - To 1F - id: 124 - area: 36 - coordinates: [9, 10] - teleporter: [63, 0] -- name: Ice Pyramid 2F Center Room - To 1F - id: 125 - area: 36 - coordinates: [22, 14] - teleporter: [64, 0] -- name: Ice Pyramid 2F Small North Room - To 1F - id: 126 - area: 36 - coordinates: [26, 4] - teleporter: [65, 0] -- name: Ice Pyramid 2F North Corridor - To 1F - id: 127 - area: 36 - coordinates: [32, 8] - teleporter: [66, 0] -- name: Ice Pyramid 2F North Corridor - To 3F Main Loop - id: 128 - area: 36 - coordinates: [12, 7] - teleporter: [68, 0] -- name: Ice Pyramid 3F Two Boxes Room - To 2F South Tiled Room - id: 129 - area: 37 - coordinates: [24, 54] - teleporter: [69, 0] -- name: Ice Pyramid 3F Main Loop - To 2F Corridor - id: 130 - area: 37 - coordinates: [16, 45] - teleporter: [70, 0] -- name: Ice Pyramid 3F Main Loop - To 4F - id: 131 - area: 37 - coordinates: [19, 43] - teleporter: [71, 0] -- name: Ice Pyramid 4F Treasure Room - To 3F Main Loop - id: 132 - area: 38 - coordinates: [52, 5] - teleporter: [72, 0] -- name: Ice Pyramid 4F Treasure Room - To 5F Leap of Faith Room - id: 133 - area: 38 - coordinates: [62, 19] - teleporter: [73, 0] -- name: Ice Pyramid 5F Leap of Faith Room - To 4F Treasure Room - id: 134 - area: 39 - coordinates: [54, 63] - teleporter: [74, 0] -- name: Ice Pyramid 5F Leap of Faith Room - Bombed Ice Plate - id: 135 - area: 39 - coordinates: [47, 54] - teleporter: [77, 8] -- name: Ice Pyramid 5F Stairs to Ice Golem - To Ice Golem Room - id: 136 - area: 39 - coordinates: [39, 43] - teleporter: [75, 0] -- name: Ice Pyramid 5F Stairs to Ice Golem - To Climbing Wall Room - id: 137 - area: 39 - coordinates: [39, 60] - teleporter: [76, 0] -- name: Ice Pyramid - Duplicate Ice Golem Room # not used? - id: 138 - area: 40 - coordinates: [44, 43] - teleporter: [77, 0] -- name: Ice Pyramid Climbing Wall Room - To Taunt Room - id: 139 - area: 41 - coordinates: [4, 59] - teleporter: [78, 0] -- name: Ice Pyramid Climbing Wall Room - To 5F Stairs - id: 140 - area: 41 - coordinates: [4, 45] - teleporter: [79, 0] -- name: Ice Pyramid Ice Golem Room - To 5F Stairs - id: 141 - area: 42 - coordinates: [44, 43] - teleporter: [80, 0] -- name: Ice Pyramid Ice Golem Room - Ice Golem Script - id: 142 - area: 42 - coordinates: [53, 32] - teleporter: [10, 8] -- name: Spencer Waterfall - To Spencer Cave - id: 143 - area: 43 - coordinates: [48, 57] - teleporter: [81, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 1 - id: 144 - area: 43 - coordinates: [40, 5] - teleporter: [82, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 2 - id: 145 - area: 43 - coordinates: [40, 6] - teleporter: [82, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 3 - id: 146 - area: 43 - coordinates: [41, 5] - teleporter: [82, 0] -- name: Spencer Waterfall - Upper Exit to Aquaria 4 - id: 147 - area: 43 - coordinates: [41, 6] - teleporter: [82, 0] -- name: Spencer Waterfall - Right Exit to Aquaria 1 - id: 148 - area: 43 - coordinates: [46, 8] - teleporter: [83, 0] -- name: Spencer Waterfall - Right Exit to Aquaria 2 - id: 149 - area: 43 - coordinates: [47, 8] - teleporter: [83, 0] -- name: Spencer Cave Normal Main - To Waterfall - id: 150 - area: 44 - coordinates: [14, 39] - teleporter: [85, 0] -- name: Spencer Cave Normal From Overworld - Exit to Overworld - id: 151 - area: 44 - coordinates: [15, 57] - teleporter: [7, 6] -- name: Spencer Cave Unplug - Exit to Overworld - id: 152 - area: 45 - coordinates: [40, 29] - teleporter: [7, 6] -- name: Spencer Cave Unplug - Libra Teleporter Start Script - id: 153 - area: 45 - coordinates: [28, 21] - teleporter: [33, 8] -- name: Spencer Cave Unplug - Libra Teleporter End Script - id: 154 - area: 45 - coordinates: [46, 4] - teleporter: [34, 8] -- name: Spencer Cave Unplug - Mobius Teleporter Chest Script - id: 155 - area: 45 - coordinates: [21, 9] - teleporter: [35, 8] -- name: Spencer Cave Unplug - Mobius Teleporter Start Script - id: 156 - area: 45 - coordinates: [29, 28] - teleporter: [36, 8] -- name: Wintry Temple Outer Room - Main Entrance - id: 157 - area: 46 - coordinates: [8, 31] - teleporter: [15, 6] -- name: Wintry Temple Inner Room - Gemini Tile to Sealed temple - id: 158 - area: 46 - coordinates: [9, 24] - teleporter: [62, 8] -- name: Fireburg - To Overworld - id: 159 - area: 47 - coordinates: [4, 13] - teleporter: [9, 6] -- name: Fireburg - To Overworld - id: 160 - area: 47 - coordinates: [5, 13] - teleporter: [9, 6] -- name: Fireburg - To Overworld - id: 161 - area: 47 - coordinates: [28, 15] - teleporter: [9, 6] -- name: Fireburg - To Overworld - id: 162 - area: 47 - coordinates: [27, 15] - teleporter: [9, 6] -- name: Fireburg - Vendor House - id: 163 - area: 47 - coordinates: [10, 24] - teleporter: [91, 0] -- name: Fireburg - Reuben House - id: 164 - area: 47 - coordinates: [14, 6] - teleporter: [98, 8] # Script for reuben, original value [16, 2] -- name: Fireburg - Hotel - id: 165 - area: 47 - coordinates: [20, 8] - teleporter: [96, 8] # It's a script now for tristam, original value [17, 2] -- name: Fireburg - GrenadeMan House Script - id: 166 - area: 47 - coordinates: [12, 18] - teleporter: [11, 8] -- name: Reuben House - Main Entrance - id: 167 - area: 48 - coordinates: [33, 46] - teleporter: [98, 3] -- name: GrenadeMan House - Entrance Script - id: 168 - area: 49 - coordinates: [55, 60] - teleporter: [9, 8] -- name: GrenadeMan House - To Mobius Crest Room - id: 169 - area: 49 - coordinates: [57, 52] - teleporter: [93, 0] -- name: GrenadeMan Mobius Room - Stairs to House - id: 170 - area: 49 - coordinates: [39, 26] - teleporter: [94, 0] -- name: GrenadeMan Mobius Room - Mobius Teleporter Script - id: 171 - area: 49 - coordinates: [39, 23] - teleporter: [54, 8] -- name: Fireburg Vendor House - Entrance Script # No use to be a script - id: 172 - area: 49 - coordinates: [7, 10] - teleporter: [95, 0] # Original value [39, 8] -- name: Fireburg Vendor House - Stairs to Gemini Room - id: 173 - area: 49 - coordinates: [1, 4] - teleporter: [96, 0] -- name: Fireburg Gemini Room - Stairs to Vendor House - id: 174 - area: 49 - coordinates: [4, 39] - teleporter: [97, 0] -- name: Fireburg Gemini Room - Gemini Teleporter Script - id: 175 - area: 49 - coordinates: [2, 40] - teleporter: [45, 8] -- name: Fireburg Hotel Lobby - Stairs to beds - id: 176 - area: 49 - coordinates: [4, 50] - teleporter: [213, 0] -- name: Fireburg Hotel Lobby - Entrance - id: 177 - area: 49 - coordinates: [17, 56] - teleporter: [99, 3] -- name: Fireburg Hotel Beds - Stairs to Hotel Lobby - id: 178 - area: 49 - coordinates: [45, 59] - teleporter: [214, 0] -- name: Mine Exterior - Main Entrance - id: 179 - area: 50 - coordinates: [5, 28] - teleporter: [98, 0] -- name: Mine Exterior - To Cliff - id: 180 - area: 50 - coordinates: [58, 29] - teleporter: [99, 0] -- name: Mine Exterior - To Parallel Room - id: 181 - area: 50 - coordinates: [8, 7] - teleporter: [20, 2] -- name: Mine Exterior - To Crescent Room - id: 182 - area: 50 - coordinates: [26, 15] - teleporter: [21, 2] -- name: Mine Exterior - To Climbing Room - id: 183 - area: 50 - coordinates: [21, 35] - teleporter: [22, 2] -- name: Mine Exterior - Jinn Fight Script - id: 184 - area: 50 - coordinates: [58, 31] - teleporter: [74, 8] -- name: Mine Parallel Room - To Mine Exterior - id: 185 - area: 51 - coordinates: [7, 60] - teleporter: [100, 3] -- name: Mine Crescent Room - To Mine Exterior - id: 186 - area: 51 - coordinates: [22, 61] - teleporter: [101, 3] -- name: Mine Climbing Room - To Mine Exterior - id: 187 - area: 51 - coordinates: [56, 21] - teleporter: [102, 3] -- name: Mine Cliff - Entrance - id: 188 - area: 52 - coordinates: [9, 5] - teleporter: [100, 0] -- name: Mine Cliff - Reuben Grenade Script - id: 189 - area: 52 - coordinates: [15, 7] - teleporter: [12, 8] -- name: Sealed Temple - To Overworld - id: 190 - area: 53 - coordinates: [58, 43] - teleporter: [16, 6] -- name: Sealed Temple - Gemini Tile Script - id: 191 - area: 53 - coordinates: [56, 38] - teleporter: [63, 8] -- name: Volcano Base - Main Entrance 1 - id: 192 - area: 54 - coordinates: [23, 25] - teleporter: [103, 0] -- name: Volcano Base - Main Entrance 2 - id: 193 - area: 54 - coordinates: [23, 26] - teleporter: [103, 0] -- name: Volcano Base - Main Entrance 3 - id: 194 - area: 54 - coordinates: [24, 25] - teleporter: [103, 0] -- name: Volcano Base - Main Entrance 4 - id: 195 - area: 54 - coordinates: [24, 26] - teleporter: [103, 0] -- name: Volcano Base - Left Stairs Script - id: 196 - area: 54 - coordinates: [20, 5] - teleporter: [31, 8] -- name: Volcano Base - Right Stairs Script - id: 197 - area: 54 - coordinates: [32, 5] - teleporter: [30, 8] -- name: Volcano Top Right - Top Exit - id: 198 - area: 55 - coordinates: [44, 8] - teleporter: [9, 0] # Original value [103, 0] changed to volcano escape so floor shuffling doesn't pick it up -- name: Volcano Top Left - To Right-Left Path Script - id: 199 - area: 55 - coordinates: [40, 24] - teleporter: [26, 8] -- name: Volcano Top Right - To Left-Right Path Script - id: 200 - area: 55 - coordinates: [52, 24] - teleporter: [79, 8] # Original Value [26, 8] -- name: Volcano Right Path - To Volcano Base Script - id: 201 - area: 56 - coordinates: [48, 42] - teleporter: [15, 8] # Original Value [27, 8] -- name: Volcano Left Path - To Volcano Cross Left-Right - id: 202 - area: 56 - coordinates: [40, 31] - teleporter: [25, 2] -- name: Volcano Left Path - To Volcano Cross Right-Left - id: 203 - area: 56 - coordinates: [52, 29] - teleporter: [26, 2] -- name: Volcano Left Path - To Volcano Base Script - id: 204 - area: 56 - coordinates: [36, 42] - teleporter: [27, 8] -- name: Volcano Cross Left-Right - To Volcano Left Path - id: 205 - area: 56 - coordinates: [10, 42] - teleporter: [103, 3] -- name: Volcano Cross Left-Right - To Volcano Top Right Script - id: 206 - area: 56 - coordinates: [16, 24] - teleporter: [29, 8] -- name: Volcano Cross Right-Left - To Volcano Top Left Script - id: 207 - area: 56 - coordinates: [8, 22] - teleporter: [28, 8] -- name: Volcano Cross Right-Left - To Volcano Left Path - id: 208 - area: 56 - coordinates: [16, 42] - teleporter: [104, 3] -- name: Lava Dome Inner Ring Main Loop - Main Entrance 1 - id: 209 - area: 57 - coordinates: [32, 5] - teleporter: [104, 0] -- name: Lava Dome Inner Ring Main Loop - Main Entrance 2 - id: 210 - area: 57 - coordinates: [33, 5] - teleporter: [104, 0] -- name: Lava Dome Inner Ring Main Loop - To Three Steps Room - id: 211 - area: 57 - coordinates: [14, 5] - teleporter: [105, 0] -- name: Lava Dome Inner Ring Main Loop - To Life Chest Room Lower - id: 212 - area: 57 - coordinates: [40, 17] - teleporter: [106, 0] -- name: Lava Dome Inner Ring Main Loop - To Big Jump Room Left - id: 213 - area: 57 - coordinates: [8, 11] - teleporter: [108, 0] -- name: Lava Dome Inner Ring Main Loop - To Split Corridor Room - id: 214 - area: 57 - coordinates: [11, 19] - teleporter: [111, 0] -- name: Lava Dome Inner Ring Center Ledge - To Life Chest Room Higher - id: 215 - area: 57 - coordinates: [32, 11] - teleporter: [107, 0] -- name: Lava Dome Inner Ring Plate Ledge - To Plate Corridor - id: 216 - area: 57 - coordinates: [12, 23] - teleporter: [109, 0] -- name: Lava Dome Inner Ring Plate Ledge - Plate Script - id: 217 - area: 57 - coordinates: [5, 23] - teleporter: [47, 8] -- name: Lava Dome Inner Ring Upper Ledges - To Pointless Room - id: 218 - area: 57 - coordinates: [0, 9] - teleporter: [110, 0] -- name: Lava Dome Inner Ring Upper Ledges - To Lower Moon Helm Room - id: 219 - area: 57 - coordinates: [0, 15] - teleporter: [112, 0] -- name: Lava Dome Inner Ring Upper Ledges - To Up-Down Corridor - id: 220 - area: 57 - coordinates: [54, 5] - teleporter: [113, 0] -- name: Lava Dome Inner Ring Big Door Ledge - To Jumping Maze II - id: 221 - area: 57 - coordinates: [54, 21] - teleporter: [114, 0] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 1 - id: 222 - area: 57 - coordinates: [62, 20] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 2 - id: 223 - area: 57 - coordinates: [63, 20] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 3 - id: 224 - area: 57 - coordinates: [62, 21] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 4 - id: 225 - area: 57 - coordinates: [63, 21] - teleporter: [29, 2] -- name: Lava Dome Inner Ring Tiny Bottom Ledge - To Four Boxes Corridor - id: 226 - area: 57 - coordinates: [50, 25] - teleporter: [115, 0] -- name: Lava Dome Jump Maze II - Lower Right Entrance - id: 227 - area: 58 - coordinates: [55, 28] - teleporter: [116, 0] -- name: Lava Dome Jump Maze II - Upper Entrance - id: 228 - area: 58 - coordinates: [35, 3] - teleporter: [119, 0] -- name: Lava Dome Jump Maze II - Lower Left Entrance - id: 229 - area: 58 - coordinates: [34, 27] - teleporter: [120, 0] -- name: Lava Dome Up-Down Corridor - Upper Entrance - id: 230 - area: 58 - coordinates: [29, 8] - teleporter: [117, 0] -- name: Lava Dome Up-Down Corridor - Lower Entrance - id: 231 - area: 58 - coordinates: [28, 25] - teleporter: [118, 0] -- name: Lava Dome Jump Maze I - South Entrance - id: 232 - area: 59 - coordinates: [20, 27] - teleporter: [121, 0] -- name: Lava Dome Jump Maze I - North Entrance - id: 233 - area: 59 - coordinates: [7, 3] - teleporter: [122, 0] -- name: Lava Dome Pointless Room - Entrance - id: 234 - area: 60 - coordinates: [2, 7] - teleporter: [123, 0] -- name: Lava Dome Pointless Room - Visit Quest Script 1 - id: 490 - area: 60 - coordinates: [4, 4] - teleporter: [99, 8] -- name: Lava Dome Pointless Room - Visit Quest Script 2 - id: 491 - area: 60 - coordinates: [4, 5] - teleporter: [99, 8] -- name: Lava Dome Lower Moon Helm Room - Left Entrance - id: 235 - area: 60 - coordinates: [2, 19] - teleporter: [124, 0] -- name: Lava Dome Lower Moon Helm Room - Right Entrance - id: 236 - area: 60 - coordinates: [11, 21] - teleporter: [125, 0] -- name: Lava Dome Moon Helm Room - Entrance - id: 237 - area: 60 - coordinates: [15, 23] - teleporter: [126, 0] -- name: Lava Dome Three Jumps Room - To Main Loop - id: 238 - area: 61 - coordinates: [58, 15] - teleporter: [127, 0] -- name: Lava Dome Life Chest Room - Lower South Entrance - id: 239 - area: 61 - coordinates: [38, 27] - teleporter: [128, 0] -- name: Lava Dome Life Chest Room - Upper South Entrance - id: 240 - area: 61 - coordinates: [28, 23] - teleporter: [129, 0] -- name: Lava Dome Big Jump Room - Left Entrance - id: 241 - area: 62 - coordinates: [42, 51] - teleporter: [133, 0] -- name: Lava Dome Big Jump Room - North Entrance - id: 242 - area: 62 - coordinates: [30, 29] - teleporter: [131, 0] -- name: Lava Dome Big Jump Room - Lower Right Stairs - id: 243 - area: 62 - coordinates: [61, 59] - teleporter: [132, 0] -- name: Lava Dome Split Corridor - Upper Stairs - id: 244 - area: 62 - coordinates: [30, 43] - teleporter: [130, 0] -- name: Lava Dome Split Corridor - Lower Stairs - id: 245 - area: 62 - coordinates: [36, 61] - teleporter: [134, 0] -- name: Lava Dome Plate Corridor - Right Entrance - id: 246 - area: 63 - coordinates: [19, 29] - teleporter: [135, 0] -- name: Lava Dome Plate Corridor - Left Entrance - id: 247 - area: 63 - coordinates: [60, 21] - teleporter: [137, 0] -- name: Lava Dome Four Boxes Stairs - Upper Entrance - id: 248 - area: 63 - coordinates: [22, 3] - teleporter: [136, 0] -- name: Lava Dome Four Boxes Stairs - Lower Entrance - id: 249 - area: 63 - coordinates: [22, 17] - teleporter: [16, 0] -- name: Lava Dome Hydra Room - South Entrance - id: 250 - area: 64 - coordinates: [14, 59] - teleporter: [105, 3] -- name: Lava Dome Hydra Room - North Exit - id: 251 - area: 64 - coordinates: [25, 31] - teleporter: [138, 0] -- name: Lava Dome Hydra Room - Hydra Script - id: 252 - area: 64 - coordinates: [14, 36] - teleporter: [14, 8] -- name: Lava Dome Escape Corridor - South Entrance - id: 253 - area: 65 - coordinates: [22, 17] - teleporter: [139, 0] -- name: Lava Dome Escape Corridor - North Entrance - id: 254 - area: 65 - coordinates: [22, 3] - teleporter: [9, 0] -- name: Rope Bridge - West Entrance 1 - id: 255 - area: 66 - coordinates: [3, 10] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 2 - id: 256 - area: 66 - coordinates: [3, 11] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 3 - id: 257 - area: 66 - coordinates: [3, 12] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 4 - id: 258 - area: 66 - coordinates: [3, 13] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 5 - id: 259 - area: 66 - coordinates: [4, 10] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 6 - id: 260 - area: 66 - coordinates: [4, 11] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 7 - id: 261 - area: 66 - coordinates: [4, 12] - teleporter: [140, 0] -- name: Rope Bridge - West Entrance 8 - id: 262 - area: 66 - coordinates: [4, 13] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 1 - id: 263 - area: 66 - coordinates: [59, 10] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 2 - id: 264 - area: 66 - coordinates: [59, 11] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 3 - id: 265 - area: 66 - coordinates: [59, 12] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 4 - id: 266 - area: 66 - coordinates: [59, 13] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 5 - id: 267 - area: 66 - coordinates: [60, 10] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 6 - id: 268 - area: 66 - coordinates: [60, 11] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 7 - id: 269 - area: 66 - coordinates: [60, 12] - teleporter: [140, 0] -- name: Rope Bridge - East Entrance 8 - id: 270 - area: 66 - coordinates: [60, 13] - teleporter: [140, 0] -- name: Rope Bridge - Reuben Fall Script - id: 271 - area: 66 - coordinates: [13, 12] - teleporter: [15, 8] -- name: Alive Forest - West Entrance 1 - id: 272 - area: 67 - coordinates: [8, 13] - teleporter: [142, 0] -- name: Alive Forest - West Entrance 2 - id: 273 - area: 67 - coordinates: [9, 13] - teleporter: [142, 0] -- name: Alive Forest - Giant Tree Entrance - id: 274 - area: 67 - coordinates: [42, 42] - teleporter: [143, 0] -- name: Alive Forest - Libra Teleporter Script - id: 275 - area: 67 - coordinates: [8, 52] - teleporter: [64, 8] -- name: Alive Forest - Gemini Teleporter Script - id: 276 - area: 67 - coordinates: [57, 49] - teleporter: [65, 8] -- name: Alive Forest - Mobius Teleporter Script - id: 277 - area: 67 - coordinates: [24, 10] - teleporter: [66, 8] -- name: Giant Tree 1F - Entrance Script 1 - id: 278 - area: 68 - coordinates: [18, 31] - teleporter: [56, 1] # The script is restored if no map shuffling [49, 8] -- name: Giant Tree 1F - Entrance Script 2 - id: 279 - area: 68 - coordinates: [19, 31] - teleporter: [56, 1] # Same [49, 8] -- name: Giant Tree 1F - North Entrance To 2F - id: 280 - area: 68 - coordinates: [16, 1] - teleporter: [144, 0] -- name: Giant Tree 2F Main Lobby - North Entrance to 1F - id: 281 - area: 69 - coordinates: [44, 33] - teleporter: [145, 0] -- name: Giant Tree 2F Main Lobby - Central Entrance to 3F - id: 282 - area: 69 - coordinates: [42, 47] - teleporter: [146, 0] -- name: Giant Tree 2F Main Lobby - West Entrance to Mushroom Room - id: 283 - area: 69 - coordinates: [58, 49] - teleporter: [149, 0] -- name: Giant Tree 2F West Ledge - To 3F Northwest Ledge - id: 284 - area: 69 - coordinates: [34, 37] - teleporter: [147, 0] -- name: Giant Tree 2F Fall From Vine Script - id: 482 - area: 69 - coordinates: [0x2E, 0x33] - teleporter: [76, 8] -- name: Giant Tree Meteor Chest Room - To 2F Mushroom Room - id: 285 - area: 69 - coordinates: [58, 44] - teleporter: [148, 0] -- name: Giant Tree 2F Mushroom Room - Entrance - id: 286 - area: 70 - coordinates: [55, 18] - teleporter: [150, 0] -- name: Giant Tree 2F Mushroom Room - North Face to Meteor - id: 287 - area: 70 - coordinates: [56, 7] - teleporter: [151, 0] -- name: Giant Tree 3F Central Room - Central Entrance to 2F - id: 288 - area: 71 - coordinates: [46, 53] - teleporter: [152, 0] -- name: Giant Tree 3F Central Room - East Entrance to Worm Room - id: 289 - area: 71 - coordinates: [58, 39] - teleporter: [153, 0] -- name: Giant Tree 3F Lower Corridor - Entrance from Worm Room - id: 290 - area: 71 - coordinates: [45, 39] - teleporter: [154, 0] -- name: Giant Tree 3F West Platform - Lower Entrance - id: 291 - area: 71 - coordinates: [33, 43] - teleporter: [155, 0] -- name: Giant Tree 3F West Platform - Top Entrance - id: 292 - area: 71 - coordinates: [52, 25] - teleporter: [156, 0] -- name: Giant Tree Worm Room - East Entrance - id: 293 - area: 72 - coordinates: [20, 58] - teleporter: [157, 0] -- name: Giant Tree Worm Room - West Entrance - id: 294 - area: 72 - coordinates: [6, 56] - teleporter: [158, 0] -- name: Giant Tree 4F Lower Floor - Entrance - id: 295 - area: 73 - coordinates: [20, 7] - teleporter: [159, 0] -- name: Giant Tree 4F Lower Floor - Lower West Mouth - id: 296 - area: 73 - coordinates: [8, 23] - teleporter: [160, 0] -- name: Giant Tree 4F Lower Floor - Lower Central Mouth - id: 297 - area: 73 - coordinates: [14, 25] - teleporter: [161, 0] -- name: Giant Tree 4F Lower Floor - Lower East Mouth - id: 298 - area: 73 - coordinates: [20, 25] - teleporter: [162, 0] -- name: Giant Tree 4F Upper Floor - Upper West Mouth - id: 299 - area: 73 - coordinates: [8, 19] - teleporter: [163, 0] -- name: Giant Tree 4F Upper Floor - Upper Central Mouth - id: 300 - area: 73 - coordinates: [12, 17] - teleporter: [164, 0] -- name: Giant Tree 4F Slime Room - Exit - id: 301 - area: 74 - coordinates: [47, 10] - teleporter: [165, 0] -- name: Giant Tree 4F Slime Room - West Entrance - id: 302 - area: 74 - coordinates: [45, 24] - teleporter: [166, 0] -- name: Giant Tree 4F Slime Room - Central Entrance - id: 303 - area: 74 - coordinates: [50, 24] - teleporter: [167, 0] -- name: Giant Tree 4F Slime Room - East Entrance - id: 304 - area: 74 - coordinates: [57, 28] - teleporter: [168, 0] -- name: Giant Tree 5F - Entrance - id: 305 - area: 75 - coordinates: [14, 51] - teleporter: [169, 0] -- name: Giant Tree 5F - Giant Tree Face # Unused - id: 306 - area: 75 - coordinates: [14, 37] - teleporter: [170, 0] -- name: Kaidge Temple - Entrance - id: 307 - area: 77 - coordinates: [44, 63] - teleporter: [18, 6] -- name: Kaidge Temple - Mobius Teleporter Script - id: 308 - area: 77 - coordinates: [35, 57] - teleporter: [71, 8] -- name: Windhole Temple - Entrance - id: 309 - area: 78 - coordinates: [10, 29] - teleporter: [173, 0] -- name: Mount Gale - Entrance 1 - id: 310 - area: 79 - coordinates: [1, 45] - teleporter: [174, 0] -- name: Mount Gale - Entrance 2 - id: 311 - area: 79 - coordinates: [2, 45] - teleporter: [174, 0] -- name: Mount Gale - Visit Quest - id: 494 - area: 79 - coordinates: [44, 7] - teleporter: [101, 8] -- name: Windia - Main Entrance 1 - id: 312 - area: 80 - coordinates: [12, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 2 - id: 313 - area: 80 - coordinates: [13, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 3 - id: 314 - area: 80 - coordinates: [14, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 4 - id: 315 - area: 80 - coordinates: [15, 40] - teleporter: [10, 6] -- name: Windia - Main Entrance 5 - id: 316 - area: 80 - coordinates: [12, 41] - teleporter: [10, 6] -- name: Windia - Main Entrance 6 - id: 317 - area: 80 - coordinates: [13, 41] - teleporter: [10, 6] -- name: Windia - Main Entrance 7 - id: 318 - area: 80 - coordinates: [14, 41] - teleporter: [10, 6] -- name: Windia - Main Entrance 8 - id: 319 - area: 80 - coordinates: [15, 41] - teleporter: [10, 6] -- name: Windia - Otto's House - id: 320 - area: 80 - coordinates: [21, 39] - teleporter: [30, 5] -- name: Windia - INN's Script # Change to teleporter / Change back to script! - id: 321 - area: 80 - coordinates: [18, 34] - teleporter: [97, 8] # Original value [79, 8] > [31, 2] -- name: Windia - Vendor House - id: 322 - area: 80 - coordinates: [8, 36] - teleporter: [32, 5] -- name: Windia - Kid House - id: 323 - area: 80 - coordinates: [7, 23] - teleporter: [176, 4] -- name: Windia - Old People House - id: 324 - area: 80 - coordinates: [19, 21] - teleporter: [177, 4] -- name: Windia - Rainbow Bridge Script - id: 325 - area: 80 - coordinates: [21, 9] - teleporter: [10, 6] # Change to entrance, usually a script [41, 8] -- name: Otto's House - Attic Stairs - id: 326 - area: 81 - coordinates: [2, 19] - teleporter: [33, 2] -- name: Otto's House - Entrance - id: 327 - area: 81 - coordinates: [9, 30] - teleporter: [106, 3] -- name: Otto's Attic - Stairs - id: 328 - area: 81 - coordinates: [26, 23] - teleporter: [107, 3] -- name: Windia Kid House - Entrance Script # Change to teleporter - id: 329 - area: 82 - coordinates: [7, 10] - teleporter: [178, 0] # Original value [38, 8] -- name: Windia Kid House - Basement Stairs - id: 330 - area: 82 - coordinates: [1, 4] - teleporter: [180, 0] -- name: Windia Old People House - Entrance - id: 331 - area: 82 - coordinates: [55, 12] - teleporter: [179, 0] -- name: Windia Old People House - Basement Stairs - id: 332 - area: 82 - coordinates: [60, 5] - teleporter: [181, 0] -- name: Windia Kid House Basement - Stairs - id: 333 - area: 82 - coordinates: [43, 8] - teleporter: [182, 0] -- name: Windia Kid House Basement - Mobius Teleporter - id: 334 - area: 82 - coordinates: [41, 9] - teleporter: [44, 8] -- name: Windia Old People House Basement - Stairs - id: 335 - area: 82 - coordinates: [39, 26] - teleporter: [183, 0] -- name: Windia Old People House Basement - Mobius Teleporter Script - id: 336 - area: 82 - coordinates: [39, 23] - teleporter: [43, 8] -- name: Windia Inn Lobby - Stairs to Beds - id: 337 - area: 82 - coordinates: [45, 24] - teleporter: [102, 8] # Changed to script, original value [215, 0] -- name: Windia Inn Lobby - Exit - id: 338 - area: 82 - coordinates: [53, 30] - teleporter: [135, 3] -- name: Windia Inn Beds - Stairs to Lobby - id: 339 - area: 82 - coordinates: [33, 59] - teleporter: [216, 0] -- name: Windia Vendor House - Entrance - id: 340 - area: 82 - coordinates: [29, 14] - teleporter: [108, 3] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 1 - id: 341 - area: 83 - coordinates: [47, 29] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 2 - id: 342 - area: 83 - coordinates: [47, 30] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 3 - id: 343 - area: 83 - coordinates: [48, 29] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - Main Entrance 4 - id: 344 - area: 83 - coordinates: [48, 30] - teleporter: [184, 0] -- name: Pazuzu Tower 1F Main Lobby - East Entrance - id: 345 - area: 83 - coordinates: [55, 12] - teleporter: [185, 0] -- name: Pazuzu Tower 1F Main Lobby - South Stairs - id: 346 - area: 83 - coordinates: [51, 25] - teleporter: [186, 0] -- name: Pazuzu Tower 1F Main Lobby - Pazuzu Script 1 - id: 347 - area: 83 - coordinates: [47, 8] - teleporter: [16, 8] -- name: Pazuzu Tower 1F Main Lobby - Pazuzu Script 2 - id: 348 - area: 83 - coordinates: [48, 8] - teleporter: [16, 8] -- name: Pazuzu Tower 1F Boxes Room - West Stairs - id: 349 - area: 83 - coordinates: [38, 17] - teleporter: [187, 0] -- name: Pazuzu 2F - West Upper Stairs - id: 350 - area: 84 - coordinates: [7, 11] - teleporter: [188, 0] -- name: Pazuzu 2F - South Stairs - id: 351 - area: 84 - coordinates: [20, 24] - teleporter: [189, 0] -- name: Pazuzu 2F - West Lower Stairs - id: 352 - area: 84 - coordinates: [6, 17] - teleporter: [190, 0] -- name: Pazuzu 2F - Central Stairs - id: 353 - area: 84 - coordinates: [15, 15] - teleporter: [191, 0] -- name: Pazuzu 2F - Pazuzu 1 - id: 354 - area: 84 - coordinates: [15, 8] - teleporter: [17, 8] -- name: Pazuzu 2F - Pazuzu 2 - id: 355 - area: 84 - coordinates: [16, 8] - teleporter: [17, 8] -- name: Pazuzu 3F Main Room - North Stairs - id: 356 - area: 85 - coordinates: [23, 11] - teleporter: [192, 0] -- name: Pazuzu 3F Main Room - West Stairs - id: 357 - area: 85 - coordinates: [7, 15] - teleporter: [193, 0] -- name: Pazuzu 3F Main Room - Pazuzu Script 1 - id: 358 - area: 85 - coordinates: [15, 8] - teleporter: [18, 8] -- name: Pazuzu 3F Main Room - Pazuzu Script 2 - id: 359 - area: 85 - coordinates: [16, 8] - teleporter: [18, 8] -- name: Pazuzu 3F Central Island - Central Stairs - id: 360 - area: 85 - coordinates: [15, 14] - teleporter: [194, 0] -- name: Pazuzu 3F Central Island - South Stairs - id: 361 - area: 85 - coordinates: [17, 25] - teleporter: [195, 0] -- name: Pazuzu 4F - Northwest Stairs - id: 362 - area: 86 - coordinates: [39, 12] - teleporter: [196, 0] -- name: Pazuzu 4F - Southwest Stairs - id: 363 - area: 86 - coordinates: [39, 19] - teleporter: [197, 0] -- name: Pazuzu 4F - South Stairs - id: 364 - area: 86 - coordinates: [47, 24] - teleporter: [198, 0] -- name: Pazuzu 4F - Northeast Stairs - id: 365 - area: 86 - coordinates: [54, 9] - teleporter: [199, 0] -- name: Pazuzu 4F - Pazuzu Script 1 - id: 366 - area: 86 - coordinates: [47, 8] - teleporter: [19, 8] -- name: Pazuzu 4F - Pazuzu Script 2 - id: 367 - area: 86 - coordinates: [48, 8] - teleporter: [19, 8] -- name: Pazuzu 5F Pazuzu Loop - West Stairs - id: 368 - area: 87 - coordinates: [9, 49] - teleporter: [200, 0] -- name: Pazuzu 5F Pazuzu Loop - South Stairs - id: 369 - area: 87 - coordinates: [16, 55] - teleporter: [201, 0] -- name: Pazuzu 5F Upper Loop - Northeast Stairs - id: 370 - area: 87 - coordinates: [22, 40] - teleporter: [202, 0] -- name: Pazuzu 5F Upper Loop - Northwest Stairs - id: 371 - area: 87 - coordinates: [9, 40] - teleporter: [203, 0] -- name: Pazuzu 5F Upper Loop - Pazuzu Script 1 - id: 372 - area: 87 - coordinates: [15, 40] - teleporter: [20, 8] -- name: Pazuzu 5F Upper Loop - Pazuzu Script 2 - id: 373 - area: 87 - coordinates: [16, 40] - teleporter: [20, 8] -- name: Pazuzu 6F - West Stairs - id: 374 - area: 88 - coordinates: [41, 47] - teleporter: [204, 0] -- name: Pazuzu 6F - Northwest Stairs - id: 375 - area: 88 - coordinates: [41, 40] - teleporter: [205, 0] -- name: Pazuzu 6F - Northeast Stairs - id: 376 - area: 88 - coordinates: [54, 40] - teleporter: [206, 0] -- name: Pazuzu 6F - South Stairs - id: 377 - area: 88 - coordinates: [52, 56] - teleporter: [207, 0] -- name: Pazuzu 6F - Pazuzu Script 1 - id: 378 - area: 88 - coordinates: [47, 40] - teleporter: [21, 8] -- name: Pazuzu 6F - Pazuzu Script 2 - id: 379 - area: 88 - coordinates: [48, 40] - teleporter: [21, 8] -- name: Pazuzu 7F Main Room - Southwest Stairs - id: 380 - area: 89 - coordinates: [15, 54] - teleporter: [26, 0] -- name: Pazuzu 7F Main Room - Northeast Stairs - id: 381 - area: 89 - coordinates: [21, 40] - teleporter: [27, 0] -- name: Pazuzu 7F Main Room - Southeast Stairs - id: 382 - area: 89 - coordinates: [21, 56] - teleporter: [28, 0] -- name: Pazuzu 7F Main Room - Pazuzu Script 1 - id: 383 - area: 89 - coordinates: [15, 44] - teleporter: [22, 8] -- name: Pazuzu 7F Main Room - Pazuzu Script 2 - id: 384 - area: 89 - coordinates: [16, 44] - teleporter: [22, 8] -- name: Pazuzu 7F Main Room - Crystal Script # Added for floor shuffle - id: 480 - area: 89 - coordinates: [15, 40] - teleporter: [38, 8] -- name: Pazuzu 1F to 3F - South Stairs - id: 385 - area: 90 - coordinates: [43, 60] - teleporter: [29, 0] -- name: Pazuzu 1F to 3F - North Stairs - id: 386 - area: 90 - coordinates: [43, 36] - teleporter: [30, 0] -- name: Pazuzu 3F to 5F - South Stairs - id: 387 - area: 91 - coordinates: [43, 60] - teleporter: [40, 0] -- name: Pazuzu 3F to 5F - North Stairs - id: 388 - area: 91 - coordinates: [43, 36] - teleporter: [41, 0] -- name: Pazuzu 5F to 7F - South Stairs - id: 389 - area: 92 - coordinates: [43, 60] - teleporter: [38, 0] -- name: Pazuzu 5F to 7F - North Stairs - id: 390 - area: 92 - coordinates: [43, 36] - teleporter: [39, 0] -- name: Pazuzu 2F to 4F - South Stairs - id: 391 - area: 93 - coordinates: [43, 60] - teleporter: [21, 0] -- name: Pazuzu 2F to 4F - North Stairs - id: 392 - area: 93 - coordinates: [43, 36] - teleporter: [22, 0] -- name: Pazuzu 4F to 6F - South Stairs - id: 393 - area: 94 - coordinates: [43, 60] - teleporter: [2, 0] -- name: Pazuzu 4F to 6F - North Stairs - id: 394 - area: 94 - coordinates: [43, 36] - teleporter: [3, 0] -- name: Light Temple - Entrance - id: 395 - area: 95 - coordinates: [28, 57] - teleporter: [19, 6] -- name: Light Temple - Mobius Teleporter Script - id: 396 - area: 95 - coordinates: [29, 37] - teleporter: [70, 8] -- name: Light Temple - Visit Quest Script 1 - id: 492 - area: 95 - coordinates: [34, 39] - teleporter: [100, 8] -- name: Light Temple - Visit Quest Script 2 - id: 493 - area: 95 - coordinates: [35, 39] - teleporter: [100, 8] -- name: Ship Dock - Mobius Teleporter Script - id: 397 - area: 96 - coordinates: [15, 18] - teleporter: [61, 8] -- name: Ship Dock - From Overworld - id: 398 - area: 96 - coordinates: [15, 11] - teleporter: [73, 0] -- name: Ship Dock - Entrance - id: 399 - area: 96 - coordinates: [15, 23] - teleporter: [17, 6] -- name: Mac Ship Deck - East Entrance Script - id: 400 - area: 97 - coordinates: [26, 40] - teleporter: [37, 8] -- name: Mac Ship Deck - Central Stairs Script - id: 401 - area: 97 - coordinates: [16, 47] - teleporter: [50, 8] -- name: Mac Ship Deck - West Stairs Script - id: 402 - area: 97 - coordinates: [8, 34] - teleporter: [51, 8] -- name: Mac Ship Deck - East Stairs Script - id: 403 - area: 97 - coordinates: [24, 36] - teleporter: [52, 8] -- name: Mac Ship Deck - North Stairs Script - id: 404 - area: 97 - coordinates: [12, 9] - teleporter: [53, 8] -- name: Mac Ship B1 Outer Ring - South Stairs - id: 405 - area: 98 - coordinates: [16, 45] - teleporter: [208, 0] -- name: Mac Ship B1 Outer Ring - West Stairs - id: 406 - area: 98 - coordinates: [8, 35] - teleporter: [175, 0] -- name: Mac Ship B1 Outer Ring - East Stairs - id: 407 - area: 98 - coordinates: [25, 37] - teleporter: [172, 0] -- name: Mac Ship B1 Outer Ring - Northwest Stairs - id: 408 - area: 98 - coordinates: [10, 23] - teleporter: [88, 0] -- name: Mac Ship B1 Square Room - North Stairs - id: 409 - area: 98 - coordinates: [14, 9] - teleporter: [141, 0] -- name: Mac Ship B1 Square Room - South Stairs - id: 410 - area: 98 - coordinates: [16, 12] - teleporter: [87, 0] -- name: Mac Ship B1 Mac Room - Stairs # Unused? - id: 411 - area: 98 - coordinates: [16, 51] - teleporter: [101, 0] -- name: Mac Ship B1 Central Corridor - South Stairs - id: 412 - area: 98 - coordinates: [16, 38] - teleporter: [102, 0] -- name: Mac Ship B1 Central Corridor - North Stairs - id: 413 - area: 98 - coordinates: [16, 26] - teleporter: [86, 0] -- name: Mac Ship B2 South Corridor - South Stairs - id: 414 - area: 99 - coordinates: [48, 51] - teleporter: [57, 1] -- name: Mac Ship B2 South Corridor - North Stairs Script - id: 415 - area: 99 - coordinates: [48, 38] - teleporter: [55, 8] -- name: Mac Ship B2 North Corridor - South Stairs Script - id: 416 - area: 99 - coordinates: [48, 27] - teleporter: [56, 8] -- name: Mac Ship B2 North Corridor - North Stairs Script - id: 417 - area: 99 - coordinates: [48, 12] - teleporter: [57, 8] -- name: Mac Ship B2 Outer Ring - Northwest Stairs Script - id: 418 - area: 99 - coordinates: [55, 11] - teleporter: [58, 8] -- name: Mac Ship B1 Outer Ring Cleared - South Stairs - id: 419 - area: 100 - coordinates: [16, 45] - teleporter: [208, 0] -- name: Mac Ship B1 Outer Ring Cleared - West Stairs - id: 420 - area: 100 - coordinates: [8, 35] - teleporter: [175, 0] -- name: Mac Ship B1 Outer Ring Cleared - East Stairs - id: 421 - area: 100 - coordinates: [25, 37] - teleporter: [172, 0] -- name: Mac Ship B1 Square Room Cleared - North Stairs - id: 422 - area: 100 - coordinates: [14, 9] - teleporter: [141, 0] -- name: Mac Ship B1 Square Room Cleared - South Stairs - id: 423 - area: 100 - coordinates: [16, 12] - teleporter: [87, 0] -- name: Mac Ship B1 Mac Room Cleared - Main Stairs - id: 424 - area: 100 - coordinates: [16, 51] - teleporter: [101, 0] -- name: Mac Ship B1 Central Corridor Cleared - South Stairs - id: 425 - area: 100 - coordinates: [16, 38] - teleporter: [102, 0] -- name: Mac Ship B1 Central Corridor Cleared - North Stairs - id: 426 - area: 100 - coordinates: [16, 26] - teleporter: [86, 0] -- name: Mac Ship B1 Central Corridor Cleared - Northwest Stairs - id: 427 - area: 100 - coordinates: [23, 10] - teleporter: [88, 0] -- name: Doom Castle Corridor of Destiny - South Entrance - id: 428 - area: 101 - coordinates: [59, 29] - teleporter: [84, 0] -- name: Doom Castle Corridor of Destiny - Ice Floor Entrance - id: 429 - area: 101 - coordinates: [59, 21] - teleporter: [35, 2] -- name: Doom Castle Corridor of Destiny - Lava Floor Entrance - id: 430 - area: 101 - coordinates: [59, 13] - teleporter: [209, 0] -- name: Doom Castle Corridor of Destiny - Sky Floor Entrance - id: 431 - area: 101 - coordinates: [59, 5] - teleporter: [211, 0] -- name: Doom Castle Corridor of Destiny - Hero Room Entrance - id: 432 - area: 101 - coordinates: [59, 61] - teleporter: [13, 2] -- name: Doom Castle Ice Floor - Entrance - id: 433 - area: 102 - coordinates: [23, 42] - teleporter: [109, 3] -- name: Doom Castle Lava Floor - Entrance - id: 434 - area: 103 - coordinates: [23, 40] - teleporter: [210, 0] -- name: Doom Castle Sky Floor - Entrance - id: 435 - area: 104 - coordinates: [24, 41] - teleporter: [212, 0] -- name: Doom Castle Hero Room - Dark King Entrance 1 - id: 436 - area: 106 - coordinates: [15, 5] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Dark King Entrance 2 - id: 437 - area: 106 - coordinates: [16, 5] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Dark King Entrance 3 - id: 438 - area: 106 - coordinates: [15, 4] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Dark King Entrance 4 - id: 439 - area: 106 - coordinates: [16, 4] - teleporter: [54, 0] -- name: Doom Castle Hero Room - Hero Statue Script - id: 440 - area: 106 - coordinates: [15, 17] - teleporter: [24, 8] -- name: Doom Castle Hero Room - Entrance - id: 441 - area: 106 - coordinates: [15, 24] - teleporter: [110, 3] -- name: Doom Castle Dark King Room - Entrance - id: 442 - area: 107 - coordinates: [14, 26] - teleporter: [52, 0] -- name: Doom Castle Dark King Room - Dark King Script - id: 443 - area: 107 - coordinates: [14, 15] - teleporter: [25, 8] -- name: Doom Castle Dark King Room - Unknown - id: 444 - area: 107 - coordinates: [47, 54] - teleporter: [77, 0] -- name: Overworld - Level Forest - id: 445 - area: 0 - type: "Overworld" - teleporter: [0x2E, 8] -- name: Overworld - Foresta - id: 446 - area: 0 - type: "Overworld" - teleporter: [0x02, 1] -- name: Overworld - Sand Temple - id: 447 - area: 0 - type: "Overworld" - teleporter: [0x03, 1] -- name: Overworld - Bone Dungeon - id: 448 - area: 0 - type: "Overworld" - teleporter: [0x04, 1] -- name: Overworld - Focus Tower Foresta - id: 449 - area: 0 - type: "Overworld" - teleporter: [0x05, 1] -- name: Overworld - Focus Tower Aquaria - id: 450 - area: 0 - type: "Overworld" - teleporter: [0x13, 1] -- name: Overworld - Libra Temple - id: 451 - area: 0 - type: "Overworld" - teleporter: [0x07, 1] -- name: Overworld - Aquaria - id: 452 - area: 0 - type: "Overworld" - teleporter: [0x08, 8] -- name: Overworld - Wintry Cave - id: 453 - area: 0 - type: "Overworld" - teleporter: [0x0A, 1] -- name: Overworld - Life Temple - id: 454 - area: 0 - type: "Overworld" - teleporter: [0x0B, 1] -- name: Overworld - Falls Basin - id: 455 - area: 0 - type: "Overworld" - teleporter: [0x0C, 1] -- name: Overworld - Ice Pyramid - id: 456 - area: 0 - type: "Overworld" - teleporter: [0x0D, 1] # Will be switched to a script -- name: Overworld - Spencer's Place - id: 457 - area: 0 - type: "Overworld" - teleporter: [0x30, 8] -- name: Overworld - Wintry Temple - id: 458 - area: 0 - type: "Overworld" - teleporter: [0x10, 1] -- name: Overworld - Focus Tower Frozen Strip - id: 459 - area: 0 - type: "Overworld" - teleporter: [0x11, 1] -- name: Overworld - Focus Tower Fireburg - id: 460 - area: 0 - type: "Overworld" - teleporter: [0x12, 1] -- name: Overworld - Fireburg - id: 461 - area: 0 - type: "Overworld" - teleporter: [0x14, 1] -- name: Overworld - Mine - id: 462 - area: 0 - type: "Overworld" - teleporter: [0x15, 1] -- name: Overworld - Sealed Temple - id: 463 - area: 0 - type: "Overworld" - teleporter: [0x16, 1] -- name: Overworld - Volcano - id: 464 - area: 0 - type: "Overworld" - teleporter: [0x17, 1] -- name: Overworld - Lava Dome - id: 465 - area: 0 - type: "Overworld" - teleporter: [0x18, 1] -- name: Overworld - Focus Tower Windia - id: 466 - area: 0 - type: "Overworld" - teleporter: [0x06, 1] -- name: Overworld - Rope Bridge - id: 467 - area: 0 - type: "Overworld" - teleporter: [0x19, 1] -- name: Overworld - Alive Forest - id: 468 - area: 0 - type: "Overworld" - teleporter: [0x1A, 1] -- name: Overworld - Giant Tree - id: 469 - area: 0 - type: "Overworld" - teleporter: [0x1B, 1] -- name: Overworld - Kaidge Temple - id: 470 - area: 0 - type: "Overworld" - teleporter: [0x1C, 1] -- name: Overworld - Windia - id: 471 - area: 0 - type: "Overworld" - teleporter: [0x1D, 1] -- name: Overworld - Windhole Temple - id: 472 - area: 0 - type: "Overworld" - teleporter: [0x1E, 1] -- name: Overworld - Mount Gale - id: 473 - area: 0 - type: "Overworld" - teleporter: [0x1F, 1] -- name: Overworld - Pazuzu Tower - id: 474 - area: 0 - type: "Overworld" - teleporter: [0x20, 1] -- name: Overworld - Ship Dock - id: 475 - area: 0 - type: "Overworld" - teleporter: [0x3E, 1] -- name: Overworld - Doom Castle - id: 476 - area: 0 - type: "Overworld" - teleporter: [0x21, 1] -- name: Overworld - Light Temple - id: 477 - area: 0 - type: "Overworld" - teleporter: [0x22, 1] -- name: Overworld - Mac Ship - id: 478 - area: 0 - type: "Overworld" - teleporter: [0x24, 1] -- name: Overworld - Mac Ship Doom - id: 479 - area: 0 - type: "Overworld" - teleporter: [0x24, 1] -- name: Dummy House - Bed Script - id: 480 - area: 17 - coordinates: [0x28, 0x38] - teleporter: [1, 8] -- name: Dummy House - Entrance - id: 481 - area: 17 - coordinates: [0x29, 0x3B] - teleporter: [0, 10] #None diff --git a/worlds/ffmq/data/rooms.py b/worlds/ffmq/data/rooms.py new file mode 100644 index 00000000000..38634f10767 --- /dev/null +++ b/worlds/ffmq/data/rooms.py @@ -0,0 +1,2 @@ +rooms = [{'name': 'Overworld', 'id': 0, 'type': 'Overworld', 'game_objects': [], 'links': [{'target_room': 220, 'access': []}]}, {'name': 'Subregion Foresta', 'id': 220, 'type': 'Subregion', 'region': 'Foresta', 'game_objects': [{'name': 'Foresta South Battlefield', 'object_id': 1, 'location': 'ForestaSouthBattlefield', 'location_slot': 'ForestaSouthBattlefield', 'type': 'BattlefieldXp', 'access': []}, {'name': 'Foresta West Battlefield', 'object_id': 2, 'location': 'ForestaWestBattlefield', 'location_slot': 'ForestaWestBattlefield', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Foresta East Battlefield', 'object_id': 3, 'location': 'ForestaEastBattlefield', 'location_slot': 'ForestaEastBattlefield', 'type': 'BattlefieldGp', 'access': []}], 'links': [{'target_room': 15, 'location': 'LevelForest', 'location_slot': 'LevelForest', 'entrance': 445, 'teleporter': [46, 8], 'access': []}, {'target_room': 16, 'location': 'Foresta', 'location_slot': 'Foresta', 'entrance': 446, 'teleporter': [2, 1], 'access': []}, {'target_room': 24, 'location': 'SandTemple', 'location_slot': 'SandTemple', 'entrance': 447, 'teleporter': [3, 1], 'access': []}, {'target_room': 25, 'location': 'BoneDungeon', 'location_slot': 'BoneDungeon', 'entrance': 448, 'teleporter': [4, 1], 'access': []}, {'target_room': 3, 'location': 'FocusTowerForesta', 'location_slot': 'FocusTowerForesta', 'entrance': 449, 'teleporter': [5, 1], 'access': []}, {'target_room': 221, 'access': ['SandCoin']}, {'target_room': 224, 'access': ['RiverCoin']}, {'target_room': 226, 'access': ['SunCoin']}]}, {'name': 'Subregion Aquaria', 'id': 221, 'type': 'Subregion', 'region': 'Aquaria', 'game_objects': [{'name': 'South of Libra Temple Battlefield', 'object_id': 4, 'location': 'AquariaBattlefield01', 'location_slot': 'AquariaBattlefield01', 'type': 'BattlefieldXp', 'access': []}, {'name': 'East of Libra Temple Battlefield', 'object_id': 5, 'location': 'AquariaBattlefield02', 'location_slot': 'AquariaBattlefield02', 'type': 'BattlefieldGp', 'access': []}, {'name': 'South of Aquaria Battlefield', 'object_id': 6, 'location': 'AquariaBattlefield03', 'location_slot': 'AquariaBattlefield03', 'type': 'BattlefieldItem', 'access': []}, {'name': 'South of Wintry Cave Battlefield', 'object_id': 7, 'location': 'WintryBattlefield01', 'location_slot': 'WintryBattlefield01', 'type': 'BattlefieldXp', 'access': []}, {'name': 'West of Wintry Cave Battlefield', 'object_id': 8, 'location': 'WintryBattlefield02', 'location_slot': 'WintryBattlefield02', 'type': 'BattlefieldGp', 'access': []}, {'name': 'Ice Pyramid Battlefield', 'object_id': 9, 'location': 'PyramidBattlefield01', 'location_slot': 'PyramidBattlefield01', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 10, 'location': 'FocusTowerAquaria', 'location_slot': 'FocusTowerAquaria', 'entrance': 450, 'teleporter': [19, 1], 'access': []}, {'target_room': 39, 'location': 'LibraTemple', 'location_slot': 'LibraTemple', 'entrance': 451, 'teleporter': [7, 1], 'access': []}, {'target_room': 40, 'location': 'Aquaria', 'location_slot': 'Aquaria', 'entrance': 452, 'teleporter': [8, 8], 'access': []}, {'target_room': 45, 'location': 'WintryCave', 'location_slot': 'WintryCave', 'entrance': 453, 'teleporter': [10, 1], 'access': []}, {'target_room': 52, 'location': 'FallsBasin', 'location_slot': 'FallsBasin', 'entrance': 455, 'teleporter': [12, 1], 'access': []}, {'target_room': 54, 'location': 'IcePyramid', 'location_slot': 'IcePyramid', 'entrance': 456, 'teleporter': [13, 1], 'access': []}, {'target_room': 220, 'access': ['SandCoin']}, {'target_room': 224, 'access': ['SandCoin', 'RiverCoin']}, {'target_room': 226, 'access': ['SandCoin', 'SunCoin']}, {'target_room': 223, 'access': ['SummerAquaria']}]}, {'name': 'Subregion Life Temple', 'id': 222, 'type': 'Subregion', 'region': 'LifeTemple', 'game_objects': [], 'links': [{'target_room': 51, 'location': 'LifeTemple', 'location_slot': 'LifeTemple', 'entrance': 454, 'teleporter': [11, 1], 'access': []}]}, {'name': 'Subregion Frozen Fields', 'id': 223, 'type': 'Subregion', 'region': 'AquariaFrozenField', 'game_objects': [{'name': 'North of Libra Temple Battlefield', 'object_id': 10, 'location': 'LibraBattlefield01', 'location_slot': 'LibraBattlefield01', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Aquaria Frozen Field Battlefield', 'object_id': 11, 'location': 'LibraBattlefield02', 'location_slot': 'LibraBattlefield02', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 74, 'location': 'WintryTemple', 'location_slot': 'WintryTemple', 'entrance': 458, 'teleporter': [16, 1], 'access': []}, {'target_room': 14, 'location': 'FocusTowerFrozen', 'location_slot': 'FocusTowerFrozen', 'entrance': 459, 'teleporter': [17, 1], 'access': []}, {'target_room': 221, 'access': []}, {'target_room': 225, 'access': ['SummerAquaria', 'DualheadHydra']}]}, {'name': 'Subregion Fireburg', 'id': 224, 'type': 'Subregion', 'region': 'Fireburg', 'game_objects': [{'name': 'Path to Fireburg Southern Battlefield', 'object_id': 12, 'location': 'FireburgBattlefield01', 'location_slot': 'FireburgBattlefield01', 'type': 'BattlefieldGp', 'access': []}, {'name': 'Path to Fireburg Central Battlefield', 'object_id': 13, 'location': 'FireburgBattlefield02', 'location_slot': 'FireburgBattlefield02', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Path to Fireburg Northern Battlefield', 'object_id': 14, 'location': 'FireburgBattlefield03', 'location_slot': 'FireburgBattlefield03', 'type': 'BattlefieldXp', 'access': []}, {'name': 'Sealed Temple Battlefield', 'object_id': 15, 'location': 'MineBattlefield01', 'location_slot': 'MineBattlefield01', 'type': 'BattlefieldGp', 'access': []}, {'name': 'Mine Battlefield', 'object_id': 16, 'location': 'MineBattlefield02', 'location_slot': 'MineBattlefield02', 'type': 'BattlefieldItem', 'access': []}, {'name': 'Boulder Battlefield', 'object_id': 17, 'location': 'MineBattlefield03', 'location_slot': 'MineBattlefield03', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 13, 'location': 'FocusTowerFireburg', 'location_slot': 'FocusTowerFireburg', 'entrance': 460, 'teleporter': [18, 1], 'access': []}, {'target_room': 76, 'location': 'Fireburg', 'location_slot': 'Fireburg', 'entrance': 461, 'teleporter': [20, 1], 'access': []}, {'target_room': 84, 'location': 'Mine', 'location_slot': 'Mine', 'entrance': 462, 'teleporter': [21, 1], 'access': []}, {'target_room': 92, 'location': 'SealedTemple', 'location_slot': 'SealedTemple', 'entrance': 463, 'teleporter': [22, 1], 'access': []}, {'target_room': 93, 'location': 'Volcano', 'location_slot': 'Volcano', 'entrance': 464, 'teleporter': [23, 1], 'access': []}, {'target_room': 100, 'location': 'LavaDome', 'location_slot': 'LavaDome', 'entrance': 465, 'teleporter': [24, 1], 'access': []}, {'target_room': 220, 'access': ['RiverCoin']}, {'target_room': 221, 'access': ['SandCoin', 'RiverCoin']}, {'target_room': 226, 'access': ['RiverCoin', 'SunCoin']}, {'target_room': 225, 'access': ['DualheadHydra']}]}, {'name': 'Subregion Volcano Battlefield', 'id': 225, 'type': 'Subregion', 'region': 'VolcanoBattlefield', 'game_objects': [{'name': 'Volcano Battlefield', 'object_id': 18, 'location': 'VolcanoBattlefield01', 'location_slot': 'VolcanoBattlefield01', 'type': 'BattlefieldXp', 'access': []}], 'links': [{'target_room': 224, 'access': ['DualheadHydra']}, {'target_room': 223, 'access': ['SummerAquaria']}]}, {'name': 'Subregion Windia', 'id': 226, 'type': 'Subregion', 'region': 'Windia', 'game_objects': [{'name': 'Kaidge Temple Battlefield', 'object_id': 19, 'location': 'WindiaBattlefield01', 'location_slot': 'WindiaBattlefield01', 'type': 'BattlefieldXp', 'access': ['SandCoin', 'RiverCoin']}, {'name': 'South of Windia Battlefield', 'object_id': 20, 'location': 'WindiaBattlefield02', 'location_slot': 'WindiaBattlefield02', 'type': 'BattlefieldXp', 'access': ['SandCoin', 'RiverCoin']}], 'links': [{'target_room': 9, 'location': 'FocusTowerWindia', 'location_slot': 'FocusTowerWindia', 'entrance': 466, 'teleporter': [6, 1], 'access': []}, {'target_room': 123, 'location': 'RopeBridge', 'location_slot': 'RopeBridge', 'entrance': 467, 'teleporter': [25, 1], 'access': []}, {'target_room': 124, 'location': 'AliveForest', 'location_slot': 'AliveForest', 'entrance': 468, 'teleporter': [26, 1], 'access': []}, {'target_room': 125, 'location': 'GiantTree', 'location_slot': 'GiantTree', 'entrance': 469, 'teleporter': [27, 1], 'access': ['Barred']}, {'target_room': 152, 'location': 'KaidgeTemple', 'location_slot': 'KaidgeTemple', 'entrance': 470, 'teleporter': [28, 1], 'access': []}, {'target_room': 156, 'location': 'Windia', 'location_slot': 'Windia', 'entrance': 471, 'teleporter': [29, 1], 'access': []}, {'target_room': 154, 'location': 'WindholeTemple', 'location_slot': 'WindholeTemple', 'entrance': 472, 'teleporter': [30, 1], 'access': []}, {'target_room': 155, 'location': 'MountGale', 'location_slot': 'MountGale', 'entrance': 473, 'teleporter': [31, 1], 'access': []}, {'target_room': 166, 'location': 'PazuzusTower', 'location_slot': 'PazuzusTower', 'entrance': 474, 'teleporter': [32, 1], 'access': []}, {'target_room': 220, 'access': ['SunCoin']}, {'target_room': 221, 'access': ['SandCoin', 'SunCoin']}, {'target_room': 224, 'access': ['RiverCoin', 'SunCoin']}, {'target_room': 227, 'access': ['RainbowBridge']}]}, {'name': "Subregion Spencer's Cave", 'id': 227, 'type': 'Subregion', 'region': 'SpencerCave', 'game_objects': [], 'links': [{'target_room': 73, 'location': 'SpencersPlace', 'location_slot': 'SpencersPlace', 'entrance': 457, 'teleporter': [48, 8], 'access': []}, {'target_room': 226, 'access': ['RainbowBridge']}]}, {'name': 'Subregion Ship Dock', 'id': 228, 'type': 'Subregion', 'region': 'ShipDock', 'game_objects': [], 'links': [{'target_room': 186, 'location': 'ShipDock', 'location_slot': 'ShipDock', 'entrance': 475, 'teleporter': [62, 1], 'access': []}, {'target_room': 229, 'access': ['ShipLiberated', 'ShipDockAccess']}]}, {'name': "Subregion Mac's Ship", 'id': 229, 'type': 'Subregion', 'region': 'MacShip', 'game_objects': [], 'links': [{'target_room': 187, 'location': 'MacsShip', 'location_slot': 'MacsShip', 'entrance': 478, 'teleporter': [36, 1], 'access': []}, {'target_room': 228, 'access': ['ShipLiberated', 'ShipDockAccess']}, {'target_room': 231, 'access': ['ShipLoaned', 'ShipDockAccess', 'ShipSteeringWheel']}]}, {'name': 'Subregion Light Temple', 'id': 230, 'type': 'Subregion', 'region': 'LightTemple', 'game_objects': [], 'links': [{'target_room': 185, 'location': 'LightTemple', 'location_slot': 'LightTemple', 'entrance': 477, 'teleporter': [35, 1], 'access': []}]}, {'name': 'Subregion Doom Castle', 'id': 231, 'type': 'Subregion', 'region': 'DoomCastle', 'game_objects': [], 'links': [{'target_room': 1, 'location': 'DoomCastle', 'location_slot': 'DoomCastle', 'entrance': 476, 'teleporter': [33, 1], 'access': []}, {'target_room': 187, 'location': 'MacsShipDoom', 'location_slot': 'MacsShipDoom', 'entrance': 479, 'teleporter': [36, 1], 'access': ['Barred']}, {'target_room': 229, 'access': ['ShipLoaned', 'ShipDockAccess', 'ShipSteeringWheel']}]}, {'name': 'Doom Castle - Sand Floor', 'id': 1, 'game_objects': [{'name': 'Doom Castle B2 - Southeast Chest', 'object_id': 1, 'type': 'Chest', 'access': ['Bomb']}, {'name': 'Doom Castle B2 - Bone Ledge Box', 'object_id': 30, 'type': 'Box', 'access': []}, {'name': 'Doom Castle B2 - Hook Platform Box', 'object_id': 31, 'type': 'Box', 'access': ['DragonClaw']}], 'links': [{'target_room': 231, 'entrance': 1, 'teleporter': [1, 6], 'access': []}, {'target_room': 5, 'entrance': 0, 'teleporter': [0, 0], 'access': ['DragonClaw', 'MegaGrenade']}]}, {'name': 'Doom Castle - Aero Room', 'id': 2, 'game_objects': [{'name': 'Doom Castle B2 - Sun Door Chest', 'object_id': 0, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 4, 'entrance': 2, 'teleporter': [1, 0], 'access': []}]}, {'name': 'Focus Tower B1 - Main Loop', 'id': 3, 'game_objects': [], 'links': [{'target_room': 220, 'entrance': 3, 'teleporter': [2, 6], 'access': []}, {'target_room': 6, 'entrance': 4, 'teleporter': [4, 0], 'access': []}]}, {'name': 'Focus Tower B1 - Aero Corridor', 'id': 4, 'game_objects': [], 'links': [{'target_room': 9, 'entrance': 5, 'teleporter': [5, 0], 'access': []}, {'target_room': 2, 'entrance': 6, 'teleporter': [8, 0], 'access': []}]}, {'name': 'Focus Tower B1 - Inner Loop', 'id': 5, 'game_objects': [], 'links': [{'target_room': 1, 'entrance': 8, 'teleporter': [7, 0], 'access': []}, {'target_room': 201, 'entrance': 7, 'teleporter': [6, 0], 'access': []}]}, {'name': 'Focus Tower 1F Main Lobby', 'id': 6, 'game_objects': [{'name': 'Focus Tower 1F - Main Lobby Box', 'object_id': 33, 'type': 'Box', 'access': []}], 'links': [{'target_room': 3, 'entrance': 11, 'teleporter': [11, 0], 'access': []}, {'target_room': 7, 'access': ['SandCoin']}, {'target_room': 8, 'access': ['RiverCoin']}, {'target_room': 9, 'access': ['SunCoin']}]}, {'name': 'Focus Tower 1F SandCoin Room', 'id': 7, 'game_objects': [], 'links': [{'target_room': 6, 'access': ['SandCoin']}, {'target_room': 10, 'entrance': 10, 'teleporter': [10, 0], 'access': []}]}, {'name': 'Focus Tower 1F RiverCoin Room', 'id': 8, 'game_objects': [], 'links': [{'target_room': 6, 'access': ['RiverCoin']}, {'target_room': 11, 'entrance': 14, 'teleporter': [14, 0], 'access': []}]}, {'name': 'Focus Tower 1F SunCoin Room', 'id': 9, 'game_objects': [], 'links': [{'target_room': 6, 'access': ['SunCoin']}, {'target_room': 4, 'entrance': 12, 'teleporter': [12, 0], 'access': []}, {'target_room': 226, 'entrance': 9, 'teleporter': [3, 6], 'access': []}]}, {'name': 'Focus Tower 1F SkyCoin Room', 'id': 201, 'game_objects': [], 'links': [{'target_room': 195, 'entrance': 13, 'teleporter': [13, 0], 'access': ['SkyCoin', 'FlamerusRex', 'IceGolem', 'DualheadHydra', 'Pazuzu']}, {'target_room': 5, 'entrance': 15, 'teleporter': [15, 0], 'access': []}]}, {'name': 'Focus Tower 2F - Sand Coin Passage', 'id': 10, 'game_objects': [{'name': 'Focus Tower 2F - Sand Door Chest', 'object_id': 3, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 221, 'entrance': 16, 'teleporter': [4, 6], 'access': []}, {'target_room': 7, 'entrance': 17, 'teleporter': [17, 0], 'access': []}]}, {'name': 'Focus Tower 2F - River Coin Passage', 'id': 11, 'game_objects': [], 'links': [{'target_room': 8, 'entrance': 18, 'teleporter': [18, 0], 'access': []}, {'target_room': 13, 'entrance': 19, 'teleporter': [20, 0], 'access': []}]}, {'name': 'Focus Tower 2F - Venus Chest Room', 'id': 12, 'game_objects': [{'name': 'Focus Tower 2F - Back Door Chest', 'object_id': 2, 'type': 'Chest', 'access': []}, {'name': 'Focus Tower 2F - Venus Chest', 'object_id': 9, 'type': 'NPC', 'access': ['Bomb', 'VenusKey']}], 'links': [{'target_room': 14, 'entrance': 20, 'teleporter': [19, 0], 'access': []}]}, {'name': 'Focus Tower 3F - Lower Floor', 'id': 13, 'game_objects': [{'name': 'Focus Tower 3F - River Door Box', 'object_id': 34, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 22, 'teleporter': [6, 6], 'access': []}, {'target_room': 11, 'entrance': 23, 'teleporter': [24, 0], 'access': []}]}, {'name': 'Focus Tower 3F - Upper Floor', 'id': 14, 'game_objects': [], 'links': [{'target_room': 223, 'entrance': 24, 'teleporter': [5, 6], 'access': []}, {'target_room': 12, 'entrance': 25, 'teleporter': [23, 0], 'access': []}]}, {'name': 'Level Forest', 'id': 15, 'game_objects': [{'name': 'Level Forest - Northwest Box', 'object_id': 40, 'type': 'Box', 'access': ['Axe']}, {'name': 'Level Forest - Northeast Box', 'object_id': 41, 'type': 'Box', 'access': ['Axe']}, {'name': 'Level Forest - Middle Box', 'object_id': 42, 'type': 'Box', 'access': []}, {'name': 'Level Forest - Southwest Box', 'object_id': 43, 'type': 'Box', 'access': ['Axe']}, {'name': 'Level Forest - Southeast Box', 'object_id': 44, 'type': 'Box', 'access': ['Axe']}, {'name': 'Minotaur', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Minotaur'], 'access': ['Kaeli1']}, {'name': 'Level Forest - Old Man', 'object_id': 0, 'type': 'NPC', 'access': []}, {'name': 'Level Forest - Kaeli', 'object_id': 1, 'type': 'NPC', 'access': ['Kaeli1', 'Minotaur']}], 'links': [{'target_room': 220, 'entrance': 28, 'teleporter': [25, 0], 'access': []}]}, {'name': 'Foresta', 'id': 16, 'game_objects': [{'name': 'Foresta - Outside Box', 'object_id': 45, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 220, 'entrance': 38, 'teleporter': [31, 0], 'access': []}, {'target_room': 17, 'entrance': 44, 'teleporter': [0, 5], 'access': []}, {'target_room': 18, 'entrance': 42, 'teleporter': [32, 4], 'access': []}, {'target_room': 19, 'entrance': 43, 'teleporter': [33, 0], 'access': []}, {'target_room': 20, 'entrance': 45, 'teleporter': [1, 5], 'access': []}]}, {'name': "Kaeli's House", 'id': 17, 'game_objects': [{'name': "Foresta - Kaeli's House Box", 'object_id': 46, 'type': 'Box', 'access': []}, {'name': 'Kaeli Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Kaeli1'], 'access': ['TreeWither']}, {'name': 'Kaeli 2', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Kaeli2'], 'access': ['Kaeli1', 'Minotaur', 'Elixir']}], 'links': [{'target_room': 16, 'entrance': 46, 'teleporter': [86, 3], 'access': []}]}, {'name': "Foresta Houses - Old Man's House Main", 'id': 18, 'game_objects': [], 'links': [{'target_room': 19, 'access': ['BarrelPushed']}, {'target_room': 16, 'entrance': 47, 'teleporter': [34, 0], 'access': []}]}, {'name': "Foresta Houses - Old Man's House Back", 'id': 19, 'game_objects': [{'name': 'Foresta - Old Man House Chest', 'object_id': 5, 'type': 'Chest', 'access': []}, {'name': 'Old Man Barrel', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['BarrelPushed'], 'access': []}], 'links': [{'target_room': 18, 'access': ['BarrelPushed']}, {'target_room': 16, 'entrance': 48, 'teleporter': [35, 0], 'access': []}]}, {'name': 'Foresta Houses - Rest House', 'id': 20, 'game_objects': [{'name': 'Foresta - Rest House Box', 'object_id': 47, 'type': 'Box', 'access': []}], 'links': [{'target_room': 16, 'entrance': 50, 'teleporter': [87, 3], 'access': []}]}, {'name': 'Libra Treehouse', 'id': 21, 'game_objects': [{'name': 'Alive Forest - Libra Treehouse Box', 'object_id': 50, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 51, 'teleporter': [67, 8], 'access': ['LibraCrest']}]}, {'name': 'Gemini Treehouse', 'id': 22, 'game_objects': [{'name': 'Alive Forest - Gemini Treehouse Box', 'object_id': 51, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 52, 'teleporter': [68, 8], 'access': ['GeminiCrest']}]}, {'name': 'Mobius Treehouse', 'id': 23, 'game_objects': [{'name': 'Alive Forest - Mobius Treehouse West Box', 'object_id': 48, 'type': 'Box', 'access': []}, {'name': 'Alive Forest - Mobius Treehouse East Box', 'object_id': 49, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 53, 'teleporter': [69, 8], 'access': ['MobiusCrest']}]}, {'name': 'Sand Temple', 'id': 24, 'game_objects': [{'name': 'Tristam Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Tristam'], 'access': []}], 'links': [{'target_room': 220, 'entrance': 54, 'teleporter': [36, 0], 'access': []}]}, {'name': 'Bone Dungeon 1F', 'id': 25, 'game_objects': [{'name': 'Bone Dungeon 1F - Entrance Room West Box', 'object_id': 53, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon 1F - Entrance Room Middle Box', 'object_id': 54, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon 1F - Entrance Room East Box', 'object_id': 55, 'type': 'Box', 'access': []}], 'links': [{'target_room': 220, 'entrance': 55, 'teleporter': [37, 0], 'access': []}, {'target_room': 26, 'entrance': 56, 'teleporter': [2, 2], 'access': []}]}, {'name': 'Bone Dungeon B1 - Waterway', 'id': 26, 'game_objects': [{'name': 'Bone Dungeon B1 - Skull Chest', 'object_id': 6, 'type': 'Chest', 'access': ['Bomb']}, {'name': 'Bone Dungeon B1 - Tristam', 'object_id': 2, 'type': 'NPC', 'access': ['Tristam']}, {'name': 'Tristam Bone Dungeon Item Given', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['TristamBoneItemGiven'], 'access': ['Tristam']}], 'links': [{'target_room': 25, 'entrance': 59, 'teleporter': [88, 3], 'access': []}, {'target_room': 28, 'entrance': 57, 'teleporter': [3, 2], 'access': ['Bomb']}]}, {'name': 'Bone Dungeon B1 - Checker Room', 'id': 28, 'game_objects': [{'name': 'Bone Dungeon B1 - Checker Room Box', 'object_id': 56, 'type': 'Box', 'access': ['Bomb']}], 'links': [{'target_room': 26, 'entrance': 61, 'teleporter': [89, 3], 'access': []}, {'target_room': 30, 'entrance': 60, 'teleporter': [4, 2], 'access': []}]}, {'name': 'Bone Dungeon B1 - Hidden Room', 'id': 29, 'game_objects': [{'name': 'Bone Dungeon B1 - Ribcage Waterway Box', 'object_id': 57, 'type': 'Box', 'access': []}], 'links': [{'target_room': 31, 'entrance': 62, 'teleporter': [91, 3], 'access': []}]}, {'name': 'Bone Dungeon B2 - Exploding Skull Room - First Room', 'id': 30, 'game_objects': [{'name': 'Bone Dungeon B2 - Spines Room Alcove Box', 'object_id': 59, 'type': 'Box', 'access': []}, {'name': 'Long Spine', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['LongSpineBombed'], 'access': ['Bomb']}], 'links': [{'target_room': 28, 'entrance': 65, 'teleporter': [90, 3], 'access': []}, {'target_room': 31, 'access': ['LongSpineBombed']}]}, {'name': 'Bone Dungeon B2 - Exploding Skull Room - Second Room', 'id': 31, 'game_objects': [{'name': 'Bone Dungeon B2 - Spines Room Looped Hallway Box', 'object_id': 58, 'type': 'Box', 'access': []}, {'name': 'Short Spine', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShortSpineBombed'], 'access': ['Bomb']}], 'links': [{'target_room': 29, 'entrance': 63, 'teleporter': [5, 2], 'access': ['LongSpineBombed']}, {'target_room': 32, 'access': ['ShortSpineBombed']}, {'target_room': 30, 'access': ['LongSpineBombed']}]}, {'name': 'Bone Dungeon B2 - Exploding Skull Room - Third Room', 'id': 32, 'game_objects': [], 'links': [{'target_room': 35, 'entrance': 64, 'teleporter': [6, 2], 'access': []}, {'target_room': 31, 'access': ['ShortSpineBombed']}]}, {'name': 'Bone Dungeon B2 - Box Room', 'id': 33, 'game_objects': [{'name': 'Bone Dungeon B2 - Lone Room Box', 'object_id': 61, 'type': 'Box', 'access': []}], 'links': [{'target_room': 36, 'entrance': 66, 'teleporter': [93, 3], 'access': []}]}, {'name': 'Bone Dungeon B2 - Quake Room', 'id': 34, 'game_objects': [{'name': 'Bone Dungeon B2 - Penultimate Room Chest', 'object_id': 7, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 37, 'entrance': 67, 'teleporter': [94, 3], 'access': []}]}, {'name': 'Bone Dungeon B2 - Two Skulls Room - First Room', 'id': 35, 'game_objects': [{'name': 'Bone Dungeon B2 - Two Skulls Room Box', 'object_id': 60, 'type': 'Box', 'access': []}, {'name': 'Skull 1', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Skull1Bombed'], 'access': ['Bomb']}], 'links': [{'target_room': 32, 'entrance': 71, 'teleporter': [92, 3], 'access': []}, {'target_room': 36, 'access': ['Skull1Bombed']}]}, {'name': 'Bone Dungeon B2 - Two Skulls Room - Second Room', 'id': 36, 'game_objects': [{'name': 'Skull 2', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Skull2Bombed'], 'access': ['Bomb']}], 'links': [{'target_room': 33, 'entrance': 68, 'teleporter': [7, 2], 'access': []}, {'target_room': 37, 'access': ['Skull2Bombed']}, {'target_room': 35, 'access': ['Skull1Bombed']}]}, {'name': 'Bone Dungeon B2 - Two Skulls Room - Third Room', 'id': 37, 'game_objects': [], 'links': [{'target_room': 34, 'entrance': 69, 'teleporter': [8, 2], 'access': []}, {'target_room': 38, 'entrance': 70, 'teleporter': [9, 2], 'access': ['Bomb']}, {'target_room': 36, 'access': ['Skull2Bombed']}]}, {'name': 'Bone Dungeon B2 - Boss Room', 'id': 38, 'game_objects': [{'name': 'Bone Dungeon B2 - North Box', 'object_id': 62, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon B2 - South Box', 'object_id': 63, 'type': 'Box', 'access': []}, {'name': 'Bone Dungeon B2 - Flamerus Rex Chest', 'object_id': 8, 'type': 'Chest', 'access': []}, {'name': "Bone Dungeon B2 - Tristam's Treasure Chest", 'object_id': 4, 'type': 'Chest', 'access': []}, {'name': 'Flamerus Rex', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['FlamerusRex'], 'access': []}], 'links': [{'target_room': 37, 'entrance': 74, 'teleporter': [95, 3], 'access': []}]}, {'name': 'Libra Temple', 'id': 39, 'game_objects': [{'name': 'Libra Temple - Box', 'object_id': 64, 'type': 'Box', 'access': []}, {'name': 'Phoebe Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Phoebe1'], 'access': []}], 'links': [{'target_room': 221, 'entrance': 75, 'teleporter': [13, 6], 'access': []}, {'target_room': 51, 'entrance': 76, 'teleporter': [59, 8], 'access': ['LibraCrest']}]}, {'name': 'Aquaria', 'id': 40, 'game_objects': [{'name': 'Summer Aquaria', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['SummerAquaria'], 'access': ['WakeWater']}], 'links': [{'target_room': 221, 'entrance': 77, 'teleporter': [8, 6], 'access': []}, {'target_room': 41, 'entrance': 81, 'teleporter': [10, 5], 'access': []}, {'target_room': 42, 'entrance': 82, 'teleporter': [44, 4], 'access': []}, {'target_room': 44, 'entrance': 83, 'teleporter': [11, 5], 'access': []}, {'target_room': 71, 'entrance': 89, 'teleporter': [42, 0], 'access': ['SummerAquaria']}, {'target_room': 71, 'entrance': 90, 'teleporter': [43, 0], 'access': ['SummerAquaria']}]}, {'name': "Phoebe's House", 'id': 41, 'game_objects': [{'name': "Aquaria - Phoebe's House Chest", 'object_id': 65, 'type': 'Box', 'access': []}], 'links': [{'target_room': 40, 'entrance': 93, 'teleporter': [5, 8], 'access': []}]}, {'name': 'Aquaria Vendor House', 'id': 42, 'game_objects': [{'name': 'Aquaria - Vendor', 'object_id': 4, 'type': 'NPC', 'access': []}, {'name': 'Aquaria - Vendor House Box', 'object_id': 66, 'type': 'Box', 'access': []}], 'links': [{'target_room': 40, 'entrance': 94, 'teleporter': [40, 8], 'access': []}, {'target_room': 43, 'entrance': 95, 'teleporter': [47, 0], 'access': []}]}, {'name': 'Aquaria Gemini Room', 'id': 43, 'game_objects': [], 'links': [{'target_room': 42, 'entrance': 97, 'teleporter': [48, 0], 'access': []}, {'target_room': 81, 'entrance': 96, 'teleporter': [72, 8], 'access': ['GeminiCrest']}]}, {'name': 'Aquaria INN', 'id': 44, 'game_objects': [], 'links': [{'target_room': 40, 'entrance': 98, 'teleporter': [75, 8], 'access': []}]}, {'name': 'Wintry Cave 1F - East Ledge', 'id': 45, 'game_objects': [{'name': 'Wintry Cave 1F - North Box', 'object_id': 67, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 1F - Entrance Box', 'object_id': 70, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 1F - Slippery Cliff Box', 'object_id': 68, 'type': 'Box', 'access': ['Claw']}, {'name': 'Wintry Cave 1F - Phoebe', 'object_id': 5, 'type': 'NPC', 'access': ['Phoebe1']}], 'links': [{'target_room': 221, 'entrance': 99, 'teleporter': [49, 0], 'access': []}, {'target_room': 49, 'entrance': 100, 'teleporter': [14, 2], 'access': ['Bomb']}, {'target_room': 46, 'access': ['Claw']}]}, {'name': 'Wintry Cave 1F - Central Space', 'id': 46, 'game_objects': [{'name': 'Wintry Cave 1F - Scenic Overlook Box', 'object_id': 69, 'type': 'Box', 'access': ['Claw']}], 'links': [{'target_room': 45, 'access': ['Claw']}, {'target_room': 47, 'access': ['Claw']}]}, {'name': 'Wintry Cave 1F - West Ledge', 'id': 47, 'game_objects': [], 'links': [{'target_room': 48, 'entrance': 101, 'teleporter': [15, 2], 'access': ['Bomb']}, {'target_room': 46, 'access': ['Claw']}]}, {'name': 'Wintry Cave 2F', 'id': 48, 'game_objects': [{'name': 'Wintry Cave 2F - West Left Box', 'object_id': 71, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 2F - West Right Box', 'object_id': 72, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 2F - East Left Box', 'object_id': 73, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 2F - East Right Box', 'object_id': 74, 'type': 'Box', 'access': []}], 'links': [{'target_room': 47, 'entrance': 104, 'teleporter': [97, 3], 'access': []}, {'target_room': 50, 'entrance': 103, 'teleporter': [50, 0], 'access': []}]}, {'name': 'Wintry Cave 3F Top', 'id': 49, 'game_objects': [{'name': 'Wintry Cave 3F - West Box', 'object_id': 75, 'type': 'Box', 'access': []}, {'name': 'Wintry Cave 3F - East Box', 'object_id': 76, 'type': 'Box', 'access': []}], 'links': [{'target_room': 45, 'entrance': 105, 'teleporter': [96, 3], 'access': []}]}, {'name': 'Wintry Cave 3F Bottom', 'id': 50, 'game_objects': [{'name': 'Wintry Cave 3F - Squidite Chest', 'object_id': 9, 'type': 'Chest', 'access': ['Phanquid']}, {'name': 'Phanquid', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Phanquid'], 'access': []}, {'name': 'Wintry Cave 3F - Before Boss Box', 'object_id': 77, 'type': 'Box', 'access': []}], 'links': [{'target_room': 48, 'entrance': 106, 'teleporter': [51, 0], 'access': []}]}, {'name': 'Life Temple', 'id': 51, 'game_objects': [{'name': 'Life Temple - Box', 'object_id': 78, 'type': 'Box', 'access': []}, {'name': 'Life Temple - Mysterious Man', 'object_id': 6, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 222, 'entrance': 107, 'teleporter': [14, 6], 'access': []}, {'target_room': 39, 'entrance': 108, 'teleporter': [60, 8], 'access': ['LibraCrest']}]}, {'name': 'Fall Basin', 'id': 52, 'game_objects': [{'name': 'Falls Basin - Snow Crab Chest', 'object_id': 10, 'type': 'Chest', 'access': ['FreezerCrab']}, {'name': 'Freezer Crab', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['FreezerCrab'], 'access': []}, {'name': 'Falls Basin - Box', 'object_id': 79, 'type': 'Box', 'access': []}], 'links': [{'target_room': 221, 'entrance': 111, 'teleporter': [53, 0], 'access': []}]}, {'name': 'Ice Pyramid B1 Taunt Room', 'id': 53, 'game_objects': [{'name': 'Ice Pyramid B1 - Chest', 'object_id': 11, 'type': 'Chest', 'access': []}, {'name': 'Ice Pyramid B1 - West Box', 'object_id': 80, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid B1 - North Box', 'object_id': 81, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid B1 - East Box', 'object_id': 82, 'type': 'Box', 'access': []}], 'links': [{'target_room': 68, 'entrance': 113, 'teleporter': [55, 0], 'access': []}]}, {'name': 'Ice Pyramid 1F Maze Lobby', 'id': 54, 'game_objects': [{'name': 'Ice Pyramid 1F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid1FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 221, 'entrance': 114, 'teleporter': [56, 0], 'access': []}, {'target_room': 55, 'access': ['IcePyramid1FStatue']}]}, {'name': 'Ice Pyramid 1F Maze', 'id': 55, 'game_objects': [{'name': 'Ice Pyramid 1F - East Alcove Chest', 'object_id': 13, 'type': 'Chest', 'access': []}, {'name': 'Ice Pyramid 1F - Sandwiched Alcove Box', 'object_id': 83, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 1F - Southwest Left Box', 'object_id': 84, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 1F - Southwest Right Box', 'object_id': 85, 'type': 'Box', 'access': []}], 'links': [{'target_room': 56, 'entrance': 116, 'teleporter': [57, 0], 'access': []}, {'target_room': 57, 'entrance': 117, 'teleporter': [58, 0], 'access': []}, {'target_room': 58, 'entrance': 118, 'teleporter': [59, 0], 'access': []}, {'target_room': 59, 'entrance': 119, 'teleporter': [60, 0], 'access': []}, {'target_room': 60, 'entrance': 120, 'teleporter': [61, 0], 'access': []}, {'target_room': 54, 'access': ['IcePyramid1FStatue']}]}, {'name': 'Ice Pyramid 2F South Tiled Room', 'id': 56, 'game_objects': [{'name': 'Ice Pyramid 2F - South Side Glass Door Box', 'object_id': 87, 'type': 'Box', 'access': ['Sword']}, {'name': 'Ice Pyramid 2F - South Side East Box', 'object_id': 91, 'type': 'Box', 'access': []}], 'links': [{'target_room': 55, 'entrance': 122, 'teleporter': [62, 0], 'access': []}, {'target_room': 61, 'entrance': 123, 'teleporter': [67, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F West Room', 'id': 57, 'game_objects': [{'name': 'Ice Pyramid 2F - Northwest Room Box', 'object_id': 90, 'type': 'Box', 'access': []}], 'links': [{'target_room': 55, 'entrance': 124, 'teleporter': [63, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F Center Room', 'id': 58, 'game_objects': [{'name': 'Ice Pyramid 2F - Center Room Box', 'object_id': 86, 'type': 'Box', 'access': []}], 'links': [{'target_room': 55, 'entrance': 125, 'teleporter': [64, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F Small North Room', 'id': 59, 'game_objects': [{'name': 'Ice Pyramid 2F - North Room Glass Door Box', 'object_id': 88, 'type': 'Box', 'access': ['Sword']}], 'links': [{'target_room': 55, 'entrance': 126, 'teleporter': [65, 0], 'access': []}]}, {'name': 'Ice Pyramid 2F North Corridor', 'id': 60, 'game_objects': [{'name': 'Ice Pyramid 2F - North Corridor Glass Door Box', 'object_id': 89, 'type': 'Box', 'access': ['Sword']}], 'links': [{'target_room': 55, 'entrance': 127, 'teleporter': [66, 0], 'access': []}, {'target_room': 62, 'entrance': 128, 'teleporter': [68, 0], 'access': []}]}, {'name': 'Ice Pyramid 3F Two Boxes Room', 'id': 61, 'game_objects': [{'name': 'Ice Pyramid 3F - Staircase Dead End Left Box', 'object_id': 94, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - Staircase Dead End Right Box', 'object_id': 95, 'type': 'Box', 'access': []}], 'links': [{'target_room': 56, 'entrance': 129, 'teleporter': [69, 0], 'access': []}]}, {'name': 'Ice Pyramid 3F Main Loop', 'id': 62, 'game_objects': [{'name': 'Ice Pyramid 3F - Inner Room North Box', 'object_id': 92, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - Inner Room South Box', 'object_id': 93, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - East Alcove Box', 'object_id': 96, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F - Leapfrog Box', 'object_id': 97, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 3F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid3FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 60, 'entrance': 130, 'teleporter': [70, 0], 'access': []}, {'target_room': 63, 'access': ['IcePyramid3FStatue']}]}, {'name': 'Ice Pyramid 3F Blocked Room', 'id': 63, 'game_objects': [], 'links': [{'target_room': 64, 'entrance': 131, 'teleporter': [71, 0], 'access': []}, {'target_room': 62, 'access': ['IcePyramid3FStatue']}]}, {'name': 'Ice Pyramid 4F Main Loop', 'id': 64, 'game_objects': [], 'links': [{'target_room': 66, 'entrance': 133, 'teleporter': [73, 0], 'access': []}, {'target_room': 63, 'entrance': 132, 'teleporter': [72, 0], 'access': []}, {'target_room': 65, 'access': ['IcePyramid4FStatue']}]}, {'name': 'Ice Pyramid 4F Treasure Room', 'id': 65, 'game_objects': [{'name': 'Ice Pyramid 4F - Chest', 'object_id': 12, 'type': 'Chest', 'access': []}, {'name': 'Ice Pyramid 4F - Northwest Box', 'object_id': 98, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - West Left Box', 'object_id': 99, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - West Right Box', 'object_id': 100, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - South Left Box', 'object_id': 101, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - South Right Box', 'object_id': 102, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - East Left Box', 'object_id': 103, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F - East Right Box', 'object_id': 104, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 4F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid4FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 64, 'access': ['IcePyramid4FStatue']}]}, {'name': 'Ice Pyramid 5F Leap of Faith Room', 'id': 66, 'game_objects': [{'name': 'Ice Pyramid 5F - Glass Door Left Box', 'object_id': 105, 'type': 'Box', 'access': ['IcePyramid5FStatue']}, {'name': 'Ice Pyramid 5F - West Ledge Box', 'object_id': 106, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 5F - South Shelf Box', 'object_id': 107, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 5F - South Leapfrog Box', 'object_id': 108, 'type': 'Box', 'access': []}, {'name': 'Ice Pyramid 5F - Glass Door Right Box', 'object_id': 109, 'type': 'Box', 'access': ['IcePyramid5FStatue']}, {'name': 'Ice Pyramid 5F - North Box', 'object_id': 110, 'type': 'Box', 'access': []}], 'links': [{'target_room': 64, 'entrance': 134, 'teleporter': [74, 0], 'access': []}, {'target_room': 65, 'access': []}, {'target_room': 53, 'access': ['Bomb', 'Claw', 'Sword']}]}, {'name': 'Ice Pyramid 5F Stairs to Ice Golem', 'id': 67, 'game_objects': [{'name': 'Ice Pyramid 5F Statue', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IcePyramid5FStatue'], 'access': ['Sword']}], 'links': [{'target_room': 69, 'entrance': 137, 'teleporter': [76, 0], 'access': []}, {'target_room': 65, 'access': []}, {'target_room': 70, 'entrance': 136, 'teleporter': [75, 0], 'access': []}]}, {'name': 'Ice Pyramid Climbing Wall Room Lower Space', 'id': 68, 'game_objects': [], 'links': [{'target_room': 53, 'entrance': 139, 'teleporter': [78, 0], 'access': []}, {'target_room': 69, 'access': ['Claw']}]}, {'name': 'Ice Pyramid Climbing Wall Room Upper Space', 'id': 69, 'game_objects': [], 'links': [{'target_room': 67, 'entrance': 140, 'teleporter': [79, 0], 'access': []}, {'target_room': 68, 'access': ['Claw']}]}, {'name': 'Ice Pyramid Ice Golem Room', 'id': 70, 'game_objects': [{'name': 'Ice Pyramid 6F - Ice Golem Chest', 'object_id': 14, 'type': 'Chest', 'access': ['IceGolem']}, {'name': 'Ice Golem', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['IceGolem'], 'access': []}], 'links': [{'target_room': 67, 'entrance': 141, 'teleporter': [80, 0], 'access': []}, {'target_room': 66, 'access': []}]}, {'name': 'Spencer Waterfall', 'id': 71, 'game_objects': [], 'links': [{'target_room': 72, 'entrance': 143, 'teleporter': [81, 0], 'access': []}, {'target_room': 40, 'entrance': 145, 'teleporter': [82, 0], 'access': []}, {'target_room': 40, 'entrance': 148, 'teleporter': [83, 0], 'access': []}]}, {'name': 'Spencer Cave Normal Main', 'id': 72, 'game_objects': [{'name': "Spencer's Cave - Box", 'object_id': 111, 'type': 'Box', 'access': ['Claw']}, {'name': "Spencer's Cave - Spencer", 'object_id': 8, 'type': 'NPC', 'access': []}, {'name': "Spencer's Cave - Locked Chest", 'object_id': 13, 'type': 'NPC', 'access': ['VenusKey']}], 'links': [{'target_room': 71, 'entrance': 150, 'teleporter': [85, 0], 'access': []}]}, {'name': 'Spencer Cave Normal South Ledge', 'id': 73, 'game_objects': [{'name': "Collapse Spencer's Cave", 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipLiberated'], 'access': ['MegaGrenade']}], 'links': [{'target_room': 227, 'entrance': 151, 'teleporter': [7, 6], 'access': []}, {'target_room': 203, 'access': ['MegaGrenade']}]}, {'name': 'Spencer Cave Caved In Main Loop', 'id': 203, 'game_objects': [], 'links': [{'target_room': 73, 'access': []}, {'target_room': 207, 'entrance': 156, 'teleporter': [36, 8], 'access': ['MobiusCrest']}, {'target_room': 204, 'access': ['Claw']}, {'target_room': 205, 'access': ['Bomb']}]}, {'name': 'Spencer Cave Caved In Waters', 'id': 204, 'game_objects': [{'name': 'Bomb Libra Block', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['SpencerCaveLibraBlockBombed'], 'access': ['MegaGrenade', 'Claw']}], 'links': [{'target_room': 203, 'access': ['Claw']}]}, {'name': 'Spencer Cave Caved In Libra Nook', 'id': 205, 'game_objects': [], 'links': [{'target_room': 206, 'entrance': 153, 'teleporter': [33, 8], 'access': ['LibraCrest']}]}, {'name': 'Spencer Cave Caved In Libra Corridor', 'id': 206, 'game_objects': [], 'links': [{'target_room': 205, 'entrance': 154, 'teleporter': [34, 8], 'access': ['LibraCrest']}, {'target_room': 207, 'access': ['SpencerCaveLibraBlockBombed']}]}, {'name': 'Spencer Cave Caved In Mobius Chest', 'id': 207, 'game_objects': [{'name': "Spencer's Cave - Mobius Chest", 'object_id': 15, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 203, 'entrance': 155, 'teleporter': [35, 8], 'access': ['MobiusCrest']}, {'target_room': 206, 'access': ['Bomb']}]}, {'name': 'Wintry Temple Outer Room', 'id': 74, 'game_objects': [], 'links': [{'target_room': 223, 'entrance': 157, 'teleporter': [15, 6], 'access': []}]}, {'name': 'Wintry Temple Inner Room', 'id': 75, 'game_objects': [{'name': 'Wintry Temple - West Box', 'object_id': 112, 'type': 'Box', 'access': []}, {'name': 'Wintry Temple - North Box', 'object_id': 113, 'type': 'Box', 'access': []}], 'links': [{'target_room': 92, 'entrance': 158, 'teleporter': [62, 8], 'access': ['GeminiCrest']}]}, {'name': 'Fireburg Upper Plaza', 'id': 76, 'game_objects': [], 'links': [{'target_room': 224, 'entrance': 159, 'teleporter': [9, 6], 'access': []}, {'target_room': 80, 'entrance': 163, 'teleporter': [91, 0], 'access': []}, {'target_room': 77, 'entrance': 164, 'teleporter': [98, 8], 'access': []}, {'target_room': 82, 'entrance': 165, 'teleporter': [96, 8], 'access': []}, {'target_room': 208, 'access': ['Claw']}]}, {'name': 'Fireburg Lower Plaza', 'id': 208, 'game_objects': [{'name': 'Fireburg - Hidden Tunnel Box', 'object_id': 116, 'type': 'Box', 'access': []}], 'links': [{'target_room': 76, 'access': ['Claw']}, {'target_room': 78, 'entrance': 166, 'teleporter': [11, 8], 'access': ['MultiKey']}]}, {'name': "Reuben's House", 'id': 77, 'game_objects': [{'name': "Fireburg - Reuben's House Arion", 'object_id': 14, 'type': 'NPC', 'access': ['ReubenDadSaved']}, {'name': 'Reuben Companion', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Reuben1'], 'access': []}, {'name': "Fireburg - Reuben's House Box", 'object_id': 117, 'type': 'Box', 'access': []}], 'links': [{'target_room': 76, 'entrance': 167, 'teleporter': [98, 3], 'access': []}]}, {'name': "GrenadeMan's House", 'id': 78, 'game_objects': [{'name': 'Fireburg - Locked House Man', 'object_id': 12, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 208, 'entrance': 168, 'teleporter': [9, 8], 'access': ['MultiKey']}, {'target_room': 79, 'entrance': 169, 'teleporter': [93, 0], 'access': []}]}, {'name': "GrenadeMan's Mobius Room", 'id': 79, 'game_objects': [], 'links': [{'target_room': 78, 'entrance': 170, 'teleporter': [94, 0], 'access': []}, {'target_room': 161, 'entrance': 171, 'teleporter': [54, 8], 'access': ['MobiusCrest']}]}, {'name': 'Fireburg Vendor House', 'id': 80, 'game_objects': [{'name': 'Fireburg - Vendor', 'object_id': 11, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 76, 'entrance': 172, 'teleporter': [95, 0], 'access': []}, {'target_room': 81, 'entrance': 173, 'teleporter': [96, 0], 'access': []}]}, {'name': 'Fireburg Gemini Room', 'id': 81, 'game_objects': [], 'links': [{'target_room': 80, 'entrance': 174, 'teleporter': [97, 0], 'access': []}, {'target_room': 43, 'entrance': 175, 'teleporter': [45, 8], 'access': ['GeminiCrest']}]}, {'name': 'Fireburg Hotel Lobby', 'id': 82, 'game_objects': [{'name': 'Fireburg - Tristam', 'object_id': 10, 'type': 'NPC', 'access': ['Tristam', 'TristamBoneItemGiven']}], 'links': [{'target_room': 76, 'entrance': 177, 'teleporter': [99, 3], 'access': []}, {'target_room': 83, 'entrance': 176, 'teleporter': [213, 0], 'access': []}]}, {'name': 'Fireburg Hotel Beds', 'id': 83, 'game_objects': [], 'links': [{'target_room': 82, 'entrance': 178, 'teleporter': [214, 0], 'access': []}]}, {'name': 'Mine Exterior North West Platforms', 'id': 84, 'game_objects': [], 'links': [{'target_room': 224, 'entrance': 179, 'teleporter': [98, 0], 'access': []}, {'target_room': 88, 'entrance': 181, 'teleporter': [20, 2], 'access': ['Bomb']}, {'target_room': 85, 'access': ['Claw']}, {'target_room': 86, 'access': ['Claw']}, {'target_room': 87, 'access': ['Claw']}]}, {'name': 'Mine Exterior Central Ledge', 'id': 85, 'game_objects': [], 'links': [{'target_room': 90, 'entrance': 183, 'teleporter': [22, 2], 'access': ['Bomb']}, {'target_room': 84, 'access': ['Claw']}]}, {'name': 'Mine Exterior North Ledge', 'id': 86, 'game_objects': [], 'links': [{'target_room': 89, 'entrance': 182, 'teleporter': [21, 2], 'access': ['Bomb']}, {'target_room': 85, 'access': ['Claw']}]}, {'name': 'Mine Exterior South East Platforms', 'id': 87, 'game_objects': [{'name': 'Jinn', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Jinn'], 'access': []}], 'links': [{'target_room': 91, 'entrance': 180, 'teleporter': [99, 0], 'access': ['Jinn']}, {'target_room': 86, 'access': []}, {'target_room': 85, 'access': ['Claw']}]}, {'name': 'Mine Parallel Room', 'id': 88, 'game_objects': [{'name': 'Mine - Parallel Room West Box', 'object_id': 119, 'type': 'Box', 'access': ['Claw']}, {'name': 'Mine - Parallel Room East Box', 'object_id': 120, 'type': 'Box', 'access': ['Claw']}], 'links': [{'target_room': 84, 'entrance': 185, 'teleporter': [100, 3], 'access': []}]}, {'name': 'Mine Crescent Room', 'id': 89, 'game_objects': [{'name': 'Mine - Crescent Room Chest', 'object_id': 16, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 86, 'entrance': 186, 'teleporter': [101, 3], 'access': []}]}, {'name': 'Mine Climbing Room', 'id': 90, 'game_objects': [{'name': 'Mine - Glitchy Collision Cave Box', 'object_id': 118, 'type': 'Box', 'access': ['Claw']}], 'links': [{'target_room': 85, 'entrance': 187, 'teleporter': [102, 3], 'access': []}]}, {'name': 'Mine Cliff', 'id': 91, 'game_objects': [{'name': 'Mine - Cliff Southwest Box', 'object_id': 121, 'type': 'Box', 'access': []}, {'name': 'Mine - Cliff Northwest Box', 'object_id': 122, 'type': 'Box', 'access': []}, {'name': 'Mine - Cliff Northeast Box', 'object_id': 123, 'type': 'Box', 'access': []}, {'name': 'Mine - Cliff Southeast Box', 'object_id': 124, 'type': 'Box', 'access': []}, {'name': 'Mine - Reuben', 'object_id': 7, 'type': 'NPC', 'access': ['Reuben1']}, {'name': "Reuben's dad Saved", 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ReubenDadSaved'], 'access': ['MegaGrenade']}], 'links': [{'target_room': 87, 'entrance': 188, 'teleporter': [100, 0], 'access': []}]}, {'name': 'Sealed Temple', 'id': 92, 'game_objects': [{'name': 'Sealed Temple - West Box', 'object_id': 125, 'type': 'Box', 'access': []}, {'name': 'Sealed Temple - East Box', 'object_id': 126, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 190, 'teleporter': [16, 6], 'access': []}, {'target_room': 75, 'entrance': 191, 'teleporter': [63, 8], 'access': ['GeminiCrest']}]}, {'name': 'Volcano Base', 'id': 93, 'game_objects': [{'name': 'Volcano - Base Chest', 'object_id': 17, 'type': 'Chest', 'access': []}, {'name': 'Volcano - Base West Box', 'object_id': 127, 'type': 'Box', 'access': []}, {'name': 'Volcano - Base East Left Box', 'object_id': 128, 'type': 'Box', 'access': []}, {'name': 'Volcano - Base East Right Box', 'object_id': 129, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 192, 'teleporter': [103, 0], 'access': []}, {'target_room': 98, 'entrance': 196, 'teleporter': [31, 8], 'access': []}, {'target_room': 96, 'entrance': 197, 'teleporter': [30, 8], 'access': []}]}, {'name': 'Volcano Top Left', 'id': 94, 'game_objects': [{'name': 'Volcano - Medusa Chest', 'object_id': 18, 'type': 'Chest', 'access': ['Medusa']}, {'name': 'Medusa', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Medusa'], 'access': []}, {'name': 'Volcano - Behind Medusa Box', 'object_id': 130, 'type': 'Box', 'access': []}], 'links': [{'target_room': 209, 'entrance': 199, 'teleporter': [26, 8], 'access': []}]}, {'name': 'Volcano Top Right', 'id': 95, 'game_objects': [{'name': 'Volcano - Top of the Volcano Left Box', 'object_id': 131, 'type': 'Box', 'access': []}, {'name': 'Volcano - Top of the Volcano Right Box', 'object_id': 132, 'type': 'Box', 'access': []}], 'links': [{'target_room': 99, 'entrance': 200, 'teleporter': [79, 8], 'access': []}]}, {'name': 'Volcano Right Path', 'id': 96, 'game_objects': [{'name': 'Volcano - Right Path Box', 'object_id': 135, 'type': 'Box', 'access': []}], 'links': [{'target_room': 93, 'entrance': 201, 'teleporter': [15, 8], 'access': []}]}, {'name': 'Volcano Left Path', 'id': 98, 'game_objects': [{'name': 'Volcano - Left Path Box', 'object_id': 134, 'type': 'Box', 'access': []}], 'links': [{'target_room': 93, 'entrance': 204, 'teleporter': [27, 8], 'access': []}, {'target_room': 99, 'entrance': 202, 'teleporter': [25, 2], 'access': []}, {'target_room': 209, 'entrance': 203, 'teleporter': [26, 2], 'access': []}]}, {'name': 'Volcano Cross Left-Right', 'id': 99, 'game_objects': [], 'links': [{'target_room': 95, 'entrance': 206, 'teleporter': [29, 8], 'access': []}, {'target_room': 98, 'entrance': 205, 'teleporter': [103, 3], 'access': []}]}, {'name': 'Volcano Cross Right-Left', 'id': 209, 'game_objects': [{'name': 'Volcano - Crossover Section Box', 'object_id': 133, 'type': 'Box', 'access': []}], 'links': [{'target_room': 98, 'entrance': 208, 'teleporter': [104, 3], 'access': []}, {'target_room': 94, 'entrance': 207, 'teleporter': [28, 8], 'access': []}]}, {'name': 'Lava Dome Inner Ring Main Loop', 'id': 100, 'game_objects': [{'name': 'Lava Dome - Exterior Caldera Near Switch Cliff Box', 'object_id': 136, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Exterior South Cliff Box', 'object_id': 137, 'type': 'Box', 'access': []}], 'links': [{'target_room': 224, 'entrance': 209, 'teleporter': [104, 0], 'access': []}, {'target_room': 113, 'entrance': 211, 'teleporter': [105, 0], 'access': []}, {'target_room': 114, 'entrance': 212, 'teleporter': [106, 0], 'access': []}, {'target_room': 116, 'entrance': 213, 'teleporter': [108, 0], 'access': []}, {'target_room': 118, 'entrance': 214, 'teleporter': [111, 0], 'access': []}]}, {'name': 'Lava Dome Inner Ring Center Ledge', 'id': 101, 'game_objects': [{'name': 'Lava Dome - Exterior Center Dropoff Ledge Box', 'object_id': 138, 'type': 'Box', 'access': []}], 'links': [{'target_room': 115, 'entrance': 215, 'teleporter': [107, 0], 'access': []}, {'target_room': 100, 'access': ['Claw']}]}, {'name': 'Lava Dome Inner Ring Plate Ledge', 'id': 102, 'game_objects': [{'name': 'Lava Dome Plate', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['LavaDomePlate'], 'access': []}], 'links': [{'target_room': 119, 'entrance': 216, 'teleporter': [109, 0], 'access': []}]}, {'name': 'Lava Dome Inner Ring Upper Ledge West', 'id': 103, 'game_objects': [], 'links': [{'target_room': 111, 'entrance': 219, 'teleporter': [112, 0], 'access': []}, {'target_room': 108, 'entrance': 220, 'teleporter': [113, 0], 'access': []}, {'target_room': 104, 'access': ['Claw']}, {'target_room': 100, 'access': ['Claw']}]}, {'name': 'Lava Dome Inner Ring Upper Ledge East', 'id': 104, 'game_objects': [], 'links': [{'target_room': 110, 'entrance': 218, 'teleporter': [110, 0], 'access': []}, {'target_room': 103, 'access': ['Claw']}]}, {'name': 'Lava Dome Inner Ring Big Door Ledge', 'id': 105, 'game_objects': [], 'links': [{'target_room': 107, 'entrance': 221, 'teleporter': [114, 0], 'access': []}, {'target_room': 121, 'entrance': 222, 'teleporter': [29, 2], 'access': ['LavaDomePlate']}]}, {'name': 'Lava Dome Inner Ring Tiny Bottom Ledge', 'id': 106, 'game_objects': [{'name': 'Lava Dome - Exterior Dead End Caldera Box', 'object_id': 139, 'type': 'Box', 'access': []}], 'links': [{'target_room': 120, 'entrance': 226, 'teleporter': [115, 0], 'access': []}]}, {'name': 'Lava Dome Jump Maze II', 'id': 107, 'game_objects': [{'name': 'Lava Dome - Gold Maze Northwest Box', 'object_id': 140, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Southwest Box', 'object_id': 246, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Northeast Box', 'object_id': 247, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze North Box', 'object_id': 248, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Center Box', 'object_id': 249, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Maze Southeast Box', 'object_id': 250, 'type': 'Box', 'access': []}], 'links': [{'target_room': 105, 'entrance': 227, 'teleporter': [116, 0], 'access': []}, {'target_room': 108, 'entrance': 228, 'teleporter': [119, 0], 'access': []}, {'target_room': 120, 'entrance': 229, 'teleporter': [120, 0], 'access': []}]}, {'name': 'Lava Dome Up-Down Corridor', 'id': 108, 'game_objects': [], 'links': [{'target_room': 107, 'entrance': 231, 'teleporter': [118, 0], 'access': []}, {'target_room': 103, 'entrance': 230, 'teleporter': [117, 0], 'access': []}]}, {'name': 'Lava Dome Jump Maze I', 'id': 109, 'game_objects': [{'name': 'Lava Dome - Bare Maze Leapfrog Alcove North Box', 'object_id': 141, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Bare Maze Leapfrog Alcove South Box', 'object_id': 142, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Bare Maze Center Box', 'object_id': 143, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Bare Maze Southwest Box', 'object_id': 144, 'type': 'Box', 'access': []}], 'links': [{'target_room': 118, 'entrance': 232, 'teleporter': [121, 0], 'access': []}, {'target_room': 111, 'entrance': 233, 'teleporter': [122, 0], 'access': []}]}, {'name': 'Lava Dome Pointless Room', 'id': 110, 'game_objects': [], 'links': [{'target_room': 104, 'entrance': 234, 'teleporter': [123, 0], 'access': []}]}, {'name': 'Lava Dome Lower Moon Helm Room', 'id': 111, 'game_objects': [{'name': 'Lava Dome - U-Bend Room North Box', 'object_id': 146, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - U-Bend Room South Box', 'object_id': 147, 'type': 'Box', 'access': []}], 'links': [{'target_room': 103, 'entrance': 235, 'teleporter': [124, 0], 'access': []}, {'target_room': 109, 'entrance': 236, 'teleporter': [125, 0], 'access': []}]}, {'name': 'Lava Dome Moon Helm Room', 'id': 112, 'game_objects': [{'name': 'Lava Dome - Beyond River Room Chest', 'object_id': 19, 'type': 'Chest', 'access': []}, {'name': 'Lava Dome - Beyond River Room Box', 'object_id': 145, 'type': 'Box', 'access': []}], 'links': [{'target_room': 117, 'entrance': 237, 'teleporter': [126, 0], 'access': []}]}, {'name': 'Lava Dome Three Jumps Room', 'id': 113, 'game_objects': [{'name': 'Lava Dome - Three Jumps Room Box', 'object_id': 150, 'type': 'Box', 'access': []}], 'links': [{'target_room': 100, 'entrance': 238, 'teleporter': [127, 0], 'access': []}]}, {'name': 'Lava Dome Life Chest Room Lower Ledge', 'id': 114, 'game_objects': [{'name': 'Lava Dome - Gold Bar Room Boulder Chest', 'object_id': 28, 'type': 'Chest', 'access': ['MegaGrenade']}], 'links': [{'target_room': 100, 'entrance': 239, 'teleporter': [128, 0], 'access': []}, {'target_room': 115, 'access': ['Claw']}]}, {'name': 'Lava Dome Life Chest Room Upper Ledge', 'id': 115, 'game_objects': [{'name': 'Lava Dome - Gold Bar Room Leapfrog Alcove Box West', 'object_id': 148, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Gold Bar Room Leapfrog Alcove Box East', 'object_id': 149, 'type': 'Box', 'access': []}], 'links': [{'target_room': 101, 'entrance': 240, 'teleporter': [129, 0], 'access': []}, {'target_room': 114, 'access': ['Claw']}]}, {'name': 'Lava Dome Big Jump Room Main Area', 'id': 116, 'game_objects': [{'name': 'Lava Dome - Lava River Room North Box', 'object_id': 152, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Lava River Room East Box', 'object_id': 153, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Lava River Room South Box', 'object_id': 154, 'type': 'Box', 'access': []}], 'links': [{'target_room': 100, 'entrance': 241, 'teleporter': [133, 0], 'access': []}, {'target_room': 119, 'entrance': 243, 'teleporter': [132, 0], 'access': []}, {'target_room': 117, 'access': ['MegaGrenade']}]}, {'name': 'Lava Dome Big Jump Room MegaGrenade Area', 'id': 117, 'game_objects': [], 'links': [{'target_room': 112, 'entrance': 242, 'teleporter': [131, 0], 'access': []}, {'target_room': 116, 'access': ['Bomb']}]}, {'name': 'Lava Dome Split Corridor', 'id': 118, 'game_objects': [{'name': 'Lava Dome - Split Corridor Box', 'object_id': 151, 'type': 'Box', 'access': []}], 'links': [{'target_room': 109, 'entrance': 244, 'teleporter': [130, 0], 'access': []}, {'target_room': 100, 'entrance': 245, 'teleporter': [134, 0], 'access': []}]}, {'name': 'Lava Dome Plate Corridor', 'id': 119, 'game_objects': [], 'links': [{'target_room': 102, 'entrance': 246, 'teleporter': [135, 0], 'access': []}, {'target_room': 116, 'entrance': 247, 'teleporter': [137, 0], 'access': []}]}, {'name': 'Lava Dome Four Boxes Stairs', 'id': 120, 'game_objects': [{'name': 'Lava Dome - Caldera Stairway West Left Box', 'object_id': 155, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Caldera Stairway West Right Box', 'object_id': 156, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Caldera Stairway East Left Box', 'object_id': 157, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Caldera Stairway East Right Box', 'object_id': 158, 'type': 'Box', 'access': []}], 'links': [{'target_room': 107, 'entrance': 248, 'teleporter': [136, 0], 'access': []}, {'target_room': 106, 'entrance': 249, 'teleporter': [16, 0], 'access': []}]}, {'name': 'Lava Dome Hydra Room', 'id': 121, 'game_objects': [{'name': 'Lava Dome - Dualhead Hydra Chest', 'object_id': 20, 'type': 'Chest', 'access': ['DualheadHydra']}, {'name': 'Dualhead Hydra', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['DualheadHydra'], 'access': []}, {'name': 'Lava Dome - Hydra Room Northwest Box', 'object_id': 159, 'type': 'Box', 'access': []}, {'name': 'Lava Dome - Hydra Room Southweast Box', 'object_id': 160, 'type': 'Box', 'access': []}], 'links': [{'target_room': 105, 'entrance': 250, 'teleporter': [105, 3], 'access': []}, {'target_room': 122, 'entrance': 251, 'teleporter': [138, 0], 'access': ['DualheadHydra']}]}, {'name': 'Lava Dome Escape Corridor', 'id': 122, 'game_objects': [], 'links': [{'target_room': 121, 'entrance': 253, 'teleporter': [139, 0], 'access': []}]}, {'name': 'Rope Bridge', 'id': 123, 'game_objects': [{'name': 'Rope Bridge - West Box', 'object_id': 163, 'type': 'Box', 'access': []}, {'name': 'Rope Bridge - East Box', 'object_id': 164, 'type': 'Box', 'access': []}], 'links': [{'target_room': 226, 'entrance': 255, 'teleporter': [140, 0], 'access': []}]}, {'name': 'Alive Forest', 'id': 124, 'game_objects': [{'name': 'Alive Forest - Tree Stump Chest', 'object_id': 21, 'type': 'Chest', 'access': ['Axe']}, {'name': 'Alive Forest - Near Entrance Box', 'object_id': 165, 'type': 'Box', 'access': ['Axe']}, {'name': 'Alive Forest - After Bridge Box', 'object_id': 166, 'type': 'Box', 'access': ['Axe']}, {'name': 'Alive Forest - Gemini Stump Box', 'object_id': 167, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 226, 'entrance': 272, 'teleporter': [142, 0], 'access': ['Axe']}, {'target_room': 21, 'entrance': 275, 'teleporter': [64, 8], 'access': ['LibraCrest', 'Axe']}, {'target_room': 22, 'entrance': 276, 'teleporter': [65, 8], 'access': ['GeminiCrest', 'Axe']}, {'target_room': 23, 'entrance': 277, 'teleporter': [66, 8], 'access': ['MobiusCrest', 'Axe']}, {'target_room': 125, 'entrance': 274, 'teleporter': [143, 0], 'access': ['Axe']}]}, {'name': 'Giant Tree 1F Main Area', 'id': 125, 'game_objects': [{'name': 'Giant Tree 1F - Northwest Box', 'object_id': 168, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 1F - Southwest Box', 'object_id': 169, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 1F - Center Box', 'object_id': 170, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 1F - East Box', 'object_id': 171, 'type': 'Box', 'access': []}], 'links': [{'target_room': 124, 'entrance': 278, 'teleporter': [56, 1], 'access': []}, {'target_room': 202, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 1F North Island', 'id': 202, 'game_objects': [], 'links': [{'target_room': 127, 'entrance': 280, 'teleporter': [144, 0], 'access': []}, {'target_room': 125, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 1F Central Island', 'id': 126, 'game_objects': [], 'links': [{'target_room': 202, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F Main Lobby', 'id': 127, 'game_objects': [{'name': 'Giant Tree 2F - North Box', 'object_id': 172, 'type': 'Box', 'access': []}], 'links': [{'target_room': 126, 'access': ['DragonClaw']}, {'target_room': 125, 'entrance': 281, 'teleporter': [145, 0], 'access': []}, {'target_room': 133, 'entrance': 283, 'teleporter': [149, 0], 'access': []}, {'target_room': 129, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F West Ledge', 'id': 128, 'game_objects': [{'name': 'Giant Tree 2F - Dropdown Ledge Box', 'object_id': 174, 'type': 'Box', 'access': []}], 'links': [{'target_room': 140, 'entrance': 284, 'teleporter': [147, 0], 'access': ['Sword']}, {'target_room': 130, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F Lower Area', 'id': 129, 'game_objects': [{'name': 'Giant Tree 2F - South Box', 'object_id': 173, 'type': 'Box', 'access': []}], 'links': [{'target_room': 130, 'access': ['Claw']}, {'target_room': 131, 'access': ['Claw']}]}, {'name': 'Giant Tree 2F Central Island', 'id': 130, 'game_objects': [], 'links': [{'target_room': 129, 'access': ['Claw']}, {'target_room': 135, 'entrance': 282, 'teleporter': [146, 0], 'access': ['Sword']}]}, {'name': 'Giant Tree 2F East Ledge', 'id': 131, 'game_objects': [], 'links': [{'target_room': 129, 'access': ['Claw']}, {'target_room': 130, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 2F Meteor Chest Room', 'id': 132, 'game_objects': [{'name': 'Giant Tree 2F - Gidrah Chest', 'object_id': 22, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 133, 'entrance': 285, 'teleporter': [148, 0], 'access': []}]}, {'name': 'Giant Tree 2F Mushroom Room', 'id': 133, 'game_objects': [{'name': 'Giant Tree 2F - Mushroom Tunnel West Box', 'object_id': 175, 'type': 'Box', 'access': ['Axe']}, {'name': 'Giant Tree 2F - Mushroom Tunnel East Box', 'object_id': 176, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 127, 'entrance': 286, 'teleporter': [150, 0], 'access': ['Axe']}, {'target_room': 132, 'entrance': 287, 'teleporter': [151, 0], 'access': ['Axe', 'Gidrah']}]}, {'name': 'Giant Tree 3F Central Island', 'id': 135, 'game_objects': [{'name': 'Giant Tree 3F - Central Island Box', 'object_id': 179, 'type': 'Box', 'access': []}], 'links': [{'target_room': 130, 'entrance': 288, 'teleporter': [152, 0], 'access': []}, {'target_room': 136, 'access': ['Claw']}, {'target_room': 137, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 3F Central Area', 'id': 136, 'game_objects': [{'name': 'Giant Tree 3F - Center North Box', 'object_id': 177, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 3F - Center West Box', 'object_id': 178, 'type': 'Box', 'access': []}], 'links': [{'target_room': 135, 'access': ['Claw']}, {'target_room': 127, 'access': []}, {'target_room': 131, 'access': []}]}, {'name': 'Giant Tree 3F Lower Ledge', 'id': 137, 'game_objects': [], 'links': [{'target_room': 135, 'access': ['DragonClaw']}, {'target_room': 142, 'entrance': 289, 'teleporter': [153, 0], 'access': ['Sword']}]}, {'name': 'Giant Tree 3F West Area', 'id': 138, 'game_objects': [{'name': 'Giant Tree 3F - West Side Box', 'object_id': 180, 'type': 'Box', 'access': []}], 'links': [{'target_room': 128, 'access': []}, {'target_room': 210, 'entrance': 290, 'teleporter': [154, 0], 'access': []}]}, {'name': 'Giant Tree 3F Middle Up Island', 'id': 139, 'game_objects': [], 'links': [{'target_room': 136, 'access': ['Claw']}]}, {'name': 'Giant Tree 3F West Platform', 'id': 140, 'game_objects': [], 'links': [{'target_room': 139, 'access': ['Claw']}, {'target_room': 141, 'access': ['Claw']}, {'target_room': 128, 'entrance': 291, 'teleporter': [155, 0], 'access': []}]}, {'name': 'Giant Tree 3F North Ledge', 'id': 141, 'game_objects': [], 'links': [{'target_room': 143, 'entrance': 292, 'teleporter': [156, 0], 'access': ['Sword']}, {'target_room': 139, 'access': ['Claw']}, {'target_room': 136, 'access': ['Claw']}]}, {'name': 'Giant Tree Worm Room Upper Ledge', 'id': 142, 'game_objects': [{'name': 'Giant Tree 3F - Worm Room North Box', 'object_id': 181, 'type': 'Box', 'access': ['Axe']}, {'name': 'Giant Tree 3F - Worm Room South Box', 'object_id': 182, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 137, 'entrance': 293, 'teleporter': [157, 0], 'access': ['Axe']}, {'target_room': 210, 'access': ['Axe', 'Claw']}]}, {'name': 'Giant Tree Worm Room Lower Ledge', 'id': 210, 'game_objects': [], 'links': [{'target_room': 138, 'entrance': 294, 'teleporter': [158, 0], 'access': []}]}, {'name': 'Giant Tree 4F Lower Floor', 'id': 143, 'game_objects': [], 'links': [{'target_room': 141, 'entrance': 295, 'teleporter': [159, 0], 'access': []}, {'target_room': 148, 'entrance': 296, 'teleporter': [160, 0], 'access': []}, {'target_room': 148, 'entrance': 297, 'teleporter': [161, 0], 'access': []}, {'target_room': 147, 'entrance': 298, 'teleporter': [162, 0], 'access': ['Sword']}]}, {'name': 'Giant Tree 4F Middle Floor', 'id': 144, 'game_objects': [{'name': 'Giant Tree 4F - Highest Platform North Box', 'object_id': 183, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 4F - Highest Platform South Box', 'object_id': 184, 'type': 'Box', 'access': []}], 'links': [{'target_room': 149, 'entrance': 299, 'teleporter': [163, 0], 'access': []}, {'target_room': 145, 'access': ['Claw']}, {'target_room': 146, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 4F Upper Floor', 'id': 145, 'game_objects': [], 'links': [{'target_room': 150, 'entrance': 300, 'teleporter': [164, 0], 'access': ['Sword']}, {'target_room': 144, 'access': ['Claw']}]}, {'name': 'Giant Tree 4F South Ledge', 'id': 146, 'game_objects': [{'name': 'Giant Tree 4F - Hook Ledge Northeast Box', 'object_id': 185, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 4F - Hook Ledge Southwest Box', 'object_id': 186, 'type': 'Box', 'access': []}], 'links': [{'target_room': 144, 'access': ['DragonClaw']}]}, {'name': 'Giant Tree 4F Slime Room East Area', 'id': 147, 'game_objects': [{'name': 'Giant Tree 4F - East Slime Room Box', 'object_id': 188, 'type': 'Box', 'access': ['Axe']}], 'links': [{'target_room': 143, 'entrance': 304, 'teleporter': [168, 0], 'access': []}]}, {'name': 'Giant Tree 4F Slime Room West Area', 'id': 148, 'game_objects': [], 'links': [{'target_room': 143, 'entrance': 303, 'teleporter': [167, 0], 'access': ['Axe']}, {'target_room': 143, 'entrance': 302, 'teleporter': [166, 0], 'access': ['Axe']}, {'target_room': 149, 'access': ['Axe', 'Claw']}]}, {'name': 'Giant Tree 4F Slime Room Platform', 'id': 149, 'game_objects': [{'name': 'Giant Tree 4F - West Slime Room Box', 'object_id': 187, 'type': 'Box', 'access': []}], 'links': [{'target_room': 144, 'entrance': 301, 'teleporter': [165, 0], 'access': []}, {'target_room': 148, 'access': ['Claw']}]}, {'name': 'Giant Tree 5F Lower Area', 'id': 150, 'game_objects': [{'name': 'Giant Tree 5F - Northwest Left Box', 'object_id': 189, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 5F - Northwest Right Box', 'object_id': 190, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 5F - South Left Box', 'object_id': 191, 'type': 'Box', 'access': []}, {'name': 'Giant Tree 5F - South Right Box', 'object_id': 192, 'type': 'Box', 'access': []}], 'links': [{'target_room': 145, 'entrance': 305, 'teleporter': [169, 0], 'access': []}, {'target_room': 151, 'access': ['Claw']}, {'target_room': 143, 'access': []}]}, {'name': 'Giant Tree 5F Gidrah Platform', 'id': 151, 'game_objects': [{'name': 'Gidrah', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Gidrah'], 'access': []}], 'links': [{'target_room': 150, 'access': ['Claw']}]}, {'name': 'Kaidge Temple Lower Ledge', 'id': 152, 'game_objects': [], 'links': [{'target_room': 226, 'entrance': 307, 'teleporter': [18, 6], 'access': []}, {'target_room': 153, 'access': ['Claw']}]}, {'name': 'Kaidge Temple Upper Ledge', 'id': 153, 'game_objects': [{'name': 'Kaidge Temple - Box', 'object_id': 193, 'type': 'Box', 'access': []}], 'links': [{'target_room': 185, 'entrance': 308, 'teleporter': [71, 8], 'access': ['MobiusCrest']}, {'target_room': 152, 'access': ['Claw']}]}, {'name': 'Windhole Temple', 'id': 154, 'game_objects': [{'name': 'Windhole Temple - Box', 'object_id': 194, 'type': 'Box', 'access': []}], 'links': [{'target_room': 226, 'entrance': 309, 'teleporter': [173, 0], 'access': []}]}, {'name': 'Mount Gale', 'id': 155, 'game_objects': [{'name': 'Mount Gale - Dullahan Chest', 'object_id': 23, 'type': 'Chest', 'access': ['DragonClaw', 'Dullahan']}, {'name': 'Dullahan', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Dullahan'], 'access': ['DragonClaw']}, {'name': 'Mount Gale - East Box', 'object_id': 195, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Mount Gale - West Box', 'object_id': 196, 'type': 'Box', 'access': []}], 'links': [{'target_room': 226, 'entrance': 310, 'teleporter': [174, 0], 'access': []}]}, {'name': 'Windia', 'id': 156, 'game_objects': [], 'links': [{'target_room': 226, 'entrance': 312, 'teleporter': [10, 6], 'access': []}, {'target_room': 157, 'entrance': 320, 'teleporter': [30, 5], 'access': []}, {'target_room': 163, 'entrance': 321, 'teleporter': [97, 8], 'access': []}, {'target_room': 165, 'entrance': 322, 'teleporter': [32, 5], 'access': []}, {'target_room': 159, 'entrance': 323, 'teleporter': [176, 4], 'access': []}, {'target_room': 160, 'entrance': 324, 'teleporter': [177, 4], 'access': []}]}, {'name': "Otto's House", 'id': 157, 'game_objects': [{'name': 'Otto', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['RainbowBridge'], 'access': ['ThunderRock']}], 'links': [{'target_room': 156, 'entrance': 327, 'teleporter': [106, 3], 'access': []}, {'target_room': 158, 'entrance': 326, 'teleporter': [33, 2], 'access': []}]}, {'name': "Otto's Attic", 'id': 158, 'game_objects': [{'name': "Windia - Otto's Attic Box", 'object_id': 197, 'type': 'Box', 'access': []}], 'links': [{'target_room': 157, 'entrance': 328, 'teleporter': [107, 3], 'access': []}]}, {'name': 'Windia Kid House', 'id': 159, 'game_objects': [], 'links': [{'target_room': 156, 'entrance': 329, 'teleporter': [178, 0], 'access': []}, {'target_room': 161, 'entrance': 330, 'teleporter': [180, 0], 'access': []}]}, {'name': 'Windia Old People House', 'id': 160, 'game_objects': [], 'links': [{'target_room': 156, 'entrance': 331, 'teleporter': [179, 0], 'access': []}, {'target_room': 162, 'entrance': 332, 'teleporter': [181, 0], 'access': []}]}, {'name': 'Windia Kid House Basement', 'id': 161, 'game_objects': [], 'links': [{'target_room': 159, 'entrance': 333, 'teleporter': [182, 0], 'access': []}, {'target_room': 79, 'entrance': 334, 'teleporter': [44, 8], 'access': ['MobiusCrest']}]}, {'name': 'Windia Old People House Basement', 'id': 162, 'game_objects': [{'name': 'Windia - Mobius Basement West Box', 'object_id': 200, 'type': 'Box', 'access': []}, {'name': 'Windia - Mobius Basement East Box', 'object_id': 201, 'type': 'Box', 'access': []}], 'links': [{'target_room': 160, 'entrance': 335, 'teleporter': [183, 0], 'access': []}, {'target_room': 186, 'entrance': 336, 'teleporter': [43, 8], 'access': ['MobiusCrest']}]}, {'name': 'Windia Inn Lobby', 'id': 163, 'game_objects': [], 'links': [{'target_room': 156, 'entrance': 338, 'teleporter': [135, 3], 'access': []}, {'target_room': 164, 'entrance': 337, 'teleporter': [102, 8], 'access': []}]}, {'name': 'Windia Inn Beds', 'id': 164, 'game_objects': [{'name': 'Windia - Inn Bedroom North Box', 'object_id': 198, 'type': 'Box', 'access': []}, {'name': 'Windia - Inn Bedroom South Box', 'object_id': 199, 'type': 'Box', 'access': []}, {'name': 'Windia - Kaeli', 'object_id': 15, 'type': 'NPC', 'access': ['Kaeli2']}], 'links': [{'target_room': 163, 'entrance': 339, 'teleporter': [216, 0], 'access': []}]}, {'name': 'Windia Vendor House', 'id': 165, 'game_objects': [{'name': 'Windia - Vendor', 'object_id': 16, 'type': 'NPC', 'access': []}], 'links': [{'target_room': 156, 'entrance': 340, 'teleporter': [108, 3], 'access': []}]}, {'name': 'Pazuzu Tower 1F Main Lobby', 'id': 166, 'game_objects': [{'name': 'Pazuzu 1F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu1F'], 'access': []}], 'links': [{'target_room': 226, 'entrance': 341, 'teleporter': [184, 0], 'access': []}, {'target_room': 180, 'entrance': 345, 'teleporter': [185, 0], 'access': []}]}, {'name': 'Pazuzu Tower 1F Boxes Room', 'id': 167, 'game_objects': [{'name': "Pazuzu's Tower 1F - Descent Bomb Wall West Box", 'object_id': 202, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 1F - Descent Bomb Wall Center Box", 'object_id': 203, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 1F - Descent Bomb Wall East Box", 'object_id': 204, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 1F - Descent Box", 'object_id': 205, 'type': 'Box', 'access': []}], 'links': [{'target_room': 169, 'entrance': 349, 'teleporter': [187, 0], 'access': []}]}, {'name': 'Pazuzu Tower 1F Southern Platform', 'id': 168, 'game_objects': [], 'links': [{'target_room': 169, 'entrance': 346, 'teleporter': [186, 0], 'access': []}, {'target_room': 166, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 2F', 'id': 169, 'game_objects': [{'name': "Pazuzu's Tower 2F - East Room West Box", 'object_id': 206, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 2F - East Room East Box", 'object_id': 207, 'type': 'Box', 'access': []}, {'name': 'Pazuzu 2F Lock', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu2FLock'], 'access': ['Axe']}, {'name': 'Pazuzu 2F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu2F'], 'access': ['Bomb']}], 'links': [{'target_room': 183, 'entrance': 350, 'teleporter': [188, 0], 'access': []}, {'target_room': 168, 'entrance': 351, 'teleporter': [189, 0], 'access': []}, {'target_room': 167, 'entrance': 352, 'teleporter': [190, 0], 'access': []}, {'target_room': 171, 'entrance': 353, 'teleporter': [191, 0], 'access': []}]}, {'name': 'Pazuzu 3F Main Room', 'id': 170, 'game_objects': [{'name': "Pazuzu's Tower 3F - Guest Room West Box", 'object_id': 208, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 3F - Guest Room East Box", 'object_id': 209, 'type': 'Box', 'access': []}, {'name': 'Pazuzu 3F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu3F'], 'access': []}], 'links': [{'target_room': 180, 'entrance': 356, 'teleporter': [192, 0], 'access': []}, {'target_room': 181, 'entrance': 357, 'teleporter': [193, 0], 'access': []}]}, {'name': 'Pazuzu 3F Central Island', 'id': 171, 'game_objects': [], 'links': [{'target_room': 169, 'entrance': 360, 'teleporter': [194, 0], 'access': []}, {'target_room': 170, 'access': ['DragonClaw']}, {'target_room': 172, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 3F Southern Island', 'id': 172, 'game_objects': [{'name': "Pazuzu's Tower 3F - South Ledge Box", 'object_id': 210, 'type': 'Box', 'access': []}], 'links': [{'target_room': 173, 'entrance': 361, 'teleporter': [195, 0], 'access': []}, {'target_room': 171, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 4F', 'id': 173, 'game_objects': [{'name': "Pazuzu's Tower 4F - Elevator West Box", 'object_id': 211, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 4F - Elevator East Box", 'object_id': 212, 'type': 'Box', 'access': ['Bomb']}, {'name': "Pazuzu's Tower 4F - East Storage Room Chest", 'object_id': 24, 'type': 'Chest', 'access': []}, {'name': 'Pazuzu 4F Lock', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu4FLock'], 'access': ['Axe']}, {'name': 'Pazuzu 4F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu4F'], 'access': ['Bomb']}], 'links': [{'target_room': 183, 'entrance': 362, 'teleporter': [196, 0], 'access': []}, {'target_room': 184, 'entrance': 363, 'teleporter': [197, 0], 'access': []}, {'target_room': 172, 'entrance': 364, 'teleporter': [198, 0], 'access': []}, {'target_room': 175, 'entrance': 365, 'teleporter': [199, 0], 'access': []}]}, {'name': 'Pazuzu 5F Pazuzu Loop', 'id': 174, 'game_objects': [{'name': 'Pazuzu 5F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu5F'], 'access': []}], 'links': [{'target_room': 181, 'entrance': 368, 'teleporter': [200, 0], 'access': []}, {'target_room': 182, 'entrance': 369, 'teleporter': [201, 0], 'access': []}]}, {'name': 'Pazuzu 5F Upper Loop', 'id': 175, 'game_objects': [{'name': "Pazuzu's Tower 5F - North Box", 'object_id': 213, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 5F - South Box", 'object_id': 214, 'type': 'Box', 'access': []}], 'links': [{'target_room': 173, 'entrance': 370, 'teleporter': [202, 0], 'access': []}, {'target_room': 176, 'entrance': 371, 'teleporter': [203, 0], 'access': []}]}, {'name': 'Pazuzu 6F', 'id': 176, 'game_objects': [{'name': "Pazuzu's Tower 6F - Box", 'object_id': 215, 'type': 'Box', 'access': []}, {'name': "Pazuzu's Tower 6F - Chest", 'object_id': 25, 'type': 'Chest', 'access': []}, {'name': 'Pazuzu 6F Lock', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu6FLock'], 'access': ['Bomb', 'Axe']}, {'name': 'Pazuzu 6F', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu6F'], 'access': ['Bomb']}], 'links': [{'target_room': 184, 'entrance': 374, 'teleporter': [204, 0], 'access': []}, {'target_room': 175, 'entrance': 375, 'teleporter': [205, 0], 'access': []}, {'target_room': 178, 'entrance': 376, 'teleporter': [206, 0], 'access': []}, {'target_room': 178, 'entrance': 377, 'teleporter': [207, 0], 'access': []}]}, {'name': 'Pazuzu 7F Southwest Area', 'id': 177, 'game_objects': [], 'links': [{'target_room': 182, 'entrance': 380, 'teleporter': [26, 0], 'access': []}, {'target_room': 178, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 7F Rest of the Area', 'id': 178, 'game_objects': [], 'links': [{'target_room': 177, 'access': ['DragonClaw']}, {'target_room': 176, 'entrance': 381, 'teleporter': [27, 0], 'access': []}, {'target_room': 176, 'entrance': 382, 'teleporter': [28, 0], 'access': []}, {'target_room': 179, 'access': ['DragonClaw', 'Pazuzu2FLock', 'Pazuzu4FLock', 'Pazuzu6FLock', 'Pazuzu1F', 'Pazuzu2F', 'Pazuzu3F', 'Pazuzu4F', 'Pazuzu5F', 'Pazuzu6F']}]}, {'name': 'Pazuzu 7F Sky Room', 'id': 179, 'game_objects': [{'name': "Pazuzu's Tower 7F - Pazuzu Chest", 'object_id': 26, 'type': 'Chest', 'access': []}, {'name': 'Pazuzu', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Pazuzu'], 'access': ['Pazuzu2FLock', 'Pazuzu4FLock', 'Pazuzu6FLock', 'Pazuzu1F', 'Pazuzu2F', 'Pazuzu3F', 'Pazuzu4F', 'Pazuzu5F', 'Pazuzu6F']}], 'links': [{'target_room': 178, 'access': ['DragonClaw']}]}, {'name': 'Pazuzu 1F to 3F', 'id': 180, 'game_objects': [], 'links': [{'target_room': 166, 'entrance': 385, 'teleporter': [29, 0], 'access': []}, {'target_room': 170, 'entrance': 386, 'teleporter': [30, 0], 'access': []}]}, {'name': 'Pazuzu 3F to 5F', 'id': 181, 'game_objects': [], 'links': [{'target_room': 170, 'entrance': 387, 'teleporter': [40, 0], 'access': []}, {'target_room': 174, 'entrance': 388, 'teleporter': [41, 0], 'access': []}]}, {'name': 'Pazuzu 5F to 7F', 'id': 182, 'game_objects': [], 'links': [{'target_room': 174, 'entrance': 389, 'teleporter': [38, 0], 'access': []}, {'target_room': 177, 'entrance': 390, 'teleporter': [39, 0], 'access': []}]}, {'name': 'Pazuzu 2F to 4F', 'id': 183, 'game_objects': [], 'links': [{'target_room': 169, 'entrance': 391, 'teleporter': [21, 0], 'access': []}, {'target_room': 173, 'entrance': 392, 'teleporter': [22, 0], 'access': []}]}, {'name': 'Pazuzu 4F to 6F', 'id': 184, 'game_objects': [], 'links': [{'target_room': 173, 'entrance': 393, 'teleporter': [2, 0], 'access': []}, {'target_room': 176, 'entrance': 394, 'teleporter': [3, 0], 'access': []}]}, {'name': 'Light Temple', 'id': 185, 'game_objects': [{'name': 'Light Temple - Box', 'object_id': 216, 'type': 'Box', 'access': []}], 'links': [{'target_room': 230, 'entrance': 395, 'teleporter': [19, 6], 'access': []}, {'target_room': 153, 'entrance': 396, 'teleporter': [70, 8], 'access': ['MobiusCrest']}]}, {'name': 'Ship Dock', 'id': 186, 'game_objects': [{'name': 'Ship Dock Access', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipDockAccess'], 'access': []}], 'links': [{'target_room': 228, 'entrance': 399, 'teleporter': [17, 6], 'access': []}, {'target_room': 162, 'entrance': 397, 'teleporter': [61, 8], 'access': ['MobiusCrest']}]}, {'name': 'Mac Ship Deck', 'id': 187, 'game_objects': [{'name': 'Mac Ship Steering Wheel', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipSteeringWheel'], 'access': []}, {'name': "Mac's Ship Deck - North Box", 'object_id': 217, 'type': 'Box', 'access': []}, {'name': "Mac's Ship Deck - Center Box", 'object_id': 218, 'type': 'Box', 'access': []}, {'name': "Mac's Ship Deck - South Box", 'object_id': 219, 'type': 'Box', 'access': []}], 'links': [{'target_room': 229, 'entrance': 400, 'teleporter': [37, 8], 'access': []}, {'target_room': 188, 'entrance': 401, 'teleporter': [50, 8], 'access': []}, {'target_room': 188, 'entrance': 402, 'teleporter': [51, 8], 'access': []}, {'target_room': 188, 'entrance': 403, 'teleporter': [52, 8], 'access': []}, {'target_room': 189, 'entrance': 404, 'teleporter': [53, 8], 'access': []}]}, {'name': 'Mac Ship B1 Outer Ring', 'id': 188, 'game_objects': [{'name': "Mac's Ship B1 - Northwest Hook Platform Box", 'object_id': 228, 'type': 'Box', 'access': ['DragonClaw']}, {'name': "Mac's Ship B1 - Center Hook Platform Box", 'object_id': 229, 'type': 'Box', 'access': ['DragonClaw']}], 'links': [{'target_room': 187, 'entrance': 405, 'teleporter': [208, 0], 'access': []}, {'target_room': 187, 'entrance': 406, 'teleporter': [175, 0], 'access': []}, {'target_room': 187, 'entrance': 407, 'teleporter': [172, 0], 'access': []}, {'target_room': 193, 'entrance': 408, 'teleporter': [88, 0], 'access': []}, {'target_room': 193, 'access': []}]}, {'name': 'Mac Ship B1 Square Room', 'id': 189, 'game_objects': [], 'links': [{'target_room': 187, 'entrance': 409, 'teleporter': [141, 0], 'access': []}, {'target_room': 192, 'entrance': 410, 'teleporter': [87, 0], 'access': []}]}, {'name': 'Mac Ship B1 Central Corridor', 'id': 190, 'game_objects': [{'name': "Mac's Ship B1 - Central Corridor Box", 'object_id': 230, 'type': 'Box', 'access': []}], 'links': [{'target_room': 192, 'entrance': 413, 'teleporter': [86, 0], 'access': []}, {'target_room': 191, 'entrance': 412, 'teleporter': [102, 0], 'access': []}, {'target_room': 193, 'access': []}]}, {'name': 'Mac Ship B2 South Corridor', 'id': 191, 'game_objects': [], 'links': [{'target_room': 190, 'entrance': 415, 'teleporter': [55, 8], 'access': []}, {'target_room': 194, 'entrance': 414, 'teleporter': [57, 1], 'access': []}]}, {'name': 'Mac Ship B2 North Corridor', 'id': 192, 'game_objects': [], 'links': [{'target_room': 190, 'entrance': 416, 'teleporter': [56, 8], 'access': []}, {'target_room': 189, 'entrance': 417, 'teleporter': [57, 8], 'access': []}]}, {'name': 'Mac Ship B2 Outer Ring', 'id': 193, 'game_objects': [{'name': "Mac's Ship B2 - Barrel Room South Box", 'object_id': 223, 'type': 'Box', 'access': []}, {'name': "Mac's Ship B2 - Barrel Room North Box", 'object_id': 224, 'type': 'Box', 'access': []}, {'name': "Mac's Ship B2 - Southwest Room Box", 'object_id': 225, 'type': 'Box', 'access': []}, {'name': "Mac's Ship B2 - Southeast Room Box", 'object_id': 226, 'type': 'Box', 'access': []}], 'links': [{'target_room': 188, 'entrance': 418, 'teleporter': [58, 8], 'access': []}]}, {'name': 'Mac Ship B1 Mac Room', 'id': 194, 'game_objects': [{'name': "Mac's Ship B1 - Mac Room Chest", 'object_id': 27, 'type': 'Chest', 'access': []}, {'name': 'Captain Mac', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['ShipLoaned'], 'access': ['CaptainCap']}], 'links': [{'target_room': 191, 'entrance': 424, 'teleporter': [101, 0], 'access': []}]}, {'name': 'Doom Castle Corridor of Destiny', 'id': 195, 'game_objects': [], 'links': [{'target_room': 201, 'entrance': 428, 'teleporter': [84, 0], 'access': []}, {'target_room': 196, 'entrance': 429, 'teleporter': [35, 2], 'access': []}, {'target_room': 197, 'entrance': 430, 'teleporter': [209, 0], 'access': ['StoneGolem']}, {'target_room': 198, 'entrance': 431, 'teleporter': [211, 0], 'access': ['StoneGolem', 'TwinheadWyvern']}, {'target_room': 199, 'entrance': 432, 'teleporter': [13, 2], 'access': ['StoneGolem', 'TwinheadWyvern', 'Zuh']}]}, {'name': 'Doom Castle Ice Floor', 'id': 196, 'game_objects': [{'name': 'Doom Castle 4F - Northwest Room Box', 'object_id': 231, 'type': 'Box', 'access': ['Sword', 'DragonClaw']}, {'name': 'Doom Castle 4F - Southwest Room Box', 'object_id': 232, 'type': 'Box', 'access': ['Sword', 'DragonClaw']}, {'name': 'Doom Castle 4F - Northeast Room Box', 'object_id': 233, 'type': 'Box', 'access': ['Sword']}, {'name': 'Doom Castle 4F - Southeast Room Box', 'object_id': 234, 'type': 'Box', 'access': ['Sword', 'DragonClaw']}, {'name': 'Stone Golem', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['StoneGolem'], 'access': ['Sword', 'DragonClaw']}], 'links': [{'target_room': 195, 'entrance': 433, 'teleporter': [109, 3], 'access': []}]}, {'name': 'Doom Castle Lava Floor', 'id': 197, 'game_objects': [{'name': 'Doom Castle 5F - North Left Box', 'object_id': 235, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Doom Castle 5F - North Right Box', 'object_id': 236, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Doom Castle 5F - South Left Box', 'object_id': 237, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Doom Castle 5F - South Right Box', 'object_id': 238, 'type': 'Box', 'access': ['DragonClaw']}, {'name': 'Twinhead Wyvern', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['TwinheadWyvern'], 'access': ['DragonClaw']}], 'links': [{'target_room': 195, 'entrance': 434, 'teleporter': [210, 0], 'access': []}]}, {'name': 'Doom Castle Sky Floor', 'id': 198, 'game_objects': [{'name': 'Doom Castle 6F - West Box', 'object_id': 239, 'type': 'Box', 'access': []}, {'name': 'Doom Castle 6F - East Box', 'object_id': 240, 'type': 'Box', 'access': []}, {'name': 'Zuh', 'object_id': 0, 'type': 'Trigger', 'on_trigger': ['Zuh'], 'access': ['DragonClaw']}], 'links': [{'target_room': 195, 'entrance': 435, 'teleporter': [212, 0], 'access': []}, {'target_room': 197, 'access': []}]}, {'name': 'Doom Castle Hero Room', 'id': 199, 'game_objects': [{'name': 'Doom Castle Hero Chest 01', 'object_id': 242, 'type': 'Chest', 'access': []}, {'name': 'Doom Castle Hero Chest 02', 'object_id': 243, 'type': 'Chest', 'access': []}, {'name': 'Doom Castle Hero Chest 03', 'object_id': 244, 'type': 'Chest', 'access': []}, {'name': 'Doom Castle Hero Chest 04', 'object_id': 245, 'type': 'Chest', 'access': []}], 'links': [{'target_room': 200, 'entrance': 436, 'teleporter': [54, 0], 'access': []}, {'target_room': 195, 'entrance': 441, 'teleporter': [110, 3], 'access': []}]}, {'name': 'Doom Castle Dark King Room', 'id': 200, 'game_objects': [], 'links': [{'target_room': 199, 'entrance': 442, 'teleporter': [52, 0], 'access': []}]}] +entrances = [{'name': 'Doom Castle - Sand Floor - To Sky Door - Sand Floor', 'id': 0, 'area': 7, 'coordinates': [24, 19], 'teleporter': [0, 0]}, {'name': 'Doom Castle - Sand Floor - Main Entrance - Sand Floor', 'id': 1, 'area': 7, 'coordinates': [19, 43], 'teleporter': [1, 6]}, {'name': 'Doom Castle - Aero Room - Aero Room Entrance', 'id': 2, 'area': 7, 'coordinates': [27, 39], 'teleporter': [1, 0]}, {'name': 'Focus Tower B1 - Main Loop - South Entrance', 'id': 3, 'area': 8, 'coordinates': [43, 60], 'teleporter': [2, 6]}, {'name': 'Focus Tower B1 - Main Loop - To Focus Tower 1F - Main Hall', 'id': 4, 'area': 8, 'coordinates': [37, 41], 'teleporter': [4, 0]}, {'name': 'Focus Tower B1 - Aero Corridor - To Focus Tower 1F - Sun Coin Room', 'id': 5, 'area': 8, 'coordinates': [59, 35], 'teleporter': [5, 0]}, {'name': 'Focus Tower B1 - Aero Corridor - To Sand Floor - Aero Chest', 'id': 6, 'area': 8, 'coordinates': [57, 59], 'teleporter': [8, 0]}, {'name': 'Focus Tower B1 - Inner Loop - To Focus Tower 1F - Sky Door', 'id': 7, 'area': 8, 'coordinates': [51, 49], 'teleporter': [6, 0]}, {'name': 'Focus Tower B1 - Inner Loop - To Doom Castle Sand Floor', 'id': 8, 'area': 8, 'coordinates': [51, 45], 'teleporter': [7, 0]}, {'name': 'Focus Tower 1F - Focus Tower West Entrance', 'id': 9, 'area': 9, 'coordinates': [25, 29], 'teleporter': [3, 6]}, {'name': 'Focus Tower 1F - To Focus Tower 2F - From SandCoin', 'id': 10, 'area': 9, 'coordinates': [16, 4], 'teleporter': [10, 0]}, {'name': 'Focus Tower 1F - To Focus Tower B1 - Main Hall', 'id': 11, 'area': 9, 'coordinates': [4, 23], 'teleporter': [11, 0]}, {'name': 'Focus Tower 1F - To Focus Tower B1 - To Aero Chest', 'id': 12, 'area': 9, 'coordinates': [26, 17], 'teleporter': [12, 0]}, {'name': 'Focus Tower 1F - Sky Door', 'id': 13, 'area': 9, 'coordinates': [16, 24], 'teleporter': [13, 0]}, {'name': 'Focus Tower 1F - To Focus Tower 2F - From RiverCoin', 'id': 14, 'area': 9, 'coordinates': [16, 10], 'teleporter': [14, 0]}, {'name': 'Focus Tower 1F - To Focus Tower B1 - From Sky Door', 'id': 15, 'area': 9, 'coordinates': [16, 29], 'teleporter': [15, 0]}, {'name': 'Focus Tower 2F - Sand Coin Passage - North Entrance', 'id': 16, 'area': 10, 'coordinates': [49, 30], 'teleporter': [4, 6]}, {'name': 'Focus Tower 2F - Sand Coin Passage - To Focus Tower 1F - To SandCoin', 'id': 17, 'area': 10, 'coordinates': [47, 33], 'teleporter': [17, 0]}, {'name': 'Focus Tower 2F - River Coin Passage - To Focus Tower 1F - To RiverCoin', 'id': 18, 'area': 10, 'coordinates': [47, 41], 'teleporter': [18, 0]}, {'name': 'Focus Tower 2F - River Coin Passage - To Focus Tower 3F - Lower Floor', 'id': 19, 'area': 10, 'coordinates': [38, 40], 'teleporter': [20, 0]}, {'name': 'Focus Tower 2F - Venus Chest Room - To Focus Tower 3F - Upper Floor', 'id': 20, 'area': 10, 'coordinates': [56, 40], 'teleporter': [19, 0]}, {'name': 'Focus Tower 2F - Venus Chest Room - Pillar Script', 'id': 21, 'area': 10, 'coordinates': [48, 53], 'teleporter': [13, 8]}, {'name': 'Focus Tower 3F - Lower Floor - To Fireburg Entrance', 'id': 22, 'area': 11, 'coordinates': [11, 39], 'teleporter': [6, 6]}, {'name': 'Focus Tower 3F - Lower Floor - To Focus Tower 2F - Jump on Pillar', 'id': 23, 'area': 11, 'coordinates': [6, 47], 'teleporter': [24, 0]}, {'name': 'Focus Tower 3F - Upper Floor - To Aquaria Entrance', 'id': 24, 'area': 11, 'coordinates': [21, 38], 'teleporter': [5, 6]}, {'name': 'Focus Tower 3F - Upper Floor - To Focus Tower 2F - Venus Chest Room', 'id': 25, 'area': 11, 'coordinates': [24, 47], 'teleporter': [23, 0]}, {'name': 'Level Forest - Boulder Script', 'id': 26, 'area': 14, 'coordinates': [52, 15], 'teleporter': [0, 8]}, {'name': 'Level Forest - Rotten Tree Script', 'id': 27, 'area': 14, 'coordinates': [47, 6], 'teleporter': [2, 8]}, {'name': 'Level Forest - Exit Level Forest 1', 'id': 28, 'area': 14, 'coordinates': [46, 25], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 2', 'id': 29, 'area': 14, 'coordinates': [46, 26], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 3', 'id': 30, 'area': 14, 'coordinates': [47, 25], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 4', 'id': 31, 'area': 14, 'coordinates': [47, 26], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 5', 'id': 32, 'area': 14, 'coordinates': [60, 14], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 6', 'id': 33, 'area': 14, 'coordinates': [61, 14], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 7', 'id': 34, 'area': 14, 'coordinates': [46, 4], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 8', 'id': 35, 'area': 14, 'coordinates': [46, 3], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest 9', 'id': 36, 'area': 14, 'coordinates': [47, 4], 'teleporter': [25, 0]}, {'name': 'Level Forest - Exit Level Forest A', 'id': 37, 'area': 14, 'coordinates': [47, 3], 'teleporter': [25, 0]}, {'name': 'Foresta - Exit Foresta 1', 'id': 38, 'area': 15, 'coordinates': [10, 25], 'teleporter': [31, 0]}, {'name': 'Foresta - Exit Foresta 2', 'id': 39, 'area': 15, 'coordinates': [10, 26], 'teleporter': [31, 0]}, {'name': 'Foresta - Exit Foresta 3', 'id': 40, 'area': 15, 'coordinates': [11, 25], 'teleporter': [31, 0]}, {'name': 'Foresta - Exit Foresta 4', 'id': 41, 'area': 15, 'coordinates': [11, 26], 'teleporter': [31, 0]}, {'name': 'Foresta - Old Man House - Front Door', 'id': 42, 'area': 15, 'coordinates': [25, 17], 'teleporter': [32, 4]}, {'name': 'Foresta - Old Man House - Back Door', 'id': 43, 'area': 15, 'coordinates': [25, 14], 'teleporter': [33, 0]}, {'name': "Foresta - Kaeli's House", 'id': 44, 'area': 15, 'coordinates': [7, 21], 'teleporter': [0, 5]}, {'name': 'Foresta - Rest House', 'id': 45, 'area': 15, 'coordinates': [23, 23], 'teleporter': [1, 5]}, {'name': "Kaeli's House - Kaeli's House Entrance", 'id': 46, 'area': 16, 'coordinates': [11, 20], 'teleporter': [86, 3]}, {'name': "Foresta Houses - Old Man's House - Old Man Front Exit", 'id': 47, 'area': 17, 'coordinates': [35, 44], 'teleporter': [34, 0]}, {'name': "Foresta Houses - Old Man's House - Old Man Back Exit", 'id': 48, 'area': 17, 'coordinates': [35, 27], 'teleporter': [35, 0]}, {'name': 'Foresta - Old Man House - Barrel Tile Script', 'id': 483, 'area': 17, 'coordinates': [35, 30], 'teleporter': [13, 8]}, {'name': 'Foresta Houses - Rest House - Bed Script', 'id': 49, 'area': 17, 'coordinates': [30, 6], 'teleporter': [1, 8]}, {'name': 'Foresta Houses - Rest House - Rest House Exit', 'id': 50, 'area': 17, 'coordinates': [35, 20], 'teleporter': [87, 3]}, {'name': 'Foresta Houses - Libra House - Libra House Script', 'id': 51, 'area': 17, 'coordinates': [8, 49], 'teleporter': [67, 8]}, {'name': 'Foresta Houses - Gemini House - Gemini House Script', 'id': 52, 'area': 17, 'coordinates': [26, 55], 'teleporter': [68, 8]}, {'name': 'Foresta Houses - Mobius House - Mobius House Script', 'id': 53, 'area': 17, 'coordinates': [14, 33], 'teleporter': [69, 8]}, {'name': 'Sand Temple - Sand Temple Entrance', 'id': 54, 'area': 18, 'coordinates': [56, 27], 'teleporter': [36, 0]}, {'name': 'Bone Dungeon 1F - Bone Dungeon Entrance', 'id': 55, 'area': 19, 'coordinates': [13, 60], 'teleporter': [37, 0]}, {'name': 'Bone Dungeon 1F - To Bone Dungeon B1', 'id': 56, 'area': 19, 'coordinates': [13, 39], 'teleporter': [2, 2]}, {'name': 'Bone Dungeon B1 - Waterway - Exit Waterway', 'id': 57, 'area': 20, 'coordinates': [27, 39], 'teleporter': [3, 2]}, {'name': "Bone Dungeon B1 - Waterway - Tristam's Script", 'id': 58, 'area': 20, 'coordinates': [27, 45], 'teleporter': [3, 8]}, {'name': 'Bone Dungeon B1 - Waterway - To Bone Dungeon 1F', 'id': 59, 'area': 20, 'coordinates': [54, 61], 'teleporter': [88, 3]}, {'name': 'Bone Dungeon B1 - Checker Room - Exit Checker Room', 'id': 60, 'area': 20, 'coordinates': [23, 40], 'teleporter': [4, 2]}, {'name': 'Bone Dungeon B1 - Checker Room - To Waterway', 'id': 61, 'area': 20, 'coordinates': [39, 49], 'teleporter': [89, 3]}, {'name': 'Bone Dungeon B1 - Hidden Room - To B2 - Exploding Skull Room', 'id': 62, 'area': 20, 'coordinates': [5, 33], 'teleporter': [91, 3]}, {'name': 'Bonne Dungeon B2 - Exploding Skull Room - To Hidden Passage', 'id': 63, 'area': 21, 'coordinates': [19, 13], 'teleporter': [5, 2]}, {'name': 'Bonne Dungeon B2 - Exploding Skull Room - To Two Skulls Room', 'id': 64, 'area': 21, 'coordinates': [29, 15], 'teleporter': [6, 2]}, {'name': 'Bonne Dungeon B2 - Exploding Skull Room - To Checker Room', 'id': 65, 'area': 21, 'coordinates': [8, 25], 'teleporter': [90, 3]}, {'name': 'Bonne Dungeon B2 - Box Room - To B2 - Two Skulls Room', 'id': 66, 'area': 21, 'coordinates': [59, 12], 'teleporter': [93, 3]}, {'name': 'Bonne Dungeon B2 - Quake Room - To B2 - Two Skulls Room', 'id': 67, 'area': 21, 'coordinates': [59, 28], 'teleporter': [94, 3]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To Box Room', 'id': 68, 'area': 21, 'coordinates': [53, 7], 'teleporter': [7, 2]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To Quake Room', 'id': 69, 'area': 21, 'coordinates': [41, 3], 'teleporter': [8, 2]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To Boss Room', 'id': 70, 'area': 21, 'coordinates': [47, 57], 'teleporter': [9, 2]}, {'name': 'Bonne Dungeon B2 - Two Skulls Room - To B2 - Exploding Skull Room', 'id': 71, 'area': 21, 'coordinates': [54, 23], 'teleporter': [92, 3]}, {'name': 'Bone Dungeon B2 - Boss Room - Flamerus Rex Script', 'id': 72, 'area': 22, 'coordinates': [29, 19], 'teleporter': [4, 8]}, {'name': 'Bone Dungeon B2 - Boss Room - Tristam Leave Script', 'id': 73, 'area': 22, 'coordinates': [29, 23], 'teleporter': [75, 8]}, {'name': 'Bone Dungeon B2 - Boss Room - To B2 - Two Skulls Room', 'id': 74, 'area': 22, 'coordinates': [30, 27], 'teleporter': [95, 3]}, {'name': 'Libra Temple - Entrance', 'id': 75, 'area': 23, 'coordinates': [10, 15], 'teleporter': [13, 6]}, {'name': 'Libra Temple - Libra Tile Script', 'id': 76, 'area': 23, 'coordinates': [9, 8], 'teleporter': [59, 8]}, {'name': 'Aquaria Winter - Winter Entrance 1', 'id': 77, 'area': 24, 'coordinates': [25, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Winter - Winter Entrance 2', 'id': 78, 'area': 24, 'coordinates': [25, 26], 'teleporter': [8, 6]}, {'name': 'Aquaria Winter - Winter Entrance 3', 'id': 79, 'area': 24, 'coordinates': [26, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Winter - Winter Entrance 4', 'id': 80, 'area': 24, 'coordinates': [26, 26], 'teleporter': [8, 6]}, {'name': "Aquaria Winter - Winter Phoebe's House Entrance Script", 'id': 81, 'area': 24, 'coordinates': [8, 19], 'teleporter': [10, 5]}, {'name': 'Aquaria Winter - Winter Vendor House Entrance', 'id': 82, 'area': 24, 'coordinates': [8, 5], 'teleporter': [44, 4]}, {'name': 'Aquaria Winter - Winter INN Entrance', 'id': 83, 'area': 24, 'coordinates': [26, 17], 'teleporter': [11, 5]}, {'name': 'Aquaria Summer - Summer Entrance 1', 'id': 84, 'area': 25, 'coordinates': [57, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Summer - Summer Entrance 2', 'id': 85, 'area': 25, 'coordinates': [57, 26], 'teleporter': [8, 6]}, {'name': 'Aquaria Summer - Summer Entrance 3', 'id': 86, 'area': 25, 'coordinates': [58, 25], 'teleporter': [8, 6]}, {'name': 'Aquaria Summer - Summer Entrance 4', 'id': 87, 'area': 25, 'coordinates': [58, 26], 'teleporter': [8, 6]}, {'name': "Aquaria Summer - Summer Phoebe's House Entrance", 'id': 88, 'area': 25, 'coordinates': [40, 19], 'teleporter': [10, 5]}, {'name': "Aquaria Summer - Spencer's Place Entrance Top", 'id': 89, 'area': 25, 'coordinates': [40, 16], 'teleporter': [42, 0]}, {'name': "Aquaria Summer - Spencer's Place Entrance Side", 'id': 90, 'area': 25, 'coordinates': [41, 18], 'teleporter': [43, 0]}, {'name': 'Aquaria Summer - Summer Vendor House Entrance', 'id': 91, 'area': 25, 'coordinates': [40, 5], 'teleporter': [44, 4]}, {'name': 'Aquaria Summer - Summer INN Entrance', 'id': 92, 'area': 25, 'coordinates': [58, 17], 'teleporter': [11, 5]}, {'name': "Phoebe's House - Entrance", 'id': 93, 'area': 26, 'coordinates': [29, 14], 'teleporter': [5, 8]}, {'name': "Aquaria Vendor House - Vendor House Entrance's Script", 'id': 94, 'area': 27, 'coordinates': [7, 10], 'teleporter': [40, 8]}, {'name': 'Aquaria Vendor House - Vendor House Stairs', 'id': 95, 'area': 27, 'coordinates': [1, 4], 'teleporter': [47, 0]}, {'name': 'Aquaria Gemini Room - Gemini Script', 'id': 96, 'area': 27, 'coordinates': [2, 40], 'teleporter': [72, 8]}, {'name': 'Aquaria Gemini Room - Gemini Room Stairs', 'id': 97, 'area': 27, 'coordinates': [4, 39], 'teleporter': [48, 0]}, {'name': 'Aquaria INN - Aquaria INN entrance', 'id': 98, 'area': 27, 'coordinates': [51, 46], 'teleporter': [75, 8]}, {'name': 'Wintry Cave 1F - Main Entrance', 'id': 99, 'area': 28, 'coordinates': [50, 58], 'teleporter': [49, 0]}, {'name': 'Wintry Cave 1F - To 3F Top', 'id': 100, 'area': 28, 'coordinates': [40, 25], 'teleporter': [14, 2]}, {'name': 'Wintry Cave 1F - To 2F', 'id': 101, 'area': 28, 'coordinates': [10, 43], 'teleporter': [15, 2]}, {'name': "Wintry Cave 1F - Phoebe's Script", 'id': 102, 'area': 28, 'coordinates': [44, 37], 'teleporter': [6, 8]}, {'name': 'Wintry Cave 2F - To 3F Bottom', 'id': 103, 'area': 29, 'coordinates': [58, 5], 'teleporter': [50, 0]}, {'name': 'Wintry Cave 2F - To 1F', 'id': 104, 'area': 29, 'coordinates': [38, 18], 'teleporter': [97, 3]}, {'name': 'Wintry Cave 3F Top - Exit from 3F Top', 'id': 105, 'area': 30, 'coordinates': [24, 6], 'teleporter': [96, 3]}, {'name': 'Wintry Cave 3F Bottom - Exit to 2F', 'id': 106, 'area': 31, 'coordinates': [4, 29], 'teleporter': [51, 0]}, {'name': 'Life Temple - Entrance', 'id': 107, 'area': 32, 'coordinates': [9, 60], 'teleporter': [14, 6]}, {'name': 'Life Temple - Libra Tile Script', 'id': 108, 'area': 32, 'coordinates': [3, 55], 'teleporter': [60, 8]}, {'name': 'Life Temple - Mysterious Man Script', 'id': 109, 'area': 32, 'coordinates': [9, 44], 'teleporter': [78, 8]}, {'name': 'Fall Basin - Back Exit Script', 'id': 110, 'area': 33, 'coordinates': [17, 5], 'teleporter': [9, 0]}, {'name': 'Fall Basin - Main Exit', 'id': 111, 'area': 33, 'coordinates': [15, 26], 'teleporter': [53, 0]}, {'name': "Fall Basin - Phoebe's Script", 'id': 112, 'area': 33, 'coordinates': [17, 6], 'teleporter': [9, 8]}, {'name': 'Ice Pyramid B1 Taunt Room - To Climbing Wall Room', 'id': 113, 'area': 34, 'coordinates': [43, 6], 'teleporter': [55, 0]}, {'name': 'Ice Pyramid 1F Maze - Main Entrance 1', 'id': 114, 'area': 35, 'coordinates': [18, 36], 'teleporter': [56, 0]}, {'name': 'Ice Pyramid 1F Maze - Main Entrance 2', 'id': 115, 'area': 35, 'coordinates': [19, 36], 'teleporter': [56, 0]}, {'name': 'Ice Pyramid 1F Maze - West Stairs To 2F South Tiled Room', 'id': 116, 'area': 35, 'coordinates': [3, 27], 'teleporter': [57, 0]}, {'name': 'Ice Pyramid 1F Maze - West Center Stairs to 2F West Room', 'id': 117, 'area': 35, 'coordinates': [11, 15], 'teleporter': [58, 0]}, {'name': 'Ice Pyramid 1F Maze - East Center Stairs to 2F Center Room', 'id': 118, 'area': 35, 'coordinates': [25, 16], 'teleporter': [59, 0]}, {'name': 'Ice Pyramid 1F Maze - Upper Stairs to 2F Small North Room', 'id': 119, 'area': 35, 'coordinates': [31, 1], 'teleporter': [60, 0]}, {'name': 'Ice Pyramid 1F Maze - East Stairs to 2F North Corridor', 'id': 120, 'area': 35, 'coordinates': [34, 9], 'teleporter': [61, 0]}, {'name': "Ice Pyramid 1F Maze - Statue's Script", 'id': 121, 'area': 35, 'coordinates': [21, 32], 'teleporter': [77, 8]}, {'name': 'Ice Pyramid 2F South Tiled Room - To 1F', 'id': 122, 'area': 36, 'coordinates': [4, 26], 'teleporter': [62, 0]}, {'name': 'Ice Pyramid 2F South Tiled Room - To 3F Two Boxes Room', 'id': 123, 'area': 36, 'coordinates': [22, 17], 'teleporter': [67, 0]}, {'name': 'Ice Pyramid 2F West Room - To 1F', 'id': 124, 'area': 36, 'coordinates': [9, 10], 'teleporter': [63, 0]}, {'name': 'Ice Pyramid 2F Center Room - To 1F', 'id': 125, 'area': 36, 'coordinates': [22, 14], 'teleporter': [64, 0]}, {'name': 'Ice Pyramid 2F Small North Room - To 1F', 'id': 126, 'area': 36, 'coordinates': [26, 4], 'teleporter': [65, 0]}, {'name': 'Ice Pyramid 2F North Corridor - To 1F', 'id': 127, 'area': 36, 'coordinates': [32, 8], 'teleporter': [66, 0]}, {'name': 'Ice Pyramid 2F North Corridor - To 3F Main Loop', 'id': 128, 'area': 36, 'coordinates': [12, 7], 'teleporter': [68, 0]}, {'name': 'Ice Pyramid 3F Two Boxes Room - To 2F South Tiled Room', 'id': 129, 'area': 37, 'coordinates': [24, 54], 'teleporter': [69, 0]}, {'name': 'Ice Pyramid 3F Main Loop - To 2F Corridor', 'id': 130, 'area': 37, 'coordinates': [16, 45], 'teleporter': [70, 0]}, {'name': 'Ice Pyramid 3F Main Loop - To 4F', 'id': 131, 'area': 37, 'coordinates': [19, 43], 'teleporter': [71, 0]}, {'name': 'Ice Pyramid 4F Treasure Room - To 3F Main Loop', 'id': 132, 'area': 38, 'coordinates': [52, 5], 'teleporter': [72, 0]}, {'name': 'Ice Pyramid 4F Treasure Room - To 5F Leap of Faith Room', 'id': 133, 'area': 38, 'coordinates': [62, 19], 'teleporter': [73, 0]}, {'name': 'Ice Pyramid 5F Leap of Faith Room - To 4F Treasure Room', 'id': 134, 'area': 39, 'coordinates': [54, 63], 'teleporter': [74, 0]}, {'name': 'Ice Pyramid 5F Leap of Faith Room - Bombed Ice Plate', 'id': 135, 'area': 39, 'coordinates': [47, 54], 'teleporter': [77, 8]}, {'name': 'Ice Pyramid 5F Stairs to Ice Golem - To Ice Golem Room', 'id': 136, 'area': 39, 'coordinates': [39, 43], 'teleporter': [75, 0]}, {'name': 'Ice Pyramid 5F Stairs to Ice Golem - To Climbing Wall Room', 'id': 137, 'area': 39, 'coordinates': [39, 60], 'teleporter': [76, 0]}, {'name': 'Ice Pyramid - Duplicate Ice Golem Room', 'id': 138, 'area': 40, 'coordinates': [44, 43], 'teleporter': [77, 0]}, {'name': 'Ice Pyramid Climbing Wall Room - To Taunt Room', 'id': 139, 'area': 41, 'coordinates': [4, 59], 'teleporter': [78, 0]}, {'name': 'Ice Pyramid Climbing Wall Room - To 5F Stairs', 'id': 140, 'area': 41, 'coordinates': [4, 45], 'teleporter': [79, 0]}, {'name': 'Ice Pyramid Ice Golem Room - To 5F Stairs', 'id': 141, 'area': 42, 'coordinates': [44, 43], 'teleporter': [80, 0]}, {'name': 'Ice Pyramid Ice Golem Room - Ice Golem Script', 'id': 142, 'area': 42, 'coordinates': [53, 32], 'teleporter': [10, 8]}, {'name': 'Spencer Waterfall - To Spencer Cave', 'id': 143, 'area': 43, 'coordinates': [48, 57], 'teleporter': [81, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 1', 'id': 144, 'area': 43, 'coordinates': [40, 5], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 2', 'id': 145, 'area': 43, 'coordinates': [40, 6], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 3', 'id': 146, 'area': 43, 'coordinates': [41, 5], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Upper Exit to Aquaria 4', 'id': 147, 'area': 43, 'coordinates': [41, 6], 'teleporter': [82, 0]}, {'name': 'Spencer Waterfall - Right Exit to Aquaria 1', 'id': 148, 'area': 43, 'coordinates': [46, 8], 'teleporter': [83, 0]}, {'name': 'Spencer Waterfall - Right Exit to Aquaria 2', 'id': 149, 'area': 43, 'coordinates': [47, 8], 'teleporter': [83, 0]}, {'name': 'Spencer Cave Normal Main - To Waterfall', 'id': 150, 'area': 44, 'coordinates': [14, 39], 'teleporter': [85, 0]}, {'name': 'Spencer Cave Normal From Overworld - Exit to Overworld', 'id': 151, 'area': 44, 'coordinates': [15, 57], 'teleporter': [7, 6]}, {'name': 'Spencer Cave Unplug - Exit to Overworld', 'id': 152, 'area': 45, 'coordinates': [40, 29], 'teleporter': [7, 6]}, {'name': 'Spencer Cave Unplug - Libra Teleporter Start Script', 'id': 153, 'area': 45, 'coordinates': [28, 21], 'teleporter': [33, 8]}, {'name': 'Spencer Cave Unplug - Libra Teleporter End Script', 'id': 154, 'area': 45, 'coordinates': [46, 4], 'teleporter': [34, 8]}, {'name': 'Spencer Cave Unplug - Mobius Teleporter Chest Script', 'id': 155, 'area': 45, 'coordinates': [21, 9], 'teleporter': [35, 8]}, {'name': 'Spencer Cave Unplug - Mobius Teleporter Start Script', 'id': 156, 'area': 45, 'coordinates': [29, 28], 'teleporter': [36, 8]}, {'name': 'Wintry Temple Outer Room - Main Entrance', 'id': 157, 'area': 46, 'coordinates': [8, 31], 'teleporter': [15, 6]}, {'name': 'Wintry Temple Inner Room - Gemini Tile to Sealed temple', 'id': 158, 'area': 46, 'coordinates': [9, 24], 'teleporter': [62, 8]}, {'name': 'Fireburg - To Overworld', 'id': 159, 'area': 47, 'coordinates': [4, 13], 'teleporter': [9, 6]}, {'name': 'Fireburg - To Overworld', 'id': 160, 'area': 47, 'coordinates': [5, 13], 'teleporter': [9, 6]}, {'name': 'Fireburg - To Overworld', 'id': 161, 'area': 47, 'coordinates': [28, 15], 'teleporter': [9, 6]}, {'name': 'Fireburg - To Overworld', 'id': 162, 'area': 47, 'coordinates': [27, 15], 'teleporter': [9, 6]}, {'name': 'Fireburg - Vendor House', 'id': 163, 'area': 47, 'coordinates': [10, 24], 'teleporter': [91, 0]}, {'name': 'Fireburg - Reuben House', 'id': 164, 'area': 47, 'coordinates': [14, 6], 'teleporter': [98, 8]}, {'name': 'Fireburg - Hotel', 'id': 165, 'area': 47, 'coordinates': [20, 8], 'teleporter': [96, 8]}, {'name': 'Fireburg - GrenadeMan House Script', 'id': 166, 'area': 47, 'coordinates': [12, 18], 'teleporter': [11, 8]}, {'name': 'Reuben House - Main Entrance', 'id': 167, 'area': 48, 'coordinates': [33, 46], 'teleporter': [98, 3]}, {'name': 'GrenadeMan House - Entrance Script', 'id': 168, 'area': 49, 'coordinates': [55, 60], 'teleporter': [9, 8]}, {'name': 'GrenadeMan House - To Mobius Crest Room', 'id': 169, 'area': 49, 'coordinates': [57, 52], 'teleporter': [93, 0]}, {'name': 'GrenadeMan Mobius Room - Stairs to House', 'id': 170, 'area': 49, 'coordinates': [39, 26], 'teleporter': [94, 0]}, {'name': 'GrenadeMan Mobius Room - Mobius Teleporter Script', 'id': 171, 'area': 49, 'coordinates': [39, 23], 'teleporter': [54, 8]}, {'name': 'Fireburg Vendor House - Entrance Script', 'id': 172, 'area': 49, 'coordinates': [7, 10], 'teleporter': [95, 0]}, {'name': 'Fireburg Vendor House - Stairs to Gemini Room', 'id': 173, 'area': 49, 'coordinates': [1, 4], 'teleporter': [96, 0]}, {'name': 'Fireburg Gemini Room - Stairs to Vendor House', 'id': 174, 'area': 49, 'coordinates': [4, 39], 'teleporter': [97, 0]}, {'name': 'Fireburg Gemini Room - Gemini Teleporter Script', 'id': 175, 'area': 49, 'coordinates': [2, 40], 'teleporter': [45, 8]}, {'name': 'Fireburg Hotel Lobby - Stairs to beds', 'id': 176, 'area': 49, 'coordinates': [4, 50], 'teleporter': [213, 0]}, {'name': 'Fireburg Hotel Lobby - Entrance', 'id': 177, 'area': 49, 'coordinates': [17, 56], 'teleporter': [99, 3]}, {'name': 'Fireburg Hotel Beds - Stairs to Hotel Lobby', 'id': 178, 'area': 49, 'coordinates': [45, 59], 'teleporter': [214, 0]}, {'name': 'Mine Exterior - Main Entrance', 'id': 179, 'area': 50, 'coordinates': [5, 28], 'teleporter': [98, 0]}, {'name': 'Mine Exterior - To Cliff', 'id': 180, 'area': 50, 'coordinates': [58, 29], 'teleporter': [99, 0]}, {'name': 'Mine Exterior - To Parallel Room', 'id': 181, 'area': 50, 'coordinates': [8, 7], 'teleporter': [20, 2]}, {'name': 'Mine Exterior - To Crescent Room', 'id': 182, 'area': 50, 'coordinates': [26, 15], 'teleporter': [21, 2]}, {'name': 'Mine Exterior - To Climbing Room', 'id': 183, 'area': 50, 'coordinates': [21, 35], 'teleporter': [22, 2]}, {'name': 'Mine Exterior - Jinn Fight Script', 'id': 184, 'area': 50, 'coordinates': [58, 31], 'teleporter': [74, 8]}, {'name': 'Mine Parallel Room - To Mine Exterior', 'id': 185, 'area': 51, 'coordinates': [7, 60], 'teleporter': [100, 3]}, {'name': 'Mine Crescent Room - To Mine Exterior', 'id': 186, 'area': 51, 'coordinates': [22, 61], 'teleporter': [101, 3]}, {'name': 'Mine Climbing Room - To Mine Exterior', 'id': 187, 'area': 51, 'coordinates': [56, 21], 'teleporter': [102, 3]}, {'name': 'Mine Cliff - Entrance', 'id': 188, 'area': 52, 'coordinates': [9, 5], 'teleporter': [100, 0]}, {'name': 'Mine Cliff - Reuben Grenade Script', 'id': 189, 'area': 52, 'coordinates': [15, 7], 'teleporter': [12, 8]}, {'name': 'Sealed Temple - To Overworld', 'id': 190, 'area': 53, 'coordinates': [58, 43], 'teleporter': [16, 6]}, {'name': 'Sealed Temple - Gemini Tile Script', 'id': 191, 'area': 53, 'coordinates': [56, 38], 'teleporter': [63, 8]}, {'name': 'Volcano Base - Main Entrance 1', 'id': 192, 'area': 54, 'coordinates': [23, 25], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Main Entrance 2', 'id': 193, 'area': 54, 'coordinates': [23, 26], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Main Entrance 3', 'id': 194, 'area': 54, 'coordinates': [24, 25], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Main Entrance 4', 'id': 195, 'area': 54, 'coordinates': [24, 26], 'teleporter': [103, 0]}, {'name': 'Volcano Base - Left Stairs Script', 'id': 196, 'area': 54, 'coordinates': [20, 5], 'teleporter': [31, 8]}, {'name': 'Volcano Base - Right Stairs Script', 'id': 197, 'area': 54, 'coordinates': [32, 5], 'teleporter': [30, 8]}, {'name': 'Volcano Top Right - Top Exit', 'id': 198, 'area': 55, 'coordinates': [44, 8], 'teleporter': [9, 0]}, {'name': 'Volcano Top Left - To Right-Left Path Script', 'id': 199, 'area': 55, 'coordinates': [40, 24], 'teleporter': [26, 8]}, {'name': 'Volcano Top Right - To Left-Right Path Script', 'id': 200, 'area': 55, 'coordinates': [52, 24], 'teleporter': [79, 8]}, {'name': 'Volcano Right Path - To Volcano Base Script', 'id': 201, 'area': 56, 'coordinates': [48, 42], 'teleporter': [15, 8]}, {'name': 'Volcano Left Path - To Volcano Cross Left-Right', 'id': 202, 'area': 56, 'coordinates': [40, 31], 'teleporter': [25, 2]}, {'name': 'Volcano Left Path - To Volcano Cross Right-Left', 'id': 203, 'area': 56, 'coordinates': [52, 29], 'teleporter': [26, 2]}, {'name': 'Volcano Left Path - To Volcano Base Script', 'id': 204, 'area': 56, 'coordinates': [36, 42], 'teleporter': [27, 8]}, {'name': 'Volcano Cross Left-Right - To Volcano Left Path', 'id': 205, 'area': 56, 'coordinates': [10, 42], 'teleporter': [103, 3]}, {'name': 'Volcano Cross Left-Right - To Volcano Top Right Script', 'id': 206, 'area': 56, 'coordinates': [16, 24], 'teleporter': [29, 8]}, {'name': 'Volcano Cross Right-Left - To Volcano Top Left Script', 'id': 207, 'area': 56, 'coordinates': [8, 22], 'teleporter': [28, 8]}, {'name': 'Volcano Cross Right-Left - To Volcano Left Path', 'id': 208, 'area': 56, 'coordinates': [16, 42], 'teleporter': [104, 3]}, {'name': 'Lava Dome Inner Ring Main Loop - Main Entrance 1', 'id': 209, 'area': 57, 'coordinates': [32, 5], 'teleporter': [104, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - Main Entrance 2', 'id': 210, 'area': 57, 'coordinates': [33, 5], 'teleporter': [104, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Three Steps Room', 'id': 211, 'area': 57, 'coordinates': [14, 5], 'teleporter': [105, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Life Chest Room Lower', 'id': 212, 'area': 57, 'coordinates': [40, 17], 'teleporter': [106, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Big Jump Room Left', 'id': 213, 'area': 57, 'coordinates': [8, 11], 'teleporter': [108, 0]}, {'name': 'Lava Dome Inner Ring Main Loop - To Split Corridor Room', 'id': 214, 'area': 57, 'coordinates': [11, 19], 'teleporter': [111, 0]}, {'name': 'Lava Dome Inner Ring Center Ledge - To Life Chest Room Higher', 'id': 215, 'area': 57, 'coordinates': [32, 11], 'teleporter': [107, 0]}, {'name': 'Lava Dome Inner Ring Plate Ledge - To Plate Corridor', 'id': 216, 'area': 57, 'coordinates': [12, 23], 'teleporter': [109, 0]}, {'name': 'Lava Dome Inner Ring Plate Ledge - Plate Script', 'id': 217, 'area': 57, 'coordinates': [5, 23], 'teleporter': [47, 8]}, {'name': 'Lava Dome Inner Ring Upper Ledges - To Pointless Room', 'id': 218, 'area': 57, 'coordinates': [0, 9], 'teleporter': [110, 0]}, {'name': 'Lava Dome Inner Ring Upper Ledges - To Lower Moon Helm Room', 'id': 219, 'area': 57, 'coordinates': [0, 15], 'teleporter': [112, 0]}, {'name': 'Lava Dome Inner Ring Upper Ledges - To Up-Down Corridor', 'id': 220, 'area': 57, 'coordinates': [54, 5], 'teleporter': [113, 0]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - To Jumping Maze II', 'id': 221, 'area': 57, 'coordinates': [54, 21], 'teleporter': [114, 0]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 1', 'id': 222, 'area': 57, 'coordinates': [62, 20], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 2', 'id': 223, 'area': 57, 'coordinates': [63, 20], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 3', 'id': 224, 'area': 57, 'coordinates': [62, 21], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Big Door Ledge - Hydra Gate 4', 'id': 225, 'area': 57, 'coordinates': [63, 21], 'teleporter': [29, 2]}, {'name': 'Lava Dome Inner Ring Tiny Bottom Ledge - To Four Boxes Corridor', 'id': 226, 'area': 57, 'coordinates': [50, 25], 'teleporter': [115, 0]}, {'name': 'Lava Dome Jump Maze II - Lower Right Entrance', 'id': 227, 'area': 58, 'coordinates': [55, 28], 'teleporter': [116, 0]}, {'name': 'Lava Dome Jump Maze II - Upper Entrance', 'id': 228, 'area': 58, 'coordinates': [35, 3], 'teleporter': [119, 0]}, {'name': 'Lava Dome Jump Maze II - Lower Left Entrance', 'id': 229, 'area': 58, 'coordinates': [34, 27], 'teleporter': [120, 0]}, {'name': 'Lava Dome Up-Down Corridor - Upper Entrance', 'id': 230, 'area': 58, 'coordinates': [29, 8], 'teleporter': [117, 0]}, {'name': 'Lava Dome Up-Down Corridor - Lower Entrance', 'id': 231, 'area': 58, 'coordinates': [28, 25], 'teleporter': [118, 0]}, {'name': 'Lava Dome Jump Maze I - South Entrance', 'id': 232, 'area': 59, 'coordinates': [20, 27], 'teleporter': [121, 0]}, {'name': 'Lava Dome Jump Maze I - North Entrance', 'id': 233, 'area': 59, 'coordinates': [7, 3], 'teleporter': [122, 0]}, {'name': 'Lava Dome Pointless Room - Entrance', 'id': 234, 'area': 60, 'coordinates': [2, 7], 'teleporter': [123, 0]}, {'name': 'Lava Dome Pointless Room - Visit Quest Script 1', 'id': 490, 'area': 60, 'coordinates': [4, 4], 'teleporter': [99, 8]}, {'name': 'Lava Dome Pointless Room - Visit Quest Script 2', 'id': 491, 'area': 60, 'coordinates': [4, 5], 'teleporter': [99, 8]}, {'name': 'Lava Dome Lower Moon Helm Room - Left Entrance', 'id': 235, 'area': 60, 'coordinates': [2, 19], 'teleporter': [124, 0]}, {'name': 'Lava Dome Lower Moon Helm Room - Right Entrance', 'id': 236, 'area': 60, 'coordinates': [11, 21], 'teleporter': [125, 0]}, {'name': 'Lava Dome Moon Helm Room - Entrance', 'id': 237, 'area': 60, 'coordinates': [15, 23], 'teleporter': [126, 0]}, {'name': 'Lava Dome Three Jumps Room - To Main Loop', 'id': 238, 'area': 61, 'coordinates': [58, 15], 'teleporter': [127, 0]}, {'name': 'Lava Dome Life Chest Room - Lower South Entrance', 'id': 239, 'area': 61, 'coordinates': [38, 27], 'teleporter': [128, 0]}, {'name': 'Lava Dome Life Chest Room - Upper South Entrance', 'id': 240, 'area': 61, 'coordinates': [28, 23], 'teleporter': [129, 0]}, {'name': 'Lava Dome Big Jump Room - Left Entrance', 'id': 241, 'area': 62, 'coordinates': [42, 51], 'teleporter': [133, 0]}, {'name': 'Lava Dome Big Jump Room - North Entrance', 'id': 242, 'area': 62, 'coordinates': [30, 29], 'teleporter': [131, 0]}, {'name': 'Lava Dome Big Jump Room - Lower Right Stairs', 'id': 243, 'area': 62, 'coordinates': [61, 59], 'teleporter': [132, 0]}, {'name': 'Lava Dome Split Corridor - Upper Stairs', 'id': 244, 'area': 62, 'coordinates': [30, 43], 'teleporter': [130, 0]}, {'name': 'Lava Dome Split Corridor - Lower Stairs', 'id': 245, 'area': 62, 'coordinates': [36, 61], 'teleporter': [134, 0]}, {'name': 'Lava Dome Plate Corridor - Right Entrance', 'id': 246, 'area': 63, 'coordinates': [19, 29], 'teleporter': [135, 0]}, {'name': 'Lava Dome Plate Corridor - Left Entrance', 'id': 247, 'area': 63, 'coordinates': [60, 21], 'teleporter': [137, 0]}, {'name': 'Lava Dome Four Boxes Stairs - Upper Entrance', 'id': 248, 'area': 63, 'coordinates': [22, 3], 'teleporter': [136, 0]}, {'name': 'Lava Dome Four Boxes Stairs - Lower Entrance', 'id': 249, 'area': 63, 'coordinates': [22, 17], 'teleporter': [16, 0]}, {'name': 'Lava Dome Hydra Room - South Entrance', 'id': 250, 'area': 64, 'coordinates': [14, 59], 'teleporter': [105, 3]}, {'name': 'Lava Dome Hydra Room - North Exit', 'id': 251, 'area': 64, 'coordinates': [25, 31], 'teleporter': [138, 0]}, {'name': 'Lava Dome Hydra Room - Hydra Script', 'id': 252, 'area': 64, 'coordinates': [14, 36], 'teleporter': [14, 8]}, {'name': 'Lava Dome Escape Corridor - South Entrance', 'id': 253, 'area': 65, 'coordinates': [22, 17], 'teleporter': [139, 0]}, {'name': 'Lava Dome Escape Corridor - North Entrance', 'id': 254, 'area': 65, 'coordinates': [22, 3], 'teleporter': [9, 0]}, {'name': 'Rope Bridge - West Entrance 1', 'id': 255, 'area': 66, 'coordinates': [3, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 2', 'id': 256, 'area': 66, 'coordinates': [3, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 3', 'id': 257, 'area': 66, 'coordinates': [3, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 4', 'id': 258, 'area': 66, 'coordinates': [3, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 5', 'id': 259, 'area': 66, 'coordinates': [4, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 6', 'id': 260, 'area': 66, 'coordinates': [4, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 7', 'id': 261, 'area': 66, 'coordinates': [4, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - West Entrance 8', 'id': 262, 'area': 66, 'coordinates': [4, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 1', 'id': 263, 'area': 66, 'coordinates': [59, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 2', 'id': 264, 'area': 66, 'coordinates': [59, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 3', 'id': 265, 'area': 66, 'coordinates': [59, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 4', 'id': 266, 'area': 66, 'coordinates': [59, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 5', 'id': 267, 'area': 66, 'coordinates': [60, 10], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 6', 'id': 268, 'area': 66, 'coordinates': [60, 11], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 7', 'id': 269, 'area': 66, 'coordinates': [60, 12], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - East Entrance 8', 'id': 270, 'area': 66, 'coordinates': [60, 13], 'teleporter': [140, 0]}, {'name': 'Rope Bridge - Reuben Fall Script', 'id': 271, 'area': 66, 'coordinates': [13, 12], 'teleporter': [15, 8]}, {'name': 'Alive Forest - West Entrance 1', 'id': 272, 'area': 67, 'coordinates': [8, 13], 'teleporter': [142, 0]}, {'name': 'Alive Forest - West Entrance 2', 'id': 273, 'area': 67, 'coordinates': [9, 13], 'teleporter': [142, 0]}, {'name': 'Alive Forest - Giant Tree Entrance', 'id': 274, 'area': 67, 'coordinates': [42, 42], 'teleporter': [143, 0]}, {'name': 'Alive Forest - Libra Teleporter Script', 'id': 275, 'area': 67, 'coordinates': [8, 52], 'teleporter': [64, 8]}, {'name': 'Alive Forest - Gemini Teleporter Script', 'id': 276, 'area': 67, 'coordinates': [57, 49], 'teleporter': [65, 8]}, {'name': 'Alive Forest - Mobius Teleporter Script', 'id': 277, 'area': 67, 'coordinates': [24, 10], 'teleporter': [66, 8]}, {'name': 'Giant Tree 1F - Entrance Script 1', 'id': 278, 'area': 68, 'coordinates': [18, 31], 'teleporter': [56, 1]}, {'name': 'Giant Tree 1F - Entrance Script 2', 'id': 279, 'area': 68, 'coordinates': [19, 31], 'teleporter': [56, 1]}, {'name': 'Giant Tree 1F - North Entrance To 2F', 'id': 280, 'area': 68, 'coordinates': [16, 1], 'teleporter': [144, 0]}, {'name': 'Giant Tree 2F Main Lobby - North Entrance to 1F', 'id': 281, 'area': 69, 'coordinates': [44, 33], 'teleporter': [145, 0]}, {'name': 'Giant Tree 2F Main Lobby - Central Entrance to 3F', 'id': 282, 'area': 69, 'coordinates': [42, 47], 'teleporter': [146, 0]}, {'name': 'Giant Tree 2F Main Lobby - West Entrance to Mushroom Room', 'id': 283, 'area': 69, 'coordinates': [58, 49], 'teleporter': [149, 0]}, {'name': 'Giant Tree 2F West Ledge - To 3F Northwest Ledge', 'id': 284, 'area': 69, 'coordinates': [34, 37], 'teleporter': [147, 0]}, {'name': 'Giant Tree 2F Fall From Vine Script', 'id': 482, 'area': 69, 'coordinates': [46, 51], 'teleporter': [76, 8]}, {'name': 'Giant Tree Meteor Chest Room - To 2F Mushroom Room', 'id': 285, 'area': 69, 'coordinates': [58, 44], 'teleporter': [148, 0]}, {'name': 'Giant Tree 2F Mushroom Room - Entrance', 'id': 286, 'area': 70, 'coordinates': [55, 18], 'teleporter': [150, 0]}, {'name': 'Giant Tree 2F Mushroom Room - North Face to Meteor', 'id': 287, 'area': 70, 'coordinates': [56, 7], 'teleporter': [151, 0]}, {'name': 'Giant Tree 3F Central Room - Central Entrance to 2F', 'id': 288, 'area': 71, 'coordinates': [46, 53], 'teleporter': [152, 0]}, {'name': 'Giant Tree 3F Central Room - East Entrance to Worm Room', 'id': 289, 'area': 71, 'coordinates': [58, 39], 'teleporter': [153, 0]}, {'name': 'Giant Tree 3F Lower Corridor - Entrance from Worm Room', 'id': 290, 'area': 71, 'coordinates': [45, 39], 'teleporter': [154, 0]}, {'name': 'Giant Tree 3F West Platform - Lower Entrance', 'id': 291, 'area': 71, 'coordinates': [33, 43], 'teleporter': [155, 0]}, {'name': 'Giant Tree 3F West Platform - Top Entrance', 'id': 292, 'area': 71, 'coordinates': [52, 25], 'teleporter': [156, 0]}, {'name': 'Giant Tree Worm Room - East Entrance', 'id': 293, 'area': 72, 'coordinates': [20, 58], 'teleporter': [157, 0]}, {'name': 'Giant Tree Worm Room - West Entrance', 'id': 294, 'area': 72, 'coordinates': [6, 56], 'teleporter': [158, 0]}, {'name': 'Giant Tree 4F Lower Floor - Entrance', 'id': 295, 'area': 73, 'coordinates': [20, 7], 'teleporter': [159, 0]}, {'name': 'Giant Tree 4F Lower Floor - Lower West Mouth', 'id': 296, 'area': 73, 'coordinates': [8, 23], 'teleporter': [160, 0]}, {'name': 'Giant Tree 4F Lower Floor - Lower Central Mouth', 'id': 297, 'area': 73, 'coordinates': [14, 25], 'teleporter': [161, 0]}, {'name': 'Giant Tree 4F Lower Floor - Lower East Mouth', 'id': 298, 'area': 73, 'coordinates': [20, 25], 'teleporter': [162, 0]}, {'name': 'Giant Tree 4F Upper Floor - Upper West Mouth', 'id': 299, 'area': 73, 'coordinates': [8, 19], 'teleporter': [163, 0]}, {'name': 'Giant Tree 4F Upper Floor - Upper Central Mouth', 'id': 300, 'area': 73, 'coordinates': [12, 17], 'teleporter': [164, 0]}, {'name': 'Giant Tree 4F Slime Room - Exit', 'id': 301, 'area': 74, 'coordinates': [47, 10], 'teleporter': [165, 0]}, {'name': 'Giant Tree 4F Slime Room - West Entrance', 'id': 302, 'area': 74, 'coordinates': [45, 24], 'teleporter': [166, 0]}, {'name': 'Giant Tree 4F Slime Room - Central Entrance', 'id': 303, 'area': 74, 'coordinates': [50, 24], 'teleporter': [167, 0]}, {'name': 'Giant Tree 4F Slime Room - East Entrance', 'id': 304, 'area': 74, 'coordinates': [57, 28], 'teleporter': [168, 0]}, {'name': 'Giant Tree 5F - Entrance', 'id': 305, 'area': 75, 'coordinates': [14, 51], 'teleporter': [169, 0]}, {'name': 'Giant Tree 5F - Giant Tree Face', 'id': 306, 'area': 75, 'coordinates': [14, 37], 'teleporter': [170, 0]}, {'name': 'Kaidge Temple - Entrance', 'id': 307, 'area': 77, 'coordinates': [44, 63], 'teleporter': [18, 6]}, {'name': 'Kaidge Temple - Mobius Teleporter Script', 'id': 308, 'area': 77, 'coordinates': [35, 57], 'teleporter': [71, 8]}, {'name': 'Windhole Temple - Entrance', 'id': 309, 'area': 78, 'coordinates': [10, 29], 'teleporter': [173, 0]}, {'name': 'Mount Gale - Entrance 1', 'id': 310, 'area': 79, 'coordinates': [1, 45], 'teleporter': [174, 0]}, {'name': 'Mount Gale - Entrance 2', 'id': 311, 'area': 79, 'coordinates': [2, 45], 'teleporter': [174, 0]}, {'name': 'Mount Gale - Visit Quest', 'id': 494, 'area': 79, 'coordinates': [44, 7], 'teleporter': [101, 8]}, {'name': 'Windia - Main Entrance 1', 'id': 312, 'area': 80, 'coordinates': [12, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 2', 'id': 313, 'area': 80, 'coordinates': [13, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 3', 'id': 314, 'area': 80, 'coordinates': [14, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 4', 'id': 315, 'area': 80, 'coordinates': [15, 40], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 5', 'id': 316, 'area': 80, 'coordinates': [12, 41], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 6', 'id': 317, 'area': 80, 'coordinates': [13, 41], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 7', 'id': 318, 'area': 80, 'coordinates': [14, 41], 'teleporter': [10, 6]}, {'name': 'Windia - Main Entrance 8', 'id': 319, 'area': 80, 'coordinates': [15, 41], 'teleporter': [10, 6]}, {'name': "Windia - Otto's House", 'id': 320, 'area': 80, 'coordinates': [21, 39], 'teleporter': [30, 5]}, {'name': "Windia - INN's Script", 'id': 321, 'area': 80, 'coordinates': [18, 34], 'teleporter': [97, 8]}, {'name': 'Windia - Vendor House', 'id': 322, 'area': 80, 'coordinates': [8, 36], 'teleporter': [32, 5]}, {'name': 'Windia - Kid House', 'id': 323, 'area': 80, 'coordinates': [7, 23], 'teleporter': [176, 4]}, {'name': 'Windia - Old People House', 'id': 324, 'area': 80, 'coordinates': [19, 21], 'teleporter': [177, 4]}, {'name': 'Windia - Rainbow Bridge Script', 'id': 325, 'area': 80, 'coordinates': [21, 9], 'teleporter': [10, 6]}, {'name': "Otto's House - Attic Stairs", 'id': 326, 'area': 81, 'coordinates': [2, 19], 'teleporter': [33, 2]}, {'name': "Otto's House - Entrance", 'id': 327, 'area': 81, 'coordinates': [9, 30], 'teleporter': [106, 3]}, {'name': "Otto's Attic - Stairs", 'id': 328, 'area': 81, 'coordinates': [26, 23], 'teleporter': [107, 3]}, {'name': 'Windia Kid House - Entrance Script', 'id': 329, 'area': 82, 'coordinates': [7, 10], 'teleporter': [178, 0]}, {'name': 'Windia Kid House - Basement Stairs', 'id': 330, 'area': 82, 'coordinates': [1, 4], 'teleporter': [180, 0]}, {'name': 'Windia Old People House - Entrance', 'id': 331, 'area': 82, 'coordinates': [55, 12], 'teleporter': [179, 0]}, {'name': 'Windia Old People House - Basement Stairs', 'id': 332, 'area': 82, 'coordinates': [60, 5], 'teleporter': [181, 0]}, {'name': 'Windia Kid House Basement - Stairs', 'id': 333, 'area': 82, 'coordinates': [43, 8], 'teleporter': [182, 0]}, {'name': 'Windia Kid House Basement - Mobius Teleporter', 'id': 334, 'area': 82, 'coordinates': [41, 9], 'teleporter': [44, 8]}, {'name': 'Windia Old People House Basement - Stairs', 'id': 335, 'area': 82, 'coordinates': [39, 26], 'teleporter': [183, 0]}, {'name': 'Windia Old People House Basement - Mobius Teleporter Script', 'id': 336, 'area': 82, 'coordinates': [39, 23], 'teleporter': [43, 8]}, {'name': 'Windia Inn Lobby - Stairs to Beds', 'id': 337, 'area': 82, 'coordinates': [45, 24], 'teleporter': [102, 8]}, {'name': 'Windia Inn Lobby - Exit', 'id': 338, 'area': 82, 'coordinates': [53, 30], 'teleporter': [135, 3]}, {'name': 'Windia Inn Beds - Stairs to Lobby', 'id': 339, 'area': 82, 'coordinates': [33, 59], 'teleporter': [216, 0]}, {'name': 'Windia Vendor House - Entrance', 'id': 340, 'area': 82, 'coordinates': [29, 14], 'teleporter': [108, 3]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 1', 'id': 341, 'area': 83, 'coordinates': [47, 29], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 2', 'id': 342, 'area': 83, 'coordinates': [47, 30], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 3', 'id': 343, 'area': 83, 'coordinates': [48, 29], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Main Entrance 4', 'id': 344, 'area': 83, 'coordinates': [48, 30], 'teleporter': [184, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - East Entrance', 'id': 345, 'area': 83, 'coordinates': [55, 12], 'teleporter': [185, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - South Stairs', 'id': 346, 'area': 83, 'coordinates': [51, 25], 'teleporter': [186, 0]}, {'name': 'Pazuzu Tower 1F Main Lobby - Pazuzu Script 1', 'id': 347, 'area': 83, 'coordinates': [47, 8], 'teleporter': [16, 8]}, {'name': 'Pazuzu Tower 1F Main Lobby - Pazuzu Script 2', 'id': 348, 'area': 83, 'coordinates': [48, 8], 'teleporter': [16, 8]}, {'name': 'Pazuzu Tower 1F Boxes Room - West Stairs', 'id': 349, 'area': 83, 'coordinates': [38, 17], 'teleporter': [187, 0]}, {'name': 'Pazuzu 2F - West Upper Stairs', 'id': 350, 'area': 84, 'coordinates': [7, 11], 'teleporter': [188, 0]}, {'name': 'Pazuzu 2F - South Stairs', 'id': 351, 'area': 84, 'coordinates': [20, 24], 'teleporter': [189, 0]}, {'name': 'Pazuzu 2F - West Lower Stairs', 'id': 352, 'area': 84, 'coordinates': [6, 17], 'teleporter': [190, 0]}, {'name': 'Pazuzu 2F - Central Stairs', 'id': 353, 'area': 84, 'coordinates': [15, 15], 'teleporter': [191, 0]}, {'name': 'Pazuzu 2F - Pazuzu 1', 'id': 354, 'area': 84, 'coordinates': [15, 8], 'teleporter': [17, 8]}, {'name': 'Pazuzu 2F - Pazuzu 2', 'id': 355, 'area': 84, 'coordinates': [16, 8], 'teleporter': [17, 8]}, {'name': 'Pazuzu 3F Main Room - North Stairs', 'id': 356, 'area': 85, 'coordinates': [23, 11], 'teleporter': [192, 0]}, {'name': 'Pazuzu 3F Main Room - West Stairs', 'id': 357, 'area': 85, 'coordinates': [7, 15], 'teleporter': [193, 0]}, {'name': 'Pazuzu 3F Main Room - Pazuzu Script 1', 'id': 358, 'area': 85, 'coordinates': [15, 8], 'teleporter': [18, 8]}, {'name': 'Pazuzu 3F Main Room - Pazuzu Script 2', 'id': 359, 'area': 85, 'coordinates': [16, 8], 'teleporter': [18, 8]}, {'name': 'Pazuzu 3F Central Island - Central Stairs', 'id': 360, 'area': 85, 'coordinates': [15, 14], 'teleporter': [194, 0]}, {'name': 'Pazuzu 3F Central Island - South Stairs', 'id': 361, 'area': 85, 'coordinates': [17, 25], 'teleporter': [195, 0]}, {'name': 'Pazuzu 4F - Northwest Stairs', 'id': 362, 'area': 86, 'coordinates': [39, 12], 'teleporter': [196, 0]}, {'name': 'Pazuzu 4F - Southwest Stairs', 'id': 363, 'area': 86, 'coordinates': [39, 19], 'teleporter': [197, 0]}, {'name': 'Pazuzu 4F - South Stairs', 'id': 364, 'area': 86, 'coordinates': [47, 24], 'teleporter': [198, 0]}, {'name': 'Pazuzu 4F - Northeast Stairs', 'id': 365, 'area': 86, 'coordinates': [54, 9], 'teleporter': [199, 0]}, {'name': 'Pazuzu 4F - Pazuzu Script 1', 'id': 366, 'area': 86, 'coordinates': [47, 8], 'teleporter': [19, 8]}, {'name': 'Pazuzu 4F - Pazuzu Script 2', 'id': 367, 'area': 86, 'coordinates': [48, 8], 'teleporter': [19, 8]}, {'name': 'Pazuzu 5F Pazuzu Loop - West Stairs', 'id': 368, 'area': 87, 'coordinates': [9, 49], 'teleporter': [200, 0]}, {'name': 'Pazuzu 5F Pazuzu Loop - South Stairs', 'id': 369, 'area': 87, 'coordinates': [16, 55], 'teleporter': [201, 0]}, {'name': 'Pazuzu 5F Upper Loop - Northeast Stairs', 'id': 370, 'area': 87, 'coordinates': [22, 40], 'teleporter': [202, 0]}, {'name': 'Pazuzu 5F Upper Loop - Northwest Stairs', 'id': 371, 'area': 87, 'coordinates': [9, 40], 'teleporter': [203, 0]}, {'name': 'Pazuzu 5F Upper Loop - Pazuzu Script 1', 'id': 372, 'area': 87, 'coordinates': [15, 40], 'teleporter': [20, 8]}, {'name': 'Pazuzu 5F Upper Loop - Pazuzu Script 2', 'id': 373, 'area': 87, 'coordinates': [16, 40], 'teleporter': [20, 8]}, {'name': 'Pazuzu 6F - West Stairs', 'id': 374, 'area': 88, 'coordinates': [41, 47], 'teleporter': [204, 0]}, {'name': 'Pazuzu 6F - Northwest Stairs', 'id': 375, 'area': 88, 'coordinates': [41, 40], 'teleporter': [205, 0]}, {'name': 'Pazuzu 6F - Northeast Stairs', 'id': 376, 'area': 88, 'coordinates': [54, 40], 'teleporter': [206, 0]}, {'name': 'Pazuzu 6F - South Stairs', 'id': 377, 'area': 88, 'coordinates': [52, 56], 'teleporter': [207, 0]}, {'name': 'Pazuzu 6F - Pazuzu Script 1', 'id': 378, 'area': 88, 'coordinates': [47, 40], 'teleporter': [21, 8]}, {'name': 'Pazuzu 6F - Pazuzu Script 2', 'id': 379, 'area': 88, 'coordinates': [48, 40], 'teleporter': [21, 8]}, {'name': 'Pazuzu 7F Main Room - Southwest Stairs', 'id': 380, 'area': 89, 'coordinates': [15, 54], 'teleporter': [26, 0]}, {'name': 'Pazuzu 7F Main Room - Northeast Stairs', 'id': 381, 'area': 89, 'coordinates': [21, 40], 'teleporter': [27, 0]}, {'name': 'Pazuzu 7F Main Room - Southeast Stairs', 'id': 382, 'area': 89, 'coordinates': [21, 56], 'teleporter': [28, 0]}, {'name': 'Pazuzu 7F Main Room - Pazuzu Script 1', 'id': 383, 'area': 89, 'coordinates': [15, 44], 'teleporter': [22, 8]}, {'name': 'Pazuzu 7F Main Room - Pazuzu Script 2', 'id': 384, 'area': 89, 'coordinates': [16, 44], 'teleporter': [22, 8]}, {'name': 'Pazuzu 7F Main Room - Crystal Script', 'id': 480, 'area': 89, 'coordinates': [15, 40], 'teleporter': [38, 8]}, {'name': 'Pazuzu 1F to 3F - South Stairs', 'id': 385, 'area': 90, 'coordinates': [43, 60], 'teleporter': [29, 0]}, {'name': 'Pazuzu 1F to 3F - North Stairs', 'id': 386, 'area': 90, 'coordinates': [43, 36], 'teleporter': [30, 0]}, {'name': 'Pazuzu 3F to 5F - South Stairs', 'id': 387, 'area': 91, 'coordinates': [43, 60], 'teleporter': [40, 0]}, {'name': 'Pazuzu 3F to 5F - North Stairs', 'id': 388, 'area': 91, 'coordinates': [43, 36], 'teleporter': [41, 0]}, {'name': 'Pazuzu 5F to 7F - South Stairs', 'id': 389, 'area': 92, 'coordinates': [43, 60], 'teleporter': [38, 0]}, {'name': 'Pazuzu 5F to 7F - North Stairs', 'id': 390, 'area': 92, 'coordinates': [43, 36], 'teleporter': [39, 0]}, {'name': 'Pazuzu 2F to 4F - South Stairs', 'id': 391, 'area': 93, 'coordinates': [43, 60], 'teleporter': [21, 0]}, {'name': 'Pazuzu 2F to 4F - North Stairs', 'id': 392, 'area': 93, 'coordinates': [43, 36], 'teleporter': [22, 0]}, {'name': 'Pazuzu 4F to 6F - South Stairs', 'id': 393, 'area': 94, 'coordinates': [43, 60], 'teleporter': [2, 0]}, {'name': 'Pazuzu 4F to 6F - North Stairs', 'id': 394, 'area': 94, 'coordinates': [43, 36], 'teleporter': [3, 0]}, {'name': 'Light Temple - Entrance', 'id': 395, 'area': 95, 'coordinates': [28, 57], 'teleporter': [19, 6]}, {'name': 'Light Temple - Mobius Teleporter Script', 'id': 396, 'area': 95, 'coordinates': [29, 37], 'teleporter': [70, 8]}, {'name': 'Light Temple - Visit Quest Script 1', 'id': 492, 'area': 95, 'coordinates': [34, 39], 'teleporter': [100, 8]}, {'name': 'Light Temple - Visit Quest Script 2', 'id': 493, 'area': 95, 'coordinates': [35, 39], 'teleporter': [100, 8]}, {'name': 'Ship Dock - Mobius Teleporter Script', 'id': 397, 'area': 96, 'coordinates': [15, 18], 'teleporter': [61, 8]}, {'name': 'Ship Dock - From Overworld', 'id': 398, 'area': 96, 'coordinates': [15, 11], 'teleporter': [73, 0]}, {'name': 'Ship Dock - Entrance', 'id': 399, 'area': 96, 'coordinates': [15, 23], 'teleporter': [17, 6]}, {'name': 'Mac Ship Deck - East Entrance Script', 'id': 400, 'area': 97, 'coordinates': [26, 40], 'teleporter': [37, 8]}, {'name': 'Mac Ship Deck - Central Stairs Script', 'id': 401, 'area': 97, 'coordinates': [16, 47], 'teleporter': [50, 8]}, {'name': 'Mac Ship Deck - West Stairs Script', 'id': 402, 'area': 97, 'coordinates': [8, 34], 'teleporter': [51, 8]}, {'name': 'Mac Ship Deck - East Stairs Script', 'id': 403, 'area': 97, 'coordinates': [24, 36], 'teleporter': [52, 8]}, {'name': 'Mac Ship Deck - North Stairs Script', 'id': 404, 'area': 97, 'coordinates': [12, 9], 'teleporter': [53, 8]}, {'name': 'Mac Ship B1 Outer Ring - South Stairs', 'id': 405, 'area': 98, 'coordinates': [16, 45], 'teleporter': [208, 0]}, {'name': 'Mac Ship B1 Outer Ring - West Stairs', 'id': 406, 'area': 98, 'coordinates': [8, 35], 'teleporter': [175, 0]}, {'name': 'Mac Ship B1 Outer Ring - East Stairs', 'id': 407, 'area': 98, 'coordinates': [25, 37], 'teleporter': [172, 0]}, {'name': 'Mac Ship B1 Outer Ring - Northwest Stairs', 'id': 408, 'area': 98, 'coordinates': [10, 23], 'teleporter': [88, 0]}, {'name': 'Mac Ship B1 Square Room - North Stairs', 'id': 409, 'area': 98, 'coordinates': [14, 9], 'teleporter': [141, 0]}, {'name': 'Mac Ship B1 Square Room - South Stairs', 'id': 410, 'area': 98, 'coordinates': [16, 12], 'teleporter': [87, 0]}, {'name': 'Mac Ship B1 Mac Room - Stairs', 'id': 411, 'area': 98, 'coordinates': [16, 51], 'teleporter': [101, 0]}, {'name': 'Mac Ship B1 Central Corridor - South Stairs', 'id': 412, 'area': 98, 'coordinates': [16, 38], 'teleporter': [102, 0]}, {'name': 'Mac Ship B1 Central Corridor - North Stairs', 'id': 413, 'area': 98, 'coordinates': [16, 26], 'teleporter': [86, 0]}, {'name': 'Mac Ship B2 South Corridor - South Stairs', 'id': 414, 'area': 99, 'coordinates': [48, 51], 'teleporter': [57, 1]}, {'name': 'Mac Ship B2 South Corridor - North Stairs Script', 'id': 415, 'area': 99, 'coordinates': [48, 38], 'teleporter': [55, 8]}, {'name': 'Mac Ship B2 North Corridor - South Stairs Script', 'id': 416, 'area': 99, 'coordinates': [48, 27], 'teleporter': [56, 8]}, {'name': 'Mac Ship B2 North Corridor - North Stairs Script', 'id': 417, 'area': 99, 'coordinates': [48, 12], 'teleporter': [57, 8]}, {'name': 'Mac Ship B2 Outer Ring - Northwest Stairs Script', 'id': 418, 'area': 99, 'coordinates': [55, 11], 'teleporter': [58, 8]}, {'name': 'Mac Ship B1 Outer Ring Cleared - South Stairs', 'id': 419, 'area': 100, 'coordinates': [16, 45], 'teleporter': [208, 0]}, {'name': 'Mac Ship B1 Outer Ring Cleared - West Stairs', 'id': 420, 'area': 100, 'coordinates': [8, 35], 'teleporter': [175, 0]}, {'name': 'Mac Ship B1 Outer Ring Cleared - East Stairs', 'id': 421, 'area': 100, 'coordinates': [25, 37], 'teleporter': [172, 0]}, {'name': 'Mac Ship B1 Square Room Cleared - North Stairs', 'id': 422, 'area': 100, 'coordinates': [14, 9], 'teleporter': [141, 0]}, {'name': 'Mac Ship B1 Square Room Cleared - South Stairs', 'id': 423, 'area': 100, 'coordinates': [16, 12], 'teleporter': [87, 0]}, {'name': 'Mac Ship B1 Mac Room Cleared - Main Stairs', 'id': 424, 'area': 100, 'coordinates': [16, 51], 'teleporter': [101, 0]}, {'name': 'Mac Ship B1 Central Corridor Cleared - South Stairs', 'id': 425, 'area': 100, 'coordinates': [16, 38], 'teleporter': [102, 0]}, {'name': 'Mac Ship B1 Central Corridor Cleared - North Stairs', 'id': 426, 'area': 100, 'coordinates': [16, 26], 'teleporter': [86, 0]}, {'name': 'Mac Ship B1 Central Corridor Cleared - Northwest Stairs', 'id': 427, 'area': 100, 'coordinates': [23, 10], 'teleporter': [88, 0]}, {'name': 'Doom Castle Corridor of Destiny - South Entrance', 'id': 428, 'area': 101, 'coordinates': [59, 29], 'teleporter': [84, 0]}, {'name': 'Doom Castle Corridor of Destiny - Ice Floor Entrance', 'id': 429, 'area': 101, 'coordinates': [59, 21], 'teleporter': [35, 2]}, {'name': 'Doom Castle Corridor of Destiny - Lava Floor Entrance', 'id': 430, 'area': 101, 'coordinates': [59, 13], 'teleporter': [209, 0]}, {'name': 'Doom Castle Corridor of Destiny - Sky Floor Entrance', 'id': 431, 'area': 101, 'coordinates': [59, 5], 'teleporter': [211, 0]}, {'name': 'Doom Castle Corridor of Destiny - Hero Room Entrance', 'id': 432, 'area': 101, 'coordinates': [59, 61], 'teleporter': [13, 2]}, {'name': 'Doom Castle Ice Floor - Entrance', 'id': 433, 'area': 102, 'coordinates': [23, 42], 'teleporter': [109, 3]}, {'name': 'Doom Castle Lava Floor - Entrance', 'id': 434, 'area': 103, 'coordinates': [23, 40], 'teleporter': [210, 0]}, {'name': 'Doom Castle Sky Floor - Entrance', 'id': 435, 'area': 104, 'coordinates': [24, 41], 'teleporter': [212, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 1', 'id': 436, 'area': 106, 'coordinates': [15, 5], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 2', 'id': 437, 'area': 106, 'coordinates': [16, 5], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 3', 'id': 438, 'area': 106, 'coordinates': [15, 4], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Dark King Entrance 4', 'id': 439, 'area': 106, 'coordinates': [16, 4], 'teleporter': [54, 0]}, {'name': 'Doom Castle Hero Room - Hero Statue Script', 'id': 440, 'area': 106, 'coordinates': [15, 17], 'teleporter': [24, 8]}, {'name': 'Doom Castle Hero Room - Entrance', 'id': 441, 'area': 106, 'coordinates': [15, 24], 'teleporter': [110, 3]}, {'name': 'Doom Castle Dark King Room - Entrance', 'id': 442, 'area': 107, 'coordinates': [14, 26], 'teleporter': [52, 0]}, {'name': 'Doom Castle Dark King Room - Dark King Script', 'id': 443, 'area': 107, 'coordinates': [14, 15], 'teleporter': [25, 8]}, {'name': 'Doom Castle Dark King Room - Unknown', 'id': 444, 'area': 107, 'coordinates': [47, 54], 'teleporter': [77, 0]}, {'name': 'Overworld - Level Forest', 'id': 445, 'area': 0, 'type': 'Overworld', 'teleporter': [46, 8]}, {'name': 'Overworld - Foresta', 'id': 446, 'area': 0, 'type': 'Overworld', 'teleporter': [2, 1]}, {'name': 'Overworld - Sand Temple', 'id': 447, 'area': 0, 'type': 'Overworld', 'teleporter': [3, 1]}, {'name': 'Overworld - Bone Dungeon', 'id': 448, 'area': 0, 'type': 'Overworld', 'teleporter': [4, 1]}, {'name': 'Overworld - Focus Tower Foresta', 'id': 449, 'area': 0, 'type': 'Overworld', 'teleporter': [5, 1]}, {'name': 'Overworld - Focus Tower Aquaria', 'id': 450, 'area': 0, 'type': 'Overworld', 'teleporter': [19, 1]}, {'name': 'Overworld - Libra Temple', 'id': 451, 'area': 0, 'type': 'Overworld', 'teleporter': [7, 1]}, {'name': 'Overworld - Aquaria', 'id': 452, 'area': 0, 'type': 'Overworld', 'teleporter': [8, 8]}, {'name': 'Overworld - Wintry Cave', 'id': 453, 'area': 0, 'type': 'Overworld', 'teleporter': [10, 1]}, {'name': 'Overworld - Life Temple', 'id': 454, 'area': 0, 'type': 'Overworld', 'teleporter': [11, 1]}, {'name': 'Overworld - Falls Basin', 'id': 455, 'area': 0, 'type': 'Overworld', 'teleporter': [12, 1]}, {'name': 'Overworld - Ice Pyramid', 'id': 456, 'area': 0, 'type': 'Overworld', 'teleporter': [13, 1]}, {'name': "Overworld - Spencer's Place", 'id': 457, 'area': 0, 'type': 'Overworld', 'teleporter': [48, 8]}, {'name': 'Overworld - Wintry Temple', 'id': 458, 'area': 0, 'type': 'Overworld', 'teleporter': [16, 1]}, {'name': 'Overworld - Focus Tower Frozen Strip', 'id': 459, 'area': 0, 'type': 'Overworld', 'teleporter': [17, 1]}, {'name': 'Overworld - Focus Tower Fireburg', 'id': 460, 'area': 0, 'type': 'Overworld', 'teleporter': [18, 1]}, {'name': 'Overworld - Fireburg', 'id': 461, 'area': 0, 'type': 'Overworld', 'teleporter': [20, 1]}, {'name': 'Overworld - Mine', 'id': 462, 'area': 0, 'type': 'Overworld', 'teleporter': [21, 1]}, {'name': 'Overworld - Sealed Temple', 'id': 463, 'area': 0, 'type': 'Overworld', 'teleporter': [22, 1]}, {'name': 'Overworld - Volcano', 'id': 464, 'area': 0, 'type': 'Overworld', 'teleporter': [23, 1]}, {'name': 'Overworld - Lava Dome', 'id': 465, 'area': 0, 'type': 'Overworld', 'teleporter': [24, 1]}, {'name': 'Overworld - Focus Tower Windia', 'id': 466, 'area': 0, 'type': 'Overworld', 'teleporter': [6, 1]}, {'name': 'Overworld - Rope Bridge', 'id': 467, 'area': 0, 'type': 'Overworld', 'teleporter': [25, 1]}, {'name': 'Overworld - Alive Forest', 'id': 468, 'area': 0, 'type': 'Overworld', 'teleporter': [26, 1]}, {'name': 'Overworld - Giant Tree', 'id': 469, 'area': 0, 'type': 'Overworld', 'teleporter': [27, 1]}, {'name': 'Overworld - Kaidge Temple', 'id': 470, 'area': 0, 'type': 'Overworld', 'teleporter': [28, 1]}, {'name': 'Overworld - Windia', 'id': 471, 'area': 0, 'type': 'Overworld', 'teleporter': [29, 1]}, {'name': 'Overworld - Windhole Temple', 'id': 472, 'area': 0, 'type': 'Overworld', 'teleporter': [30, 1]}, {'name': 'Overworld - Mount Gale', 'id': 473, 'area': 0, 'type': 'Overworld', 'teleporter': [31, 1]}, {'name': 'Overworld - Pazuzu Tower', 'id': 474, 'area': 0, 'type': 'Overworld', 'teleporter': [32, 1]}, {'name': 'Overworld - Ship Dock', 'id': 475, 'area': 0, 'type': 'Overworld', 'teleporter': [62, 1]}, {'name': 'Overworld - Doom Castle', 'id': 476, 'area': 0, 'type': 'Overworld', 'teleporter': [33, 1]}, {'name': 'Overworld - Light Temple', 'id': 477, 'area': 0, 'type': 'Overworld', 'teleporter': [34, 1]}, {'name': 'Overworld - Mac Ship', 'id': 478, 'area': 0, 'type': 'Overworld', 'teleporter': [36, 1]}, {'name': 'Overworld - Mac Ship Doom', 'id': 479, 'area': 0, 'type': 'Overworld', 'teleporter': [36, 1]}, {'name': 'Dummy House - Bed Script', 'id': 480, 'area': 17, 'coordinates': [40, 56], 'teleporter': [1, 8]}, {'name': 'Dummy House - Entrance', 'id': 481, 'area': 17, 'coordinates': [41, 59], 'teleporter': [0, 10]}] \ No newline at end of file diff --git a/worlds/ffmq/data/rooms.yaml b/worlds/ffmq/data/rooms.yaml deleted file mode 100644 index e0c2e8d7f9f..00000000000 --- a/worlds/ffmq/data/rooms.yaml +++ /dev/null @@ -1,4026 +0,0 @@ -- name: Overworld - id: 0 - type: "Overworld" - game_objects: [] - links: - - target_room: 220 # To Forest Subregion - access: [] -- name: Subregion Foresta - id: 220 - type: "Subregion" - region: "Foresta" - game_objects: - - name: "Foresta South Battlefield" - object_id: 0x01 - location: "ForestaSouthBattlefield" - location_slot: "ForestaSouthBattlefield" - type: "BattlefieldXp" - access: [] - - name: "Foresta West Battlefield" - object_id: 0x02 - location: "ForestaWestBattlefield" - location_slot: "ForestaWestBattlefield" - type: "BattlefieldItem" - access: [] - - name: "Foresta East Battlefield" - object_id: 0x03 - location: "ForestaEastBattlefield" - location_slot: "ForestaEastBattlefield" - type: "BattlefieldGp" - access: [] - links: - - target_room: 15 # Level Forest - location: "LevelForest" - location_slot: "LevelForest" - entrance: 445 - teleporter: [0x2E, 8] - access: [] - - target_room: 16 # Foresta - location: "Foresta" - location_slot: "Foresta" - entrance: 446 - teleporter: [0x02, 1] - access: [] - - target_room: 24 # Sand Temple - location: "SandTemple" - location_slot: "SandTemple" - entrance: 447 - teleporter: [0x03, 1] - access: [] - - target_room: 25 # Bone Dungeon - location: "BoneDungeon" - location_slot: "BoneDungeon" - entrance: 448 - teleporter: [0x04, 1] - access: [] - - target_room: 3 # Focus Tower Foresta - location: "FocusTowerForesta" - location_slot: "FocusTowerForesta" - entrance: 449 - teleporter: [0x05, 1] - access: [] - - target_room: 221 - access: ["SandCoin"] - - target_room: 224 - access: ["RiverCoin"] - - target_room: 226 - access: ["SunCoin"] -- name: Subregion Aquaria - id: 221 - type: "Subregion" - region: "Aquaria" - game_objects: - - name: "South of Libra Temple Battlefield" - object_id: 0x04 - location: "AquariaBattlefield01" - location_slot: "AquariaBattlefield01" - type: "BattlefieldXp" - access: [] - - name: "East of Libra Temple Battlefield" - object_id: 0x05 - location: "AquariaBattlefield02" - location_slot: "AquariaBattlefield02" - type: "BattlefieldGp" - access: [] - - name: "South of Aquaria Battlefield" - object_id: 0x06 - location: "AquariaBattlefield03" - location_slot: "AquariaBattlefield03" - type: "BattlefieldItem" - access: [] - - name: "South of Wintry Cave Battlefield" - object_id: 0x07 - location: "WintryBattlefield01" - location_slot: "WintryBattlefield01" - type: "BattlefieldXp" - access: [] - - name: "West of Wintry Cave Battlefield" - object_id: 0x08 - location: "WintryBattlefield02" - location_slot: "WintryBattlefield02" - type: "BattlefieldGp" - access: [] - - name: "Ice Pyramid Battlefield" - object_id: 0x09 - location: "PyramidBattlefield01" - location_slot: "PyramidBattlefield01" - type: "BattlefieldXp" - access: [] - links: - - target_room: 10 # Focus Tower Aquaria - location: "FocusTowerAquaria" - location_slot: "FocusTowerAquaria" - entrance: 450 - teleporter: [0x13, 1] - access: [] - - target_room: 39 # Libra Temple - location: "LibraTemple" - location_slot: "LibraTemple" - entrance: 451 - teleporter: [0x07, 1] - access: [] - - target_room: 40 # Aquaria - location: "Aquaria" - location_slot: "Aquaria" - entrance: 452 - teleporter: [0x08, 8] - access: [] - - target_room: 45 # Wintry Cave - location: "WintryCave" - location_slot: "WintryCave" - entrance: 453 - teleporter: [0x0A, 1] - access: [] - - target_room: 52 # Falls Basin - location: "FallsBasin" - location_slot: "FallsBasin" - entrance: 455 - teleporter: [0x0C, 1] - access: [] - - target_room: 54 # Ice Pyramid - location: "IcePyramid" - location_slot: "IcePyramid" - entrance: 456 - teleporter: [0x0D, 1] # Will be switched to a script - access: [] - - target_room: 220 - access: ["SandCoin"] - - target_room: 224 - access: ["SandCoin", "RiverCoin"] - - target_room: 226 - access: ["SandCoin", "SunCoin"] - - target_room: 223 - access: ["SummerAquaria"] -- name: Subregion Life Temple - id: 222 - type: "Subregion" - region: "LifeTemple" - game_objects: [] - links: - - target_room: 51 # Life Temple - location: "LifeTemple" - location_slot: "LifeTemple" - entrance: 454 - teleporter: [0x0B, 1] - access: [] -- name: Subregion Frozen Fields - id: 223 - type: "Subregion" - region: "AquariaFrozenField" - game_objects: - - name: "North of Libra Temple Battlefield" - object_id: 0x0A - location: "LibraBattlefield01" - location_slot: "LibraBattlefield01" - type: "BattlefieldItem" - access: [] - - name: "Aquaria Frozen Field Battlefield" - object_id: 0x0B - location: "LibraBattlefield02" - location_slot: "LibraBattlefield02" - type: "BattlefieldXp" - access: [] - links: - - target_room: 74 # Wintry Temple - location: "WintryTemple" - location_slot: "WintryTemple" - entrance: 458 - teleporter: [0x10, 1] - access: [] - - target_room: 14 # Focus Tower Frozen Strip - location: "FocusTowerFrozen" - location_slot: "FocusTowerFrozen" - entrance: 459 - teleporter: [0x11, 1] - access: [] - - target_room: 221 - access: [] - - target_room: 225 - access: ["SummerAquaria", "DualheadHydra"] -- name: Subregion Fireburg - id: 224 - type: "Subregion" - region: "Fireburg" - game_objects: - - name: "Path to Fireburg Southern Battlefield" - object_id: 0x0C - location: "FireburgBattlefield01" - location_slot: "FireburgBattlefield01" - type: "BattlefieldGp" - access: [] - - name: "Path to Fireburg Central Battlefield" - object_id: 0x0D - location: "FireburgBattlefield02" - location_slot: "FireburgBattlefield02" - type: "BattlefieldItem" - access: [] - - name: "Path to Fireburg Northern Battlefield" - object_id: 0x0E - location: "FireburgBattlefield03" - location_slot: "FireburgBattlefield03" - type: "BattlefieldXp" - access: [] - - name: "Sealed Temple Battlefield" - object_id: 0x0F - location: "MineBattlefield01" - location_slot: "MineBattlefield01" - type: "BattlefieldGp" - access: [] - - name: "Mine Battlefield" - object_id: 0x10 - location: "MineBattlefield02" - location_slot: "MineBattlefield02" - type: "BattlefieldItem" - access: [] - - name: "Boulder Battlefield" - object_id: 0x11 - location: "MineBattlefield03" - location_slot: "MineBattlefield03" - type: "BattlefieldXp" - access: [] - links: - - target_room: 13 # Focus Tower Fireburg - location: "FocusTowerFireburg" - location_slot: "FocusTowerFireburg" - entrance: 460 - teleporter: [0x12, 1] - access: [] - - target_room: 76 # Fireburg - location: "Fireburg" - location_slot: "Fireburg" - entrance: 461 - teleporter: [0x14, 1] - access: [] - - target_room: 84 # Mine - location: "Mine" - location_slot: "Mine" - entrance: 462 - teleporter: [0x15, 1] - access: [] - - target_room: 92 # Sealed Temple - location: "SealedTemple" - location_slot: "SealedTemple" - entrance: 463 - teleporter: [0x16, 1] - access: [] - - target_room: 93 # Volcano - location: "Volcano" - location_slot: "Volcano" - entrance: 464 - teleporter: [0x17, 1] # Also this one / 0x0F, 8 - access: [] - - target_room: 100 # Lava Dome - location: "LavaDome" - location_slot: "LavaDome" - entrance: 465 - teleporter: [0x18, 1] - access: [] - - target_room: 220 - access: ["RiverCoin"] - - target_room: 221 - access: ["SandCoin", "RiverCoin"] - - target_room: 226 - access: ["RiverCoin", "SunCoin"] - - target_room: 225 - access: ["DualheadHydra"] -- name: Subregion Volcano Battlefield - id: 225 - type: "Subregion" - region: "VolcanoBattlefield" - game_objects: - - name: "Volcano Battlefield" - object_id: 0x12 - location: "VolcanoBattlefield01" - location_slot: "VolcanoBattlefield01" - type: "BattlefieldXp" - access: [] - links: - - target_room: 224 - access: ["DualheadHydra"] - - target_room: 223 - access: ["SummerAquaria"] -- name: Subregion Windia - id: 226 - type: "Subregion" - region: "Windia" - game_objects: - - name: "Kaidge Temple Battlefield" - object_id: 0x13 - location: "WindiaBattlefield01" - location_slot: "WindiaBattlefield01" - type: "BattlefieldXp" - access: ["SandCoin", "RiverCoin"] - - name: "South of Windia Battlefield" - object_id: 0x14 - location: "WindiaBattlefield02" - location_slot: "WindiaBattlefield02" - type: "BattlefieldXp" - access: ["SandCoin", "RiverCoin"] - links: - - target_room: 9 # Focus Tower Windia - location: "FocusTowerWindia" - location_slot: "FocusTowerWindia" - entrance: 466 - teleporter: [0x06, 1] - access: [] - - target_room: 123 # Rope Bridge - location: "RopeBridge" - location_slot: "RopeBridge" - entrance: 467 - teleporter: [0x19, 1] - access: [] - - target_room: 124 # Alive Forest - location: "AliveForest" - location_slot: "AliveForest" - entrance: 468 - teleporter: [0x1A, 1] - access: [] - - target_room: 125 # Giant Tree - location: "GiantTree" - location_slot: "GiantTree" - entrance: 469 - teleporter: [0x1B, 1] - access: ["Barred"] - - target_room: 152 # Kaidge Temple - location: "KaidgeTemple" - location_slot: "KaidgeTemple" - entrance: 470 - teleporter: [0x1C, 1] - access: [] - - target_room: 156 # Windia - location: "Windia" - location_slot: "Windia" - entrance: 471 - teleporter: [0x1D, 1] - access: [] - - target_room: 154 # Windhole Temple - location: "WindholeTemple" - location_slot: "WindholeTemple" - entrance: 472 - teleporter: [0x1E, 1] - access: [] - - target_room: 155 # Mount Gale - location: "MountGale" - location_slot: "MountGale" - entrance: 473 - teleporter: [0x1F, 1] - access: [] - - target_room: 166 # Pazuzu Tower - location: "PazuzusTower" - location_slot: "PazuzusTower" - entrance: 474 - teleporter: [0x20, 1] - access: [] - - target_room: 220 - access: ["SunCoin"] - - target_room: 221 - access: ["SandCoin", "SunCoin"] - - target_room: 224 - access: ["RiverCoin", "SunCoin"] - - target_room: 227 - access: ["RainbowBridge"] -- name: Subregion Spencer's Cave - id: 227 - type: "Subregion" - region: "SpencerCave" - game_objects: [] - links: - - target_room: 73 # Spencer's Place - location: "SpencersPlace" - location_slot: "SpencersPlace" - entrance: 457 - teleporter: [0x30, 8] - access: [] - - target_room: 226 - access: ["RainbowBridge"] -- name: Subregion Ship Dock - id: 228 - type: "Subregion" - region: "ShipDock" - game_objects: [] - links: - - target_room: 186 # Ship Dock - location: "ShipDock" - location_slot: "ShipDock" - entrance: 475 - teleporter: [0x3E, 1] - access: [] - - target_room: 229 - access: ["ShipLiberated", "ShipDockAccess"] -- name: Subregion Mac's Ship - id: 229 - type: "Subregion" - region: "MacShip" - game_objects: [] - links: - - target_room: 187 # Mac Ship - location: "MacsShip" - location_slot: "MacsShip" - entrance: 478 - teleporter: [0x24, 1] - access: [] - - target_room: 228 - access: ["ShipLiberated", "ShipDockAccess"] - - target_room: 231 - access: ["ShipLoaned", "ShipDockAccess", "ShipSteeringWheel"] -- name: Subregion Light Temple - id: 230 - type: "Subregion" - region: "LightTemple" - game_objects: [] - links: - - target_room: 185 # Light Temple - location: "LightTemple" - location_slot: "LightTemple" - entrance: 477 - teleporter: [0x23, 1] - access: [] -- name: Subregion Doom Castle - id: 231 - type: "Subregion" - region: "DoomCastle" - game_objects: [] - links: - - target_room: 1 # Doom Castle - location: "DoomCastle" - location_slot: "DoomCastle" - entrance: 476 - teleporter: [0x21, 1] - access: [] - - target_room: 187 # Mac Ship Doom - location: "MacsShipDoom" - location_slot: "MacsShipDoom" - entrance: 479 - teleporter: [0x24, 1] - access: ["Barred"] - - target_room: 229 - access: ["ShipLoaned", "ShipDockAccess", "ShipSteeringWheel"] -- name: Doom Castle - Sand Floor - id: 1 - game_objects: - - name: "Doom Castle B2 - Southeast Chest" - object_id: 0x01 - type: "Chest" - access: ["Bomb"] - - name: "Doom Castle B2 - Bone Ledge Box" - object_id: 0x1E - type: "Box" - access: [] - - name: "Doom Castle B2 - Hook Platform Box" - object_id: 0x1F - type: "Box" - access: ["DragonClaw"] - links: - - target_room: 231 - entrance: 1 - teleporter: [1, 6] - access: [] - - target_room: 5 - entrance: 0 - teleporter: [0, 0] - access: ["DragonClaw", "MegaGrenade"] -- name: Doom Castle - Aero Room - id: 2 - game_objects: - - name: "Doom Castle B2 - Sun Door Chest" - object_id: 0x00 - type: "Chest" - access: [] - links: - - target_room: 4 - entrance: 2 - teleporter: [1, 0] - access: [] -- name: Focus Tower B1 - Main Loop - id: 3 - game_objects: [] - links: - - target_room: 220 - entrance: 3 - teleporter: [2, 6] - access: [] - - target_room: 6 - entrance: 4 - teleporter: [4, 0] - access: [] -- name: Focus Tower B1 - Aero Corridor - id: 4 - game_objects: [] - links: - - target_room: 9 - entrance: 5 - teleporter: [5, 0] - access: [] - - target_room: 2 - entrance: 6 - teleporter: [8, 0] - access: [] -- name: Focus Tower B1 - Inner Loop - id: 5 - game_objects: [] - links: - - target_room: 1 - entrance: 8 - teleporter: [7, 0] - access: [] - - target_room: 201 - entrance: 7 - teleporter: [6, 0] - access: [] -- name: Focus Tower 1F Main Lobby - id: 6 - game_objects: - - name: "Focus Tower 1F - Main Lobby Box" - object_id: 0x21 - type: "Box" - access: [] - links: - - target_room: 3 - entrance: 11 - teleporter: [11, 0] - access: [] - - target_room: 7 - access: ["SandCoin"] - - target_room: 8 - access: ["RiverCoin"] - - target_room: 9 - access: ["SunCoin"] -- name: Focus Tower 1F SandCoin Room - id: 7 - game_objects: [] - links: - - target_room: 6 - access: ["SandCoin"] - - target_room: 10 - entrance: 10 - teleporter: [10, 0] - access: [] -- name: Focus Tower 1F RiverCoin Room - id: 8 - game_objects: [] - links: - - target_room: 6 - access: ["RiverCoin"] - - target_room: 11 - entrance: 14 - teleporter: [14, 0] - access: [] -- name: Focus Tower 1F SunCoin Room - id: 9 - game_objects: [] - links: - - target_room: 6 - access: ["SunCoin"] - - target_room: 4 - entrance: 12 - teleporter: [12, 0] - access: [] - - target_room: 226 - entrance: 9 - teleporter: [3, 6] - access: [] -- name: Focus Tower 1F SkyCoin Room - id: 201 - game_objects: [] - links: - - target_room: 195 - entrance: 13 - teleporter: [13, 0] - access: ["SkyCoin", "FlamerusRex", "IceGolem", "DualheadHydra", "Pazuzu"] - - target_room: 5 - entrance: 15 - teleporter: [15, 0] - access: [] -- name: Focus Tower 2F - Sand Coin Passage - id: 10 - game_objects: - - name: "Focus Tower 2F - Sand Door Chest" - object_id: 0x03 - type: "Chest" - access: [] - links: - - target_room: 221 - entrance: 16 - teleporter: [4, 6] - access: [] - - target_room: 7 - entrance: 17 - teleporter: [17, 0] - access: [] -- name: Focus Tower 2F - River Coin Passage - id: 11 - game_objects: [] - links: - - target_room: 8 - entrance: 18 - teleporter: [18, 0] - access: [] - - target_room: 13 - entrance: 19 - teleporter: [20, 0] - access: [] -- name: Focus Tower 2F - Venus Chest Room - id: 12 - game_objects: - - name: "Focus Tower 2F - Back Door Chest" - object_id: 0x02 - type: "Chest" - access: [] - - name: "Focus Tower 2F - Venus Chest" - object_id: 9 - type: "NPC" - access: ["Bomb", "VenusKey"] - links: - - target_room: 14 - entrance: 20 - teleporter: [19, 0] - access: [] -- name: Focus Tower 3F - Lower Floor - id: 13 - game_objects: - - name: "Focus Tower 3F - River Door Box" - object_id: 0x22 - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 22 - teleporter: [6, 6] - access: [] - - target_room: 11 - entrance: 23 - teleporter: [24, 0] - access: [] -- name: Focus Tower 3F - Upper Floor - id: 14 - game_objects: [] - links: - - target_room: 223 - entrance: 24 - teleporter: [5, 6] - access: [] - - target_room: 12 - entrance: 25 - teleporter: [23, 0] - access: [] -- name: Level Forest - id: 15 - game_objects: - - name: "Level Forest - Northwest Box" - object_id: 0x28 - type: "Box" - access: ["Axe"] - - name: "Level Forest - Northeast Box" - object_id: 0x29 - type: "Box" - access: ["Axe"] - - name: "Level Forest - Middle Box" - object_id: 0x2A - type: "Box" - access: [] - - name: "Level Forest - Southwest Box" - object_id: 0x2B - type: "Box" - access: ["Axe"] - - name: "Level Forest - Southeast Box" - object_id: 0x2C - type: "Box" - access: ["Axe"] - - name: "Minotaur" - object_id: 0 - type: "Trigger" - on_trigger: ["Minotaur"] - access: ["Kaeli1"] - - name: "Level Forest - Old Man" - object_id: 0 - type: "NPC" - access: [] - - name: "Level Forest - Kaeli" - object_id: 1 - type: "NPC" - access: ["Kaeli1", "Minotaur"] - links: - - target_room: 220 - entrance: 28 - teleporter: [25, 0] - access: [] -- name: Foresta - id: 16 - game_objects: - - name: "Foresta - Outside Box" - object_id: 0x2D - type: "Box" - access: ["Axe"] - links: - - target_room: 220 - entrance: 38 - teleporter: [31, 0] - access: [] - - target_room: 17 - entrance: 44 - teleporter: [0, 5] - access: [] - - target_room: 18 - entrance: 42 - teleporter: [32, 4] - access: [] - - target_room: 19 - entrance: 43 - teleporter: [33, 0] - access: [] - - target_room: 20 - entrance: 45 - teleporter: [1, 5] - access: [] -- name: Kaeli's House - id: 17 - game_objects: - - name: "Foresta - Kaeli's House Box" - object_id: 0x2E - type: "Box" - access: [] - - name: "Kaeli Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Kaeli1"] - access: ["TreeWither"] - - name: "Kaeli 2" - object_id: 0 - type: "Trigger" - on_trigger: ["Kaeli2"] - access: ["Kaeli1", "Minotaur", "Elixir"] - links: - - target_room: 16 - entrance: 46 - teleporter: [86, 3] - access: [] -- name: Foresta Houses - Old Man's House Main - id: 18 - game_objects: [] - links: - - target_room: 19 - access: ["BarrelPushed"] - - target_room: 16 - entrance: 47 - teleporter: [34, 0] - access: [] -- name: Foresta Houses - Old Man's House Back - id: 19 - game_objects: - - name: "Foresta - Old Man House Chest" - object_id: 0x05 - type: "Chest" - access: [] - - name: "Old Man Barrel" - object_id: 0 - type: "Trigger" - on_trigger: ["BarrelPushed"] - access: [] - links: - - target_room: 18 - access: ["BarrelPushed"] - - target_room: 16 - entrance: 48 - teleporter: [35, 0] - access: [] -- name: Foresta Houses - Rest House - id: 20 - game_objects: - - name: "Foresta - Rest House Box" - object_id: 0x2F - type: "Box" - access: [] - links: - - target_room: 16 - entrance: 50 - teleporter: [87, 3] - access: [] -- name: Libra Treehouse - id: 21 - game_objects: - - name: "Alive Forest - Libra Treehouse Box" - object_id: 0x32 - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 51 - teleporter: [67, 8] - access: ["LibraCrest"] -- name: Gemini Treehouse - id: 22 - game_objects: - - name: "Alive Forest - Gemini Treehouse Box" - object_id: 0x33 - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 52 - teleporter: [68, 8] - access: ["GeminiCrest"] -- name: Mobius Treehouse - id: 23 - game_objects: - - name: "Alive Forest - Mobius Treehouse West Box" - object_id: 0x30 - type: "Box" - access: [] - - name: "Alive Forest - Mobius Treehouse East Box" - object_id: 0x31 - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 53 - teleporter: [69, 8] - access: ["MobiusCrest"] -- name: Sand Temple - id: 24 - game_objects: - - name: "Tristam Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Tristam"] - access: [] - links: - - target_room: 220 - entrance: 54 - teleporter: [36, 0] - access: [] -- name: Bone Dungeon 1F - id: 25 - game_objects: - - name: "Bone Dungeon 1F - Entrance Room West Box" - object_id: 0x35 - type: "Box" - access: [] - - name: "Bone Dungeon 1F - Entrance Room Middle Box" - object_id: 0x36 - type: "Box" - access: [] - - name: "Bone Dungeon 1F - Entrance Room East Box" - object_id: 0x37 - type: "Box" - access: [] - links: - - target_room: 220 - entrance: 55 - teleporter: [37, 0] - access: [] - - target_room: 26 - entrance: 56 - teleporter: [2, 2] - access: [] -- name: Bone Dungeon B1 - Waterway - id: 26 - game_objects: - - name: "Bone Dungeon B1 - Skull Chest" - object_id: 0x06 - type: "Chest" - access: ["Bomb"] - - name: "Bone Dungeon B1 - Tristam" - object_id: 2 - type: "NPC" - access: ["Tristam"] - - name: "Tristam Bone Dungeon Item Given" - object_id: 0 - type: "Trigger" - on_trigger: ["TristamBoneItemGiven"] - access: ["Tristam"] - links: - - target_room: 25 - entrance: 59 - teleporter: [88, 3] - access: [] - - target_room: 28 - entrance: 57 - teleporter: [3, 2] - access: ["Bomb"] -- name: Bone Dungeon B1 - Checker Room - id: 28 - game_objects: - - name: "Bone Dungeon B1 - Checker Room Box" - object_id: 0x38 - type: "Box" - access: ["Bomb"] - links: - - target_room: 26 - entrance: 61 - teleporter: [89, 3] - access: [] - - target_room: 30 - entrance: 60 - teleporter: [4, 2] - access: [] -- name: Bone Dungeon B1 - Hidden Room - id: 29 - game_objects: - - name: "Bone Dungeon B1 - Ribcage Waterway Box" - object_id: 0x39 - type: "Box" - access: [] - links: - - target_room: 31 - entrance: 62 - teleporter: [91, 3] - access: [] -- name: Bone Dungeon B2 - Exploding Skull Room - First Room - id: 30 - game_objects: - - name: "Bone Dungeon B2 - Spines Room Alcove Box" - object_id: 0x3B - type: "Box" - access: [] - - name: "Long Spine" - object_id: 0 - type: "Trigger" - on_trigger: ["LongSpineBombed"] - access: ["Bomb"] - links: - - target_room: 28 - entrance: 65 - teleporter: [90, 3] - access: [] - - target_room: 31 - access: ["LongSpineBombed"] -- name: Bone Dungeon B2 - Exploding Skull Room - Second Room - id: 31 - game_objects: - - name: "Bone Dungeon B2 - Spines Room Looped Hallway Box" - object_id: 0x3A - type: "Box" - access: [] - - name: "Short Spine" - object_id: 0 - type: "Trigger" - on_trigger: ["ShortSpineBombed"] - access: ["Bomb"] - links: - - target_room: 29 - entrance: 63 - teleporter: [5, 2] - access: ["LongSpineBombed"] - - target_room: 32 - access: ["ShortSpineBombed"] - - target_room: 30 - access: ["LongSpineBombed"] -- name: Bone Dungeon B2 - Exploding Skull Room - Third Room - id: 32 - game_objects: [] - links: - - target_room: 35 - entrance: 64 - teleporter: [6, 2] - access: [] - - target_room: 31 - access: ["ShortSpineBombed"] -- name: Bone Dungeon B2 - Box Room - id: 33 - game_objects: - - name: "Bone Dungeon B2 - Lone Room Box" - object_id: 0x3D - type: "Box" - access: [] - links: - - target_room: 36 - entrance: 66 - teleporter: [93, 3] - access: [] -- name: Bone Dungeon B2 - Quake Room - id: 34 - game_objects: - - name: "Bone Dungeon B2 - Penultimate Room Chest" - object_id: 0x07 - type: "Chest" - access: [] - links: - - target_room: 37 - entrance: 67 - teleporter: [94, 3] - access: [] -- name: Bone Dungeon B2 - Two Skulls Room - First Room - id: 35 - game_objects: - - name: "Bone Dungeon B2 - Two Skulls Room Box" - object_id: 0x3C - type: "Box" - access: [] - - name: "Skull 1" - object_id: 0 - type: "Trigger" - on_trigger: ["Skull1Bombed"] - access: ["Bomb"] - links: - - target_room: 32 - entrance: 71 - teleporter: [92, 3] - access: [] - - target_room: 36 - access: ["Skull1Bombed"] -- name: Bone Dungeon B2 - Two Skulls Room - Second Room - id: 36 - game_objects: - - name: "Skull 2" - object_id: 0 - type: "Trigger" - on_trigger: ["Skull2Bombed"] - access: ["Bomb"] - links: - - target_room: 33 - entrance: 68 - teleporter: [7, 2] - access: [] - - target_room: 37 - access: ["Skull2Bombed"] - - target_room: 35 - access: ["Skull1Bombed"] -- name: Bone Dungeon B2 - Two Skulls Room - Third Room - id: 37 - game_objects: [] - links: - - target_room: 34 - entrance: 69 - teleporter: [8, 2] - access: [] - - target_room: 38 - entrance: 70 - teleporter: [9, 2] - access: ["Bomb"] - - target_room: 36 - access: ["Skull2Bombed"] -- name: Bone Dungeon B2 - Boss Room - id: 38 - game_objects: - - name: "Bone Dungeon B2 - North Box" - object_id: 0x3E - type: "Box" - access: [] - - name: "Bone Dungeon B2 - South Box" - object_id: 0x3F - type: "Box" - access: [] - - name: "Bone Dungeon B2 - Flamerus Rex Chest" - object_id: 0x08 - type: "Chest" - access: [] - - name: "Bone Dungeon B2 - Tristam's Treasure Chest" - object_id: 0x04 - type: "Chest" - access: [] - - name: "Flamerus Rex" - object_id: 0 - type: "Trigger" - on_trigger: ["FlamerusRex"] - access: [] - links: - - target_room: 37 - entrance: 74 - teleporter: [95, 3] - access: [] -- name: Libra Temple - id: 39 - game_objects: - - name: "Libra Temple - Box" - object_id: 0x40 - type: "Box" - access: [] - - name: "Phoebe Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Phoebe1"] - access: [] - links: - - target_room: 221 - entrance: 75 - teleporter: [13, 6] - access: [] - - target_room: 51 - entrance: 76 - teleporter: [59, 8] - access: ["LibraCrest"] -- name: Aquaria - id: 40 - game_objects: - - name: "Summer Aquaria" - object_id: 0 - type: "Trigger" - on_trigger: ["SummerAquaria"] - access: ["WakeWater"] - links: - - target_room: 221 - entrance: 77 - teleporter: [8, 6] - access: [] - - target_room: 41 - entrance: 81 - teleporter: [10, 5] - access: [] - - target_room: 42 - entrance: 82 - teleporter: [44, 4] - access: [] - - target_room: 44 - entrance: 83 - teleporter: [11, 5] - access: [] - - target_room: 71 - entrance: 89 - teleporter: [42, 0] - access: ["SummerAquaria"] - - target_room: 71 - entrance: 90 - teleporter: [43, 0] - access: ["SummerAquaria"] -- name: Phoebe's House - id: 41 - game_objects: - - name: "Aquaria - Phoebe's House Chest" - object_id: 0x41 - type: "Box" - access: [] - links: - - target_room: 40 - entrance: 93 - teleporter: [5, 8] - access: [] -- name: Aquaria Vendor House - id: 42 - game_objects: - - name: "Aquaria - Vendor" - object_id: 4 - type: "NPC" - access: [] - - name: "Aquaria - Vendor House Box" - object_id: 0x42 - type: "Box" - access: [] - links: - - target_room: 40 - entrance: 94 - teleporter: [40, 8] - access: [] - - target_room: 43 - entrance: 95 - teleporter: [47, 0] - access: [] -- name: Aquaria Gemini Room - id: 43 - game_objects: [] - links: - - target_room: 42 - entrance: 97 - teleporter: [48, 0] - access: [] - - target_room: 81 - entrance: 96 - teleporter: [72, 8] - access: ["GeminiCrest"] -- name: Aquaria INN - id: 44 - game_objects: [] - links: - - target_room: 40 - entrance: 98 - teleporter: [75, 8] - access: [] -- name: Wintry Cave 1F - East Ledge - id: 45 - game_objects: - - name: "Wintry Cave 1F - North Box" - object_id: 0x43 - type: "Box" - access: [] - - name: "Wintry Cave 1F - Entrance Box" - object_id: 0x46 - type: "Box" - access: [] - - name: "Wintry Cave 1F - Slippery Cliff Box" - object_id: 0x44 - type: "Box" - access: ["Claw"] - - name: "Wintry Cave 1F - Phoebe" - object_id: 5 - type: "NPC" - access: ["Phoebe1"] - links: - - target_room: 221 - entrance: 99 - teleporter: [49, 0] - access: [] - - target_room: 49 - entrance: 100 - teleporter: [14, 2] - access: ["Bomb"] - - target_room: 46 - access: ["Claw"] -- name: Wintry Cave 1F - Central Space - id: 46 - game_objects: - - name: "Wintry Cave 1F - Scenic Overlook Box" - object_id: 0x45 - type: "Box" - access: ["Claw"] - links: - - target_room: 45 - access: ["Claw"] - - target_room: 47 - access: ["Claw"] -- name: Wintry Cave 1F - West Ledge - id: 47 - game_objects: [] - links: - - target_room: 48 - entrance: 101 - teleporter: [15, 2] - access: ["Bomb"] - - target_room: 46 - access: ["Claw"] -- name: Wintry Cave 2F - id: 48 - game_objects: - - name: "Wintry Cave 2F - West Left Box" - object_id: 0x47 - type: "Box" - access: [] - - name: "Wintry Cave 2F - West Right Box" - object_id: 0x48 - type: "Box" - access: [] - - name: "Wintry Cave 2F - East Left Box" - object_id: 0x49 - type: "Box" - access: [] - - name: "Wintry Cave 2F - East Right Box" - object_id: 0x4A - type: "Box" - access: [] - links: - - target_room: 47 - entrance: 104 - teleporter: [97, 3] - access: [] - - target_room: 50 - entrance: 103 - teleporter: [50, 0] - access: [] -- name: Wintry Cave 3F Top - id: 49 - game_objects: - - name: "Wintry Cave 3F - West Box" - object_id: 0x4B - type: "Box" - access: [] - - name: "Wintry Cave 3F - East Box" - object_id: 0x4C - type: "Box" - access: [] - links: - - target_room: 45 - entrance: 105 - teleporter: [96, 3] - access: [] -- name: Wintry Cave 3F Bottom - id: 50 - game_objects: - - name: "Wintry Cave 3F - Squidite Chest" - object_id: 0x09 - type: "Chest" - access: ["Phanquid"] - - name: "Phanquid" - object_id: 0 - type: "Trigger" - on_trigger: ["Phanquid"] - access: [] - - name: "Wintry Cave 3F - Before Boss Box" - object_id: 0x4D - type: "Box" - access: [] - links: - - target_room: 48 - entrance: 106 - teleporter: [51, 0] - access: [] -- name: Life Temple - id: 51 - game_objects: - - name: "Life Temple - Box" - object_id: 0x4E - type: "Box" - access: [] - - name: "Life Temple - Mysterious Man" - object_id: 6 - type: "NPC" - access: [] - links: - - target_room: 222 - entrance: 107 - teleporter: [14, 6] - access: [] - - target_room: 39 - entrance: 108 - teleporter: [60, 8] - access: ["LibraCrest"] -- name: Fall Basin - id: 52 - game_objects: - - name: "Falls Basin - Snow Crab Chest" - object_id: 0x0A - type: "Chest" - access: ["FreezerCrab"] - - name: "Freezer Crab" - object_id: 0 - type: "Trigger" - on_trigger: ["FreezerCrab"] - access: [] - - name: "Falls Basin - Box" - object_id: 0x4F - type: "Box" - access: [] - links: - - target_room: 221 - entrance: 111 - teleporter: [53, 0] - access: [] -- name: Ice Pyramid B1 Taunt Room - id: 53 - game_objects: - - name: "Ice Pyramid B1 - Chest" - object_id: 0x0B - type: "Chest" - access: [] - - name: "Ice Pyramid B1 - West Box" - object_id: 0x50 - type: "Box" - access: [] - - name: "Ice Pyramid B1 - North Box" - object_id: 0x51 - type: "Box" - access: [] - - name: "Ice Pyramid B1 - East Box" - object_id: 0x52 - type: "Box" - access: [] - links: - - target_room: 68 - entrance: 113 - teleporter: [55, 0] - access: [] -- name: Ice Pyramid 1F Maze Lobby - id: 54 - game_objects: - - name: "Ice Pyramid 1F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid1FStatue"] - access: ["Sword"] - links: - - target_room: 221 - entrance: 114 - teleporter: [56, 0] - access: [] - - target_room: 55 - access: ["IcePyramid1FStatue"] -- name: Ice Pyramid 1F Maze - id: 55 - game_objects: - - name: "Ice Pyramid 1F - East Alcove Chest" - object_id: 0x0D - type: "Chest" - access: [] - - name: "Ice Pyramid 1F - Sandwiched Alcove Box" - object_id: 0x53 - type: "Box" - access: [] - - name: "Ice Pyramid 1F - Southwest Left Box" - object_id: 0x54 - type: "Box" - access: [] - - name: "Ice Pyramid 1F - Southwest Right Box" - object_id: 0x55 - type: "Box" - access: [] - links: - - target_room: 56 - entrance: 116 - teleporter: [57, 0] - access: [] - - target_room: 57 - entrance: 117 - teleporter: [58, 0] - access: [] - - target_room: 58 - entrance: 118 - teleporter: [59, 0] - access: [] - - target_room: 59 - entrance: 119 - teleporter: [60, 0] - access: [] - - target_room: 60 - entrance: 120 - teleporter: [61, 0] - access: [] - - target_room: 54 - access: ["IcePyramid1FStatue"] -- name: Ice Pyramid 2F South Tiled Room - id: 56 - game_objects: - - name: "Ice Pyramid 2F - South Side Glass Door Box" - object_id: 0x57 - type: "Box" - access: ["Sword"] - - name: "Ice Pyramid 2F - South Side East Box" - object_id: 0x5B - type: "Box" - access: [] - links: - - target_room: 55 - entrance: 122 - teleporter: [62, 0] - access: [] - - target_room: 61 - entrance: 123 - teleporter: [67, 0] - access: [] -- name: Ice Pyramid 2F West Room - id: 57 - game_objects: - - name: "Ice Pyramid 2F - Northwest Room Box" - object_id: 0x5A - type: "Box" - access: [] - links: - - target_room: 55 - entrance: 124 - teleporter: [63, 0] - access: [] -- name: Ice Pyramid 2F Center Room - id: 58 - game_objects: - - name: "Ice Pyramid 2F - Center Room Box" - object_id: 0x56 - type: "Box" - access: [] - links: - - target_room: 55 - entrance: 125 - teleporter: [64, 0] - access: [] -- name: Ice Pyramid 2F Small North Room - id: 59 - game_objects: - - name: "Ice Pyramid 2F - North Room Glass Door Box" - object_id: 0x58 - type: "Box" - access: ["Sword"] - links: - - target_room: 55 - entrance: 126 - teleporter: [65, 0] - access: [] -- name: Ice Pyramid 2F North Corridor - id: 60 - game_objects: - - name: "Ice Pyramid 2F - North Corridor Glass Door Box" - object_id: 0x59 - type: "Box" - access: ["Sword"] - links: - - target_room: 55 - entrance: 127 - teleporter: [66, 0] - access: [] - - target_room: 62 - entrance: 128 - teleporter: [68, 0] - access: [] -- name: Ice Pyramid 3F Two Boxes Room - id: 61 - game_objects: - - name: "Ice Pyramid 3F - Staircase Dead End Left Box" - object_id: 0x5E - type: "Box" - access: [] - - name: "Ice Pyramid 3F - Staircase Dead End Right Box" - object_id: 0x5F - type: "Box" - access: [] - links: - - target_room: 56 - entrance: 129 - teleporter: [69, 0] - access: [] -- name: Ice Pyramid 3F Main Loop - id: 62 - game_objects: - - name: "Ice Pyramid 3F - Inner Room North Box" - object_id: 0x5C - type: "Box" - access: [] - - name: "Ice Pyramid 3F - Inner Room South Box" - object_id: 0x5D - type: "Box" - access: [] - - name: "Ice Pyramid 3F - East Alcove Box" - object_id: 0x60 - type: "Box" - access: [] - - name: "Ice Pyramid 3F - Leapfrog Box" - object_id: 0x61 - type: "Box" - access: [] - - name: "Ice Pyramid 3F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid3FStatue"] - access: ["Sword"] - links: - - target_room: 60 - entrance: 130 - teleporter: [70, 0] - access: [] - - target_room: 63 - access: ["IcePyramid3FStatue"] -- name: Ice Pyramid 3F Blocked Room - id: 63 - game_objects: [] - links: - - target_room: 64 - entrance: 131 - teleporter: [71, 0] - access: [] - - target_room: 62 - access: ["IcePyramid3FStatue"] -- name: Ice Pyramid 4F Main Loop - id: 64 - game_objects: [] - links: - - target_room: 66 - entrance: 133 - teleporter: [73, 0] - access: [] - - target_room: 63 - entrance: 132 - teleporter: [72, 0] - access: [] - - target_room: 65 - access: ["IcePyramid4FStatue"] -- name: Ice Pyramid 4F Treasure Room - id: 65 - game_objects: - - name: "Ice Pyramid 4F - Chest" - object_id: 0x0C - type: "Chest" - access: [] - - name: "Ice Pyramid 4F - Northwest Box" - object_id: 0x62 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - West Left Box" - object_id: 0x63 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - West Right Box" - object_id: 0x64 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - South Left Box" - object_id: 0x65 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - South Right Box" - object_id: 0x66 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - East Left Box" - object_id: 0x67 - type: "Box" - access: [] - - name: "Ice Pyramid 4F - East Right Box" - object_id: 0x68 - type: "Box" - access: [] - - name: "Ice Pyramid 4F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid4FStatue"] - access: ["Sword"] - links: - - target_room: 64 - access: ["IcePyramid4FStatue"] -- name: Ice Pyramid 5F Leap of Faith Room - id: 66 - game_objects: - - name: "Ice Pyramid 5F - Glass Door Left Box" - object_id: 0x69 - type: "Box" - access: ["IcePyramid5FStatue"] - - name: "Ice Pyramid 5F - West Ledge Box" - object_id: 0x6A - type: "Box" - access: [] - - name: "Ice Pyramid 5F - South Shelf Box" - object_id: 0x6B - type: "Box" - access: [] - - name: "Ice Pyramid 5F - South Leapfrog Box" - object_id: 0x6C - type: "Box" - access: [] - - name: "Ice Pyramid 5F - Glass Door Right Box" - object_id: 0x6D - type: "Box" - access: ["IcePyramid5FStatue"] - - name: "Ice Pyramid 5F - North Box" - object_id: 0x6E - type: "Box" - access: [] - links: - - target_room: 64 - entrance: 134 - teleporter: [74, 0] - access: [] - - target_room: 65 - access: [] - - target_room: 53 - access: ["Bomb", "Claw", "Sword"] -- name: Ice Pyramid 5F Stairs to Ice Golem - id: 67 - game_objects: - - name: "Ice Pyramid 5F Statue" - object_id: 0 - type: "Trigger" - on_trigger: ["IcePyramid5FStatue"] - access: ["Sword"] - links: - - target_room: 69 - entrance: 137 - teleporter: [76, 0] - access: [] - - target_room: 65 - access: [] - - target_room: 70 - entrance: 136 - teleporter: [75, 0] - access: [] -- name: Ice Pyramid Climbing Wall Room Lower Space - id: 68 - game_objects: [] - links: - - target_room: 53 - entrance: 139 - teleporter: [78, 0] - access: [] - - target_room: 69 - access: ["Claw"] -- name: Ice Pyramid Climbing Wall Room Upper Space - id: 69 - game_objects: [] - links: - - target_room: 67 - entrance: 140 - teleporter: [79, 0] - access: [] - - target_room: 68 - access: ["Claw"] -- name: Ice Pyramid Ice Golem Room - id: 70 - game_objects: - - name: "Ice Pyramid 6F - Ice Golem Chest" - object_id: 0x0E - type: "Chest" - access: ["IceGolem"] - - name: "Ice Golem" - object_id: 0 - type: "Trigger" - on_trigger: ["IceGolem"] - access: [] - links: - - target_room: 67 - entrance: 141 - teleporter: [80, 0] - access: [] - - target_room: 66 - access: [] -- name: Spencer Waterfall - id: 71 - game_objects: [] - links: - - target_room: 72 - entrance: 143 - teleporter: [81, 0] - access: [] - - target_room: 40 - entrance: 145 - teleporter: [82, 0] - access: [] - - target_room: 40 - entrance: 148 - teleporter: [83, 0] - access: [] -- name: Spencer Cave Normal Main - id: 72 - game_objects: - - name: "Spencer's Cave - Box" - object_id: 0x6F - type: "Box" - access: ["Claw"] - - name: "Spencer's Cave - Spencer" - object_id: 8 - type: "NPC" - access: [] - - name: "Spencer's Cave - Locked Chest" - object_id: 13 - type: "NPC" - access: ["VenusKey"] - links: - - target_room: 71 - entrance: 150 - teleporter: [85, 0] - access: [] -- name: Spencer Cave Normal South Ledge - id: 73 - game_objects: - - name: "Collapse Spencer's Cave" - object_id: 0 - type: "Trigger" - on_trigger: ["ShipLiberated"] - access: ["MegaGrenade"] - links: - - target_room: 227 - entrance: 151 - teleporter: [7, 6] - access: [] - - target_room: 203 - access: ["MegaGrenade"] -# - target_room: 72 # access to spencer? -# access: ["MegaGrenade"] -- name: Spencer Cave Caved In Main Loop - id: 203 - game_objects: [] - links: - - target_room: 73 - access: [] - - target_room: 207 - entrance: 156 - teleporter: [36, 8] - access: ["MobiusCrest"] - - target_room: 204 - access: ["Claw"] - - target_room: 205 - access: ["Bomb"] -- name: Spencer Cave Caved In Waters - id: 204 - game_objects: - - name: "Bomb Libra Block" - object_id: 0 - type: "Trigger" - on_trigger: ["SpencerCaveLibraBlockBombed"] - access: ["MegaGrenade", "Claw"] - links: - - target_room: 203 - access: ["Claw"] -- name: Spencer Cave Caved In Libra Nook - id: 205 - game_objects: [] - links: - - target_room: 206 - entrance: 153 - teleporter: [33, 8] - access: ["LibraCrest"] -- name: Spencer Cave Caved In Libra Corridor - id: 206 - game_objects: [] - links: - - target_room: 205 - entrance: 154 - teleporter: [34, 8] - access: ["LibraCrest"] - - target_room: 207 - access: ["SpencerCaveLibraBlockBombed"] -- name: Spencer Cave Caved In Mobius Chest - id: 207 - game_objects: - - name: "Spencer's Cave - Mobius Chest" - object_id: 0x0F - type: "Chest" - access: [] - links: - - target_room: 203 - entrance: 155 - teleporter: [35, 8] - access: ["MobiusCrest"] - - target_room: 206 - access: ["Bomb"] -- name: Wintry Temple Outer Room - id: 74 - game_objects: [] - links: - - target_room: 223 - entrance: 157 - teleporter: [15, 6] - access: [] -- name: Wintry Temple Inner Room - id: 75 - game_objects: - - name: "Wintry Temple - West Box" - object_id: 0x70 - type: "Box" - access: [] - - name: "Wintry Temple - North Box" - object_id: 0x71 - type: "Box" - access: [] - links: - - target_room: 92 - entrance: 158 - teleporter: [62, 8] - access: ["GeminiCrest"] -- name: Fireburg Upper Plaza - id: 76 - game_objects: [] - links: - - target_room: 224 - entrance: 159 - teleporter: [9, 6] - access: [] - - target_room: 80 - entrance: 163 - teleporter: [91, 0] - access: [] - - target_room: 77 - entrance: 164 - teleporter: [98, 8] # original value [16, 2] - access: [] - - target_room: 82 - entrance: 165 - teleporter: [96, 8] # original value [17, 2] - access: [] - - target_room: 208 - access: ["Claw"] -- name: Fireburg Lower Plaza - id: 208 - game_objects: - - name: "Fireburg - Hidden Tunnel Box" - object_id: 0x74 - type: "Box" - access: [] - links: - - target_room: 76 - access: ["Claw"] - - target_room: 78 - entrance: 166 - teleporter: [11, 8] - access: ["MultiKey"] -- name: Reuben's House - id: 77 - game_objects: - - name: "Fireburg - Reuben's House Arion" - object_id: 14 - type: "NPC" - access: ["ReubenDadSaved"] - - name: "Reuben Companion" - object_id: 0 - type: "Trigger" - on_trigger: ["Reuben1"] - access: [] - - name: "Fireburg - Reuben's House Box" - object_id: 0x75 - type: "Box" - access: [] - links: - - target_room: 76 - entrance: 167 - teleporter: [98, 3] - access: [] -- name: GrenadeMan's House - id: 78 - game_objects: - - name: "Fireburg - Locked House Man" - object_id: 12 - type: "NPC" - access: [] - links: - - target_room: 208 - entrance: 168 - teleporter: [9, 8] - access: ["MultiKey"] - - target_room: 79 - entrance: 169 - teleporter: [93, 0] - access: [] -- name: GrenadeMan's Mobius Room - id: 79 - game_objects: [] - links: - - target_room: 78 - entrance: 170 - teleporter: [94, 0] - access: [] - - target_room: 161 - entrance: 171 - teleporter: [54, 8] - access: ["MobiusCrest"] -- name: Fireburg Vendor House - id: 80 - game_objects: - - name: "Fireburg - Vendor" - object_id: 11 - type: "NPC" - access: [] - links: - - target_room: 76 - entrance: 172 - teleporter: [95, 0] - access: [] - - target_room: 81 - entrance: 173 - teleporter: [96, 0] - access: [] -- name: Fireburg Gemini Room - id: 81 - game_objects: [] - links: - - target_room: 80 - entrance: 174 - teleporter: [97, 0] - access: [] - - target_room: 43 - entrance: 175 - teleporter: [45, 8] - access: ["GeminiCrest"] -- name: Fireburg Hotel Lobby - id: 82 - game_objects: - - name: "Fireburg - Tristam" - object_id: 10 - type: "NPC" - access: ["Tristam", "TristamBoneItemGiven"] - links: - - target_room: 76 - entrance: 177 - teleporter: [99, 3] - access: [] - - target_room: 83 - entrance: 176 - teleporter: [213, 0] - access: [] -- name: Fireburg Hotel Beds - id: 83 - game_objects: [] - links: - - target_room: 82 - entrance: 178 - teleporter: [214, 0] - access: [] -- name: Mine Exterior North West Platforms - id: 84 - game_objects: [] - links: - - target_room: 224 - entrance: 179 - teleporter: [98, 0] - access: [] - - target_room: 88 - entrance: 181 - teleporter: [20, 2] - access: ["Bomb"] - - target_room: 85 - access: ["Claw"] - - target_room: 86 - access: ["Claw"] - - target_room: 87 - access: ["Claw"] -- name: Mine Exterior Central Ledge - id: 85 - game_objects: [] - links: - - target_room: 90 - entrance: 183 - teleporter: [22, 2] - access: ["Bomb"] - - target_room: 84 - access: ["Claw"] -- name: Mine Exterior North Ledge - id: 86 - game_objects: [] - links: - - target_room: 89 - entrance: 182 - teleporter: [21, 2] - access: ["Bomb"] - - target_room: 85 - access: ["Claw"] -- name: Mine Exterior South East Platforms - id: 87 - game_objects: - - name: "Jinn" - object_id: 0 - type: "Trigger" - on_trigger: ["Jinn"] - access: [] - links: - - target_room: 91 - entrance: 180 - teleporter: [99, 0] - access: ["Jinn"] - - target_room: 86 - access: [] - - target_room: 85 - access: ["Claw"] -- name: Mine Parallel Room - id: 88 - game_objects: - - name: "Mine - Parallel Room West Box" - object_id: 0x77 - type: "Box" - access: ["Claw"] - - name: "Mine - Parallel Room East Box" - object_id: 0x78 - type: "Box" - access: ["Claw"] - links: - - target_room: 84 - entrance: 185 - teleporter: [100, 3] - access: [] -- name: Mine Crescent Room - id: 89 - game_objects: - - name: "Mine - Crescent Room Chest" - object_id: 0x10 - type: "Chest" - access: [] - links: - - target_room: 86 - entrance: 186 - teleporter: [101, 3] - access: [] -- name: Mine Climbing Room - id: 90 - game_objects: - - name: "Mine - Glitchy Collision Cave Box" - object_id: 0x76 - type: "Box" - access: ["Claw"] - links: - - target_room: 85 - entrance: 187 - teleporter: [102, 3] - access: [] -- name: Mine Cliff - id: 91 - game_objects: - - name: "Mine - Cliff Southwest Box" - object_id: 0x79 - type: "Box" - access: [] - - name: "Mine - Cliff Northwest Box" - object_id: 0x7A - type: "Box" - access: [] - - name: "Mine - Cliff Northeast Box" - object_id: 0x7B - type: "Box" - access: [] - - name: "Mine - Cliff Southeast Box" - object_id: 0x7C - type: "Box" - access: [] - - name: "Mine - Reuben" - object_id: 7 - type: "NPC" - access: ["Reuben1"] - - name: "Reuben's dad Saved" - object_id: 0 - type: "Trigger" - on_trigger: ["ReubenDadSaved"] - access: ["MegaGrenade"] - links: - - target_room: 87 - entrance: 188 - teleporter: [100, 0] - access: [] -- name: Sealed Temple - id: 92 - game_objects: - - name: "Sealed Temple - West Box" - object_id: 0x7D - type: "Box" - access: [] - - name: "Sealed Temple - East Box" - object_id: 0x7E - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 190 - teleporter: [16, 6] - access: [] - - target_room: 75 - entrance: 191 - teleporter: [63, 8] - access: ["GeminiCrest"] -- name: Volcano Base - id: 93 - game_objects: - - name: "Volcano - Base Chest" - object_id: 0x11 - type: "Chest" - access: [] - - name: "Volcano - Base West Box" - object_id: 0x7F - type: "Box" - access: [] - - name: "Volcano - Base East Left Box" - object_id: 0x80 - type: "Box" - access: [] - - name: "Volcano - Base East Right Box" - object_id: 0x81 - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 192 - teleporter: [103, 0] - access: [] - - target_room: 98 - entrance: 196 - teleporter: [31, 8] - access: [] - - target_room: 96 - entrance: 197 - teleporter: [30, 8] - access: [] -- name: Volcano Top Left - id: 94 - game_objects: - - name: "Volcano - Medusa Chest" - object_id: 0x12 - type: "Chest" - access: ["Medusa"] - - name: "Medusa" - object_id: 0 - type: "Trigger" - on_trigger: ["Medusa"] - access: [] - - name: "Volcano - Behind Medusa Box" - object_id: 0x82 - type: "Box" - access: [] - links: - - target_room: 209 - entrance: 199 - teleporter: [26, 8] - access: [] -- name: Volcano Top Right - id: 95 - game_objects: - - name: "Volcano - Top of the Volcano Left Box" - object_id: 0x83 - type: "Box" - access: [] - - name: "Volcano - Top of the Volcano Right Box" - object_id: 0x84 - type: "Box" - access: [] - links: - - target_room: 99 - entrance: 200 - teleporter: [79, 8] - access: [] -- name: Volcano Right Path - id: 96 - game_objects: - - name: "Volcano - Right Path Box" - object_id: 0x87 - type: "Box" - access: [] - links: - - target_room: 93 - entrance: 201 - teleporter: [15, 8] - access: [] -- name: Volcano Left Path - id: 98 - game_objects: - - name: "Volcano - Left Path Box" - object_id: 0x86 - type: "Box" - access: [] - links: - - target_room: 93 - entrance: 204 - teleporter: [27, 8] - access: [] - - target_room: 99 - entrance: 202 - teleporter: [25, 2] - access: [] - - target_room: 209 - entrance: 203 - teleporter: [26, 2] - access: [] -- name: Volcano Cross Left-Right - id: 99 - game_objects: [] - links: - - target_room: 95 - entrance: 206 - teleporter: [29, 8] - access: [] - - target_room: 98 - entrance: 205 - teleporter: [103, 3] - access: [] -- name: Volcano Cross Right-Left - id: 209 - game_objects: - - name: "Volcano - Crossover Section Box" - object_id: 0x85 - type: "Box" - access: [] - links: - - target_room: 98 - entrance: 208 - teleporter: [104, 3] - access: [] - - target_room: 94 - entrance: 207 - teleporter: [28, 8] - access: [] -- name: Lava Dome Inner Ring Main Loop - id: 100 - game_objects: - - name: "Lava Dome - Exterior Caldera Near Switch Cliff Box" - object_id: 0x88 - type: "Box" - access: [] - - name: "Lava Dome - Exterior South Cliff Box" - object_id: 0x89 - type: "Box" - access: [] - links: - - target_room: 224 - entrance: 209 - teleporter: [104, 0] - access: [] - - target_room: 113 - entrance: 211 - teleporter: [105, 0] - access: [] - - target_room: 114 - entrance: 212 - teleporter: [106, 0] - access: [] - - target_room: 116 - entrance: 213 - teleporter: [108, 0] - access: [] - - target_room: 118 - entrance: 214 - teleporter: [111, 0] - access: [] -- name: Lava Dome Inner Ring Center Ledge - id: 101 - game_objects: - - name: "Lava Dome - Exterior Center Dropoff Ledge Box" - object_id: 0x8A - type: "Box" - access: [] - links: - - target_room: 115 - entrance: 215 - teleporter: [107, 0] - access: [] - - target_room: 100 - access: ["Claw"] -- name: Lava Dome Inner Ring Plate Ledge - id: 102 - game_objects: - - name: "Lava Dome Plate" - object_id: 0 - type: "Trigger" - on_trigger: ["LavaDomePlate"] - access: [] - links: - - target_room: 119 - entrance: 216 - teleporter: [109, 0] - access: [] -- name: Lava Dome Inner Ring Upper Ledge West - id: 103 - game_objects: [] - links: - - target_room: 111 - entrance: 219 - teleporter: [112, 0] - access: [] - - target_room: 108 - entrance: 220 - teleporter: [113, 0] - access: [] - - target_room: 104 - access: ["Claw"] - - target_room: 100 - access: ["Claw"] -- name: Lava Dome Inner Ring Upper Ledge East - id: 104 - game_objects: [] - links: - - target_room: 110 - entrance: 218 - teleporter: [110, 0] - access: [] - - target_room: 103 - access: ["Claw"] -- name: Lava Dome Inner Ring Big Door Ledge - id: 105 - game_objects: [] - links: - - target_room: 107 - entrance: 221 - teleporter: [114, 0] - access: [] - - target_room: 121 - entrance: 222 - teleporter: [29, 2] - access: ["LavaDomePlate"] -- name: Lava Dome Inner Ring Tiny Bottom Ledge - id: 106 - game_objects: - - name: "Lava Dome - Exterior Dead End Caldera Box" - object_id: 0x8B - type: "Box" - access: [] - links: - - target_room: 120 - entrance: 226 - teleporter: [115, 0] - access: [] -- name: Lava Dome Jump Maze II - id: 107 - game_objects: - - name: "Lava Dome - Gold Maze Northwest Box" - object_id: 0x8C - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Southwest Box" - object_id: 0xF6 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Northeast Box" - object_id: 0xF7 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze North Box" - object_id: 0xF8 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Center Box" - object_id: 0xF9 - type: "Box" - access: [] - - name: "Lava Dome - Gold Maze Southeast Box" - object_id: 0xFA - type: "Box" - access: [] - links: - - target_room: 105 - entrance: 227 - teleporter: [116, 0] - access: [] - - target_room: 108 - entrance: 228 - teleporter: [119, 0] - access: [] - - target_room: 120 - entrance: 229 - teleporter: [120, 0] - access: [] -- name: Lava Dome Up-Down Corridor - id: 108 - game_objects: [] - links: - - target_room: 107 - entrance: 231 - teleporter: [118, 0] - access: [] - - target_room: 103 - entrance: 230 - teleporter: [117, 0] - access: [] -- name: Lava Dome Jump Maze I - id: 109 - game_objects: - - name: "Lava Dome - Bare Maze Leapfrog Alcove North Box" - object_id: 0x8D - type: "Box" - access: [] - - name: "Lava Dome - Bare Maze Leapfrog Alcove South Box" - object_id: 0x8E - type: "Box" - access: [] - - name: "Lava Dome - Bare Maze Center Box" - object_id: 0x8F - type: "Box" - access: [] - - name: "Lava Dome - Bare Maze Southwest Box" - object_id: 0x90 - type: "Box" - access: [] - links: - - target_room: 118 - entrance: 232 - teleporter: [121, 0] - access: [] - - target_room: 111 - entrance: 233 - teleporter: [122, 0] - access: [] -- name: Lava Dome Pointless Room - id: 110 - game_objects: [] - links: - - target_room: 104 - entrance: 234 - teleporter: [123, 0] - access: [] -- name: Lava Dome Lower Moon Helm Room - id: 111 - game_objects: - - name: "Lava Dome - U-Bend Room North Box" - object_id: 0x92 - type: "Box" - access: [] - - name: "Lava Dome - U-Bend Room South Box" - object_id: 0x93 - type: "Box" - access: [] - links: - - target_room: 103 - entrance: 235 - teleporter: [124, 0] - access: [] - - target_room: 109 - entrance: 236 - teleporter: [125, 0] - access: [] -- name: Lava Dome Moon Helm Room - id: 112 - game_objects: - - name: "Lava Dome - Beyond River Room Chest" - object_id: 0x13 - type: "Chest" - access: [] - - name: "Lava Dome - Beyond River Room Box" - object_id: 0x91 - type: "Box" - access: [] - links: - - target_room: 117 - entrance: 237 - teleporter: [126, 0] - access: [] -- name: Lava Dome Three Jumps Room - id: 113 - game_objects: - - name: "Lava Dome - Three Jumps Room Box" - object_id: 0x96 - type: "Box" - access: [] - links: - - target_room: 100 - entrance: 238 - teleporter: [127, 0] - access: [] -- name: Lava Dome Life Chest Room Lower Ledge - id: 114 - game_objects: - - name: "Lava Dome - Gold Bar Room Boulder Chest" - object_id: 0x1C - type: "Chest" - access: ["MegaGrenade"] - links: - - target_room: 100 - entrance: 239 - teleporter: [128, 0] - access: [] - - target_room: 115 - access: ["Claw"] -- name: Lava Dome Life Chest Room Upper Ledge - id: 115 - game_objects: - - name: "Lava Dome - Gold Bar Room Leapfrog Alcove Box West" - object_id: 0x94 - type: "Box" - access: [] - - name: "Lava Dome - Gold Bar Room Leapfrog Alcove Box East" - object_id: 0x95 - type: "Box" - access: [] - links: - - target_room: 101 - entrance: 240 - teleporter: [129, 0] - access: [] - - target_room: 114 - access: ["Claw"] -- name: Lava Dome Big Jump Room Main Area - id: 116 - game_objects: - - name: "Lava Dome - Lava River Room North Box" - object_id: 0x98 - type: "Box" - access: [] - - name: "Lava Dome - Lava River Room East Box" - object_id: 0x99 - type: "Box" - access: [] - - name: "Lava Dome - Lava River Room South Box" - object_id: 0x9A - type: "Box" - access: [] - links: - - target_room: 100 - entrance: 241 - teleporter: [133, 0] - access: [] - - target_room: 119 - entrance: 243 - teleporter: [132, 0] - access: [] - - target_room: 117 - access: ["MegaGrenade"] -- name: Lava Dome Big Jump Room MegaGrenade Area - id: 117 - game_objects: [] - links: - - target_room: 112 - entrance: 242 - teleporter: [131, 0] - access: [] - - target_room: 116 - access: ["Bomb"] -- name: Lava Dome Split Corridor - id: 118 - game_objects: - - name: "Lava Dome - Split Corridor Box" - object_id: 0x97 - type: "Box" - access: [] - links: - - target_room: 109 - entrance: 244 - teleporter: [130, 0] - access: [] - - target_room: 100 - entrance: 245 - teleporter: [134, 0] - access: [] -- name: Lava Dome Plate Corridor - id: 119 - game_objects: [] - links: - - target_room: 102 - entrance: 246 - teleporter: [135, 0] - access: [] - - target_room: 116 - entrance: 247 - teleporter: [137, 0] - access: [] -- name: Lava Dome Four Boxes Stairs - id: 120 - game_objects: - - name: "Lava Dome - Caldera Stairway West Left Box" - object_id: 0x9B - type: "Box" - access: [] - - name: "Lava Dome - Caldera Stairway West Right Box" - object_id: 0x9C - type: "Box" - access: [] - - name: "Lava Dome - Caldera Stairway East Left Box" - object_id: 0x9D - type: "Box" - access: [] - - name: "Lava Dome - Caldera Stairway East Right Box" - object_id: 0x9E - type: "Box" - access: [] - links: - - target_room: 107 - entrance: 248 - teleporter: [136, 0] - access: [] - - target_room: 106 - entrance: 249 - teleporter: [16, 0] - access: [] -- name: Lava Dome Hydra Room - id: 121 - game_objects: - - name: "Lava Dome - Dualhead Hydra Chest" - object_id: 0x14 - type: "Chest" - access: ["DualheadHydra"] - - name: "Dualhead Hydra" - object_id: 0 - type: "Trigger" - on_trigger: ["DualheadHydra"] - access: [] - - name: "Lava Dome - Hydra Room Northwest Box" - object_id: 0x9F - type: "Box" - access: [] - - name: "Lava Dome - Hydra Room Southweast Box" - object_id: 0xA0 - type: "Box" - access: [] - links: - - target_room: 105 - entrance: 250 - teleporter: [105, 3] - access: [] - - target_room: 122 - entrance: 251 - teleporter: [138, 0] - access: ["DualheadHydra"] -- name: Lava Dome Escape Corridor - id: 122 - game_objects: [] - links: - - target_room: 121 - entrance: 253 - teleporter: [139, 0] - access: [] -- name: Rope Bridge - id: 123 - game_objects: - - name: "Rope Bridge - West Box" - object_id: 0xA3 - type: "Box" - access: [] - - name: "Rope Bridge - East Box" - object_id: 0xA4 - type: "Box" - access: [] - links: - - target_room: 226 - entrance: 255 - teleporter: [140, 0] - access: [] -- name: Alive Forest - id: 124 - game_objects: - - name: "Alive Forest - Tree Stump Chest" - object_id: 0x15 - type: "Chest" - access: ["Axe"] - - name: "Alive Forest - Near Entrance Box" - object_id: 0xA5 - type: "Box" - access: ["Axe"] - - name: "Alive Forest - After Bridge Box" - object_id: 0xA6 - type: "Box" - access: ["Axe"] - - name: "Alive Forest - Gemini Stump Box" - object_id: 0xA7 - type: "Box" - access: ["Axe"] - links: - - target_room: 226 - entrance: 272 - teleporter: [142, 0] - access: ["Axe"] - - target_room: 21 - entrance: 275 - teleporter: [64, 8] - access: ["LibraCrest", "Axe"] - - target_room: 22 - entrance: 276 - teleporter: [65, 8] - access: ["GeminiCrest", "Axe"] - - target_room: 23 - entrance: 277 - teleporter: [66, 8] - access: ["MobiusCrest", "Axe"] - - target_room: 125 - entrance: 274 - teleporter: [143, 0] - access: ["Axe"] -- name: Giant Tree 1F Main Area - id: 125 - game_objects: - - name: "Giant Tree 1F - Northwest Box" - object_id: 0xA8 - type: "Box" - access: [] - - name: "Giant Tree 1F - Southwest Box" - object_id: 0xA9 - type: "Box" - access: [] - - name: "Giant Tree 1F - Center Box" - object_id: 0xAA - type: "Box" - access: [] - - name: "Giant Tree 1F - East Box" - object_id: 0xAB - type: "Box" - access: [] - links: - - target_room: 124 - entrance: 278 - teleporter: [56, 1] # [49, 8] script restored if no map shuffling - access: [] - - target_room: 202 - access: ["DragonClaw"] -- name: Giant Tree 1F North Island - id: 202 - game_objects: [] - links: - - target_room: 127 - entrance: 280 - teleporter: [144, 0] - access: [] - - target_room: 125 - access: ["DragonClaw"] -- name: Giant Tree 1F Central Island - id: 126 - game_objects: [] - links: - - target_room: 202 - access: ["DragonClaw"] -- name: Giant Tree 2F Main Lobby - id: 127 - game_objects: - - name: "Giant Tree 2F - North Box" - object_id: 0xAC - type: "Box" - access: [] - links: - - target_room: 126 - access: ["DragonClaw"] - - target_room: 125 - entrance: 281 - teleporter: [145, 0] - access: [] - - target_room: 133 - entrance: 283 - teleporter: [149, 0] - access: [] - - target_room: 129 - access: ["DragonClaw"] -- name: Giant Tree 2F West Ledge - id: 128 - game_objects: - - name: "Giant Tree 2F - Dropdown Ledge Box" - object_id: 0xAE - type: "Box" - access: [] - links: - - target_room: 140 - entrance: 284 - teleporter: [147, 0] - access: ["Sword"] - - target_room: 130 - access: ["DragonClaw"] -- name: Giant Tree 2F Lower Area - id: 129 - game_objects: - - name: "Giant Tree 2F - South Box" - object_id: 0xAD - type: "Box" - access: [] - links: - - target_room: 130 - access: ["Claw"] - - target_room: 131 - access: ["Claw"] -- name: Giant Tree 2F Central Island - id: 130 - game_objects: [] - links: - - target_room: 129 - access: ["Claw"] - - target_room: 135 - entrance: 282 - teleporter: [146, 0] - access: ["Sword"] -- name: Giant Tree 2F East Ledge - id: 131 - game_objects: [] - links: - - target_room: 129 - access: ["Claw"] - - target_room: 130 - access: ["DragonClaw"] -- name: Giant Tree 2F Meteor Chest Room - id: 132 - game_objects: - - name: "Giant Tree 2F - Gidrah Chest" - object_id: 0x16 - type: "Chest" - access: [] - links: - - target_room: 133 - entrance: 285 - teleporter: [148, 0] - access: [] -- name: Giant Tree 2F Mushroom Room - id: 133 - game_objects: - - name: "Giant Tree 2F - Mushroom Tunnel West Box" - object_id: 0xAF - type: "Box" - access: ["Axe"] - - name: "Giant Tree 2F - Mushroom Tunnel East Box" - object_id: 0xB0 - type: "Box" - access: ["Axe"] - links: - - target_room: 127 - entrance: 286 - teleporter: [150, 0] - access: ["Axe"] - - target_room: 132 - entrance: 287 - teleporter: [151, 0] - access: ["Axe", "Gidrah"] -- name: Giant Tree 3F Central Island - id: 135 - game_objects: - - name: "Giant Tree 3F - Central Island Box" - object_id: 0xB3 - type: "Box" - access: [] - links: - - target_room: 130 - entrance: 288 - teleporter: [152, 0] - access: [] - - target_room: 136 - access: ["Claw"] - - target_room: 137 - access: ["DragonClaw"] -- name: Giant Tree 3F Central Area - id: 136 - game_objects: - - name: "Giant Tree 3F - Center North Box" - object_id: 0xB1 - type: "Box" - access: [] - - name: "Giant Tree 3F - Center West Box" - object_id: 0xB2 - type: "Box" - access: [] - links: - - target_room: 135 - access: ["Claw"] - - target_room: 127 - access: [] - - target_room: 131 - access: [] -- name: Giant Tree 3F Lower Ledge - id: 137 - game_objects: [] - links: - - target_room: 135 - access: ["DragonClaw"] - - target_room: 142 - entrance: 289 - teleporter: [153, 0] - access: ["Sword"] -- name: Giant Tree 3F West Area - id: 138 - game_objects: - - name: "Giant Tree 3F - West Side Box" - object_id: 0xB4 - type: "Box" - access: [] - links: - - target_room: 128 - access: [] - - target_room: 210 - entrance: 290 - teleporter: [154, 0] - access: [] -- name: Giant Tree 3F Middle Up Island - id: 139 - game_objects: [] - links: - - target_room: 136 - access: ["Claw"] -- name: Giant Tree 3F West Platform - id: 140 - game_objects: [] - links: - - target_room: 139 - access: ["Claw"] - - target_room: 141 - access: ["Claw"] - - target_room: 128 - entrance: 291 - teleporter: [155, 0] - access: [] -- name: Giant Tree 3F North Ledge - id: 141 - game_objects: [] - links: - - target_room: 143 - entrance: 292 - teleporter: [156, 0] - access: ["Sword"] - - target_room: 139 - access: ["Claw"] - - target_room: 136 - access: ["Claw"] -- name: Giant Tree Worm Room Upper Ledge - id: 142 - game_objects: - - name: "Giant Tree 3F - Worm Room North Box" - object_id: 0xB5 - type: "Box" - access: ["Axe"] - - name: "Giant Tree 3F - Worm Room South Box" - object_id: 0xB6 - type: "Box" - access: ["Axe"] - links: - - target_room: 137 - entrance: 293 - teleporter: [157, 0] - access: ["Axe"] - - target_room: 210 - access: ["Axe", "Claw"] -- name: Giant Tree Worm Room Lower Ledge - id: 210 - game_objects: [] - links: - - target_room: 138 - entrance: 294 - teleporter: [158, 0] - access: [] -- name: Giant Tree 4F Lower Floor - id: 143 - game_objects: [] - links: - - target_room: 141 - entrance: 295 - teleporter: [159, 0] - access: [] - - target_room: 148 - entrance: 296 - teleporter: [160, 0] - access: [] - - target_room: 148 - entrance: 297 - teleporter: [161, 0] - access: [] - - target_room: 147 - entrance: 298 - teleporter: [162, 0] - access: ["Sword"] -- name: Giant Tree 4F Middle Floor - id: 144 - game_objects: - - name: "Giant Tree 4F - Highest Platform North Box" - object_id: 0xB7 - type: "Box" - access: [] - - name: "Giant Tree 4F - Highest Platform South Box" - object_id: 0xB8 - type: "Box" - access: [] - links: - - target_room: 149 - entrance: 299 - teleporter: [163, 0] - access: [] - - target_room: 145 - access: ["Claw"] - - target_room: 146 - access: ["DragonClaw"] -- name: Giant Tree 4F Upper Floor - id: 145 - game_objects: [] - links: - - target_room: 150 - entrance: 300 - teleporter: [164, 0] - access: ["Sword"] - - target_room: 144 - access: ["Claw"] -- name: Giant Tree 4F South Ledge - id: 146 - game_objects: - - name: "Giant Tree 4F - Hook Ledge Northeast Box" - object_id: 0xB9 - type: "Box" - access: [] - - name: "Giant Tree 4F - Hook Ledge Southwest Box" - object_id: 0xBA - type: "Box" - access: [] - links: - - target_room: 144 - access: ["DragonClaw"] -- name: Giant Tree 4F Slime Room East Area - id: 147 - game_objects: - - name: "Giant Tree 4F - East Slime Room Box" - object_id: 0xBC - type: "Box" - access: ["Axe"] - links: - - target_room: 143 - entrance: 304 - teleporter: [168, 0] - access: [] -- name: Giant Tree 4F Slime Room West Area - id: 148 - game_objects: [] - links: - - target_room: 143 - entrance: 303 - teleporter: [167, 0] - access: ["Axe"] - - target_room: 143 - entrance: 302 - teleporter: [166, 0] - access: ["Axe"] - - target_room: 149 - access: ["Axe", "Claw"] -- name: Giant Tree 4F Slime Room Platform - id: 149 - game_objects: - - name: "Giant Tree 4F - West Slime Room Box" - object_id: 0xBB - type: "Box" - access: [] - links: - - target_room: 144 - entrance: 301 - teleporter: [165, 0] - access: [] - - target_room: 148 - access: ["Claw"] -- name: Giant Tree 5F Lower Area - id: 150 - game_objects: - - name: "Giant Tree 5F - Northwest Left Box" - object_id: 0xBD - type: "Box" - access: [] - - name: "Giant Tree 5F - Northwest Right Box" - object_id: 0xBE - type: "Box" - access: [] - - name: "Giant Tree 5F - South Left Box" - object_id: 0xBF - type: "Box" - access: [] - - name: "Giant Tree 5F - South Right Box" - object_id: 0xC0 - type: "Box" - access: [] - links: - - target_room: 145 - entrance: 305 - teleporter: [169, 0] - access: [] - - target_room: 151 - access: ["Claw"] - - target_room: 143 - access: [] -- name: Giant Tree 5F Gidrah Platform - id: 151 - game_objects: - - name: "Gidrah" - object_id: 0 - type: "Trigger" - on_trigger: ["Gidrah"] - access: [] - links: - - target_room: 150 - access: ["Claw"] -- name: Kaidge Temple Lower Ledge - id: 152 - game_objects: [] - links: - - target_room: 226 - entrance: 307 - teleporter: [18, 6] - access: [] - - target_room: 153 - access: ["Claw"] -- name: Kaidge Temple Upper Ledge - id: 153 - game_objects: - - name: "Kaidge Temple - Box" - object_id: 0xC1 - type: "Box" - access: [] - links: - - target_room: 185 - entrance: 308 - teleporter: [71, 8] - access: ["MobiusCrest"] - - target_room: 152 - access: ["Claw"] -- name: Windhole Temple - id: 154 - game_objects: - - name: "Windhole Temple - Box" - object_id: 0xC2 - type: "Box" - access: [] - links: - - target_room: 226 - entrance: 309 - teleporter: [173, 0] - access: [] -- name: Mount Gale - id: 155 - game_objects: - - name: "Mount Gale - Dullahan Chest" - object_id: 0x17 - type: "Chest" - access: ["DragonClaw", "Dullahan"] - - name: "Dullahan" - object_id: 0 - type: "Trigger" - on_trigger: ["Dullahan"] - access: ["DragonClaw"] - - name: "Mount Gale - East Box" - object_id: 0xC3 - type: "Box" - access: ["DragonClaw"] - - name: "Mount Gale - West Box" - object_id: 0xC4 - type: "Box" - access: [] - links: - - target_room: 226 - entrance: 310 - teleporter: [174, 0] - access: [] -- name: Windia - id: 156 - game_objects: [] - links: - - target_room: 226 - entrance: 312 - teleporter: [10, 6] - access: [] - - target_room: 157 - entrance: 320 - teleporter: [30, 5] - access: [] - - target_room: 163 - entrance: 321 - teleporter: [97, 8] - access: [] - - target_room: 165 - entrance: 322 - teleporter: [32, 5] - access: [] - - target_room: 159 - entrance: 323 - teleporter: [176, 4] - access: [] - - target_room: 160 - entrance: 324 - teleporter: [177, 4] - access: [] -- name: Otto's House - id: 157 - game_objects: - - name: "Otto" - object_id: 0 - type: "Trigger" - on_trigger: ["RainbowBridge"] - access: ["ThunderRock"] - links: - - target_room: 156 - entrance: 327 - teleporter: [106, 3] - access: [] - - target_room: 158 - entrance: 326 - teleporter: [33, 2] - access: [] -- name: Otto's Attic - id: 158 - game_objects: - - name: "Windia - Otto's Attic Box" - object_id: 0xC5 - type: "Box" - access: [] - links: - - target_room: 157 - entrance: 328 - teleporter: [107, 3] - access: [] -- name: Windia Kid House - id: 159 - game_objects: [] - links: - - target_room: 156 - entrance: 329 - teleporter: [178, 0] - access: [] - - target_room: 161 - entrance: 330 - teleporter: [180, 0] - access: [] -- name: Windia Old People House - id: 160 - game_objects: [] - links: - - target_room: 156 - entrance: 331 - teleporter: [179, 0] - access: [] - - target_room: 162 - entrance: 332 - teleporter: [181, 0] - access: [] -- name: Windia Kid House Basement - id: 161 - game_objects: [] - links: - - target_room: 159 - entrance: 333 - teleporter: [182, 0] - access: [] - - target_room: 79 - entrance: 334 - teleporter: [44, 8] - access: ["MobiusCrest"] -- name: Windia Old People House Basement - id: 162 - game_objects: - - name: "Windia - Mobius Basement West Box" - object_id: 0xC8 - type: "Box" - access: [] - - name: "Windia - Mobius Basement East Box" - object_id: 0xC9 - type: "Box" - access: [] - links: - - target_room: 160 - entrance: 335 - teleporter: [183, 0] - access: [] - - target_room: 186 - entrance: 336 - teleporter: [43, 8] - access: ["MobiusCrest"] -- name: Windia Inn Lobby - id: 163 - game_objects: [] - links: - - target_room: 156 - entrance: 338 - teleporter: [135, 3] - access: [] - - target_room: 164 - entrance: 337 - teleporter: [102, 8] - access: [] -- name: Windia Inn Beds - id: 164 - game_objects: - - name: "Windia - Inn Bedroom North Box" - object_id: 0xC6 - type: "Box" - access: [] - - name: "Windia - Inn Bedroom South Box" - object_id: 0xC7 - type: "Box" - access: [] - - name: "Windia - Kaeli" - object_id: 15 - type: "NPC" - access: ["Kaeli2"] - links: - - target_room: 163 - entrance: 339 - teleporter: [216, 0] - access: [] -- name: Windia Vendor House - id: 165 - game_objects: - - name: "Windia - Vendor" - object_id: 16 - type: "NPC" - access: [] - links: - - target_room: 156 - entrance: 340 - teleporter: [108, 3] - access: [] -- name: Pazuzu Tower 1F Main Lobby - id: 166 - game_objects: - - name: "Pazuzu 1F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu1F"] - access: [] - links: - - target_room: 226 - entrance: 341 - teleporter: [184, 0] - access: [] - - target_room: 180 - entrance: 345 - teleporter: [185, 0] - access: [] -- name: Pazuzu Tower 1F Boxes Room - id: 167 - game_objects: - - name: "Pazuzu's Tower 1F - Descent Bomb Wall West Box" - object_id: 0xCA - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 1F - Descent Bomb Wall Center Box" - object_id: 0xCB - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 1F - Descent Bomb Wall East Box" - object_id: 0xCC - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 1F - Descent Box" - object_id: 0xCD - type: "Box" - access: [] - links: - - target_room: 169 - entrance: 349 - teleporter: [187, 0] - access: [] -- name: Pazuzu Tower 1F Southern Platform - id: 168 - game_objects: [] - links: - - target_room: 169 - entrance: 346 - teleporter: [186, 0] - access: [] - - target_room: 166 - access: ["DragonClaw"] -- name: Pazuzu 2F - id: 169 - game_objects: - - name: "Pazuzu's Tower 2F - East Room West Box" - object_id: 0xCE - type: "Box" - access: [] - - name: "Pazuzu's Tower 2F - East Room East Box" - object_id: 0xCF - type: "Box" - access: [] - - name: "Pazuzu 2F Lock" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu2FLock"] - access: ["Axe"] - - name: "Pazuzu 2F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu2F"] - access: ["Bomb"] - links: - - target_room: 183 - entrance: 350 - teleporter: [188, 0] - access: [] - - target_room: 168 - entrance: 351 - teleporter: [189, 0] - access: [] - - target_room: 167 - entrance: 352 - teleporter: [190, 0] - access: [] - - target_room: 171 - entrance: 353 - teleporter: [191, 0] - access: [] -- name: Pazuzu 3F Main Room - id: 170 - game_objects: - - name: "Pazuzu's Tower 3F - Guest Room West Box" - object_id: 0xD0 - type: "Box" - access: [] - - name: "Pazuzu's Tower 3F - Guest Room East Box" - object_id: 0xD1 - type: "Box" - access: [] - - name: "Pazuzu 3F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu3F"] - access: [] - links: - - target_room: 180 - entrance: 356 - teleporter: [192, 0] - access: [] - - target_room: 181 - entrance: 357 - teleporter: [193, 0] - access: [] -- name: Pazuzu 3F Central Island - id: 171 - game_objects: [] - links: - - target_room: 169 - entrance: 360 - teleporter: [194, 0] - access: [] - - target_room: 170 - access: ["DragonClaw"] - - target_room: 172 - access: ["DragonClaw"] -- name: Pazuzu 3F Southern Island - id: 172 - game_objects: - - name: "Pazuzu's Tower 3F - South Ledge Box" - object_id: 0xD2 - type: "Box" - access: [] - links: - - target_room: 173 - entrance: 361 - teleporter: [195, 0] - access: [] - - target_room: 171 - access: ["DragonClaw"] -- name: Pazuzu 4F - id: 173 - game_objects: - - name: "Pazuzu's Tower 4F - Elevator West Box" - object_id: 0xD3 - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 4F - Elevator East Box" - object_id: 0xD4 - type: "Box" - access: ["Bomb"] - - name: "Pazuzu's Tower 4F - East Storage Room Chest" - object_id: 0x18 - type: "Chest" - access: [] - - name: "Pazuzu 4F Lock" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu4FLock"] - access: ["Axe"] - - name: "Pazuzu 4F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu4F"] - access: ["Bomb"] - links: - - target_room: 183 - entrance: 362 - teleporter: [196, 0] - access: [] - - target_room: 184 - entrance: 363 - teleporter: [197, 0] - access: [] - - target_room: 172 - entrance: 364 - teleporter: [198, 0] - access: [] - - target_room: 175 - entrance: 365 - teleporter: [199, 0] - access: [] -- name: Pazuzu 5F Pazuzu Loop - id: 174 - game_objects: - - name: "Pazuzu 5F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu5F"] - access: [] - links: - - target_room: 181 - entrance: 368 - teleporter: [200, 0] - access: [] - - target_room: 182 - entrance: 369 - teleporter: [201, 0] - access: [] -- name: Pazuzu 5F Upper Loop - id: 175 - game_objects: - - name: "Pazuzu's Tower 5F - North Box" - object_id: 0xD5 - type: "Box" - access: [] - - name: "Pazuzu's Tower 5F - South Box" - object_id: 0xD6 - type: "Box" - access: [] - links: - - target_room: 173 - entrance: 370 - teleporter: [202, 0] - access: [] - - target_room: 176 - entrance: 371 - teleporter: [203, 0] - access: [] -- name: Pazuzu 6F - id: 176 - game_objects: - - name: "Pazuzu's Tower 6F - Box" - object_id: 0xD7 - type: "Box" - access: [] - - name: "Pazuzu's Tower 6F - Chest" - object_id: 0x19 - type: "Chest" - access: [] - - name: "Pazuzu 6F Lock" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu6FLock"] - access: ["Bomb", "Axe"] - - name: "Pazuzu 6F" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu6F"] - access: ["Bomb"] - links: - - target_room: 184 - entrance: 374 - teleporter: [204, 0] - access: [] - - target_room: 175 - entrance: 375 - teleporter: [205, 0] - access: [] - - target_room: 178 - entrance: 376 - teleporter: [206, 0] - access: [] - - target_room: 178 - entrance: 377 - teleporter: [207, 0] - access: [] -- name: Pazuzu 7F Southwest Area - id: 177 - game_objects: [] - links: - - target_room: 182 - entrance: 380 - teleporter: [26, 0] - access: [] - - target_room: 178 - access: ["DragonClaw"] -- name: Pazuzu 7F Rest of the Area - id: 178 - game_objects: [] - links: - - target_room: 177 - access: ["DragonClaw"] - - target_room: 176 - entrance: 381 - teleporter: [27, 0] - access: [] - - target_room: 176 - entrance: 382 - teleporter: [28, 0] - access: [] - - target_room: 179 - access: ["DragonClaw", "Pazuzu2FLock", "Pazuzu4FLock", "Pazuzu6FLock", "Pazuzu1F", "Pazuzu2F", "Pazuzu3F", "Pazuzu4F", "Pazuzu5F", "Pazuzu6F"] -- name: Pazuzu 7F Sky Room - id: 179 - game_objects: - - name: "Pazuzu's Tower 7F - Pazuzu Chest" - object_id: 0x1A - type: "Chest" - access: [] - - name: "Pazuzu" - object_id: 0 - type: "Trigger" - on_trigger: ["Pazuzu"] - access: ["Pazuzu2FLock", "Pazuzu4FLock", "Pazuzu6FLock", "Pazuzu1F", "Pazuzu2F", "Pazuzu3F", "Pazuzu4F", "Pazuzu5F", "Pazuzu6F"] - links: - - target_room: 178 - access: ["DragonClaw"] -- name: Pazuzu 1F to 3F - id: 180 - game_objects: [] - links: - - target_room: 166 - entrance: 385 - teleporter: [29, 0] - access: [] - - target_room: 170 - entrance: 386 - teleporter: [30, 0] - access: [] -- name: Pazuzu 3F to 5F - id: 181 - game_objects: [] - links: - - target_room: 170 - entrance: 387 - teleporter: [40, 0] - access: [] - - target_room: 174 - entrance: 388 - teleporter: [41, 0] - access: [] -- name: Pazuzu 5F to 7F - id: 182 - game_objects: [] - links: - - target_room: 174 - entrance: 389 - teleporter: [38, 0] - access: [] - - target_room: 177 - entrance: 390 - teleporter: [39, 0] - access: [] -- name: Pazuzu 2F to 4F - id: 183 - game_objects: [] - links: - - target_room: 169 - entrance: 391 - teleporter: [21, 0] - access: [] - - target_room: 173 - entrance: 392 - teleporter: [22, 0] - access: [] -- name: Pazuzu 4F to 6F - id: 184 - game_objects: [] - links: - - target_room: 173 - entrance: 393 - teleporter: [2, 0] - access: [] - - target_room: 176 - entrance: 394 - teleporter: [3, 0] - access: [] -- name: Light Temple - id: 185 - game_objects: - - name: "Light Temple - Box" - object_id: 0xD8 - type: "Box" - access: [] - links: - - target_room: 230 - entrance: 395 - teleporter: [19, 6] - access: [] - - target_room: 153 - entrance: 396 - teleporter: [70, 8] - access: ["MobiusCrest"] -- name: Ship Dock - id: 186 - game_objects: - - name: "Ship Dock Access" - object_id: 0 - type: "Trigger" - on_trigger: ["ShipDockAccess"] - access: [] - links: - - target_room: 228 - entrance: 399 - teleporter: [17, 6] - access: [] - - target_room: 162 - entrance: 397 - teleporter: [61, 8] - access: ["MobiusCrest"] -- name: Mac Ship Deck - id: 187 - game_objects: - - name: "Mac Ship Steering Wheel" - object_id: 00 - type: "Trigger" - on_trigger: ["ShipSteeringWheel"] - access: [] - - name: "Mac's Ship Deck - North Box" - object_id: 0xD9 - type: "Box" - access: [] - - name: "Mac's Ship Deck - Center Box" - object_id: 0xDA - type: "Box" - access: [] - - name: "Mac's Ship Deck - South Box" - object_id: 0xDB - type: "Box" - access: [] - links: - - target_room: 229 - entrance: 400 - teleporter: [37, 8] - access: [] - - target_room: 188 - entrance: 401 - teleporter: [50, 8] - access: [] - - target_room: 188 - entrance: 402 - teleporter: [51, 8] - access: [] - - target_room: 188 - entrance: 403 - teleporter: [52, 8] - access: [] - - target_room: 189 - entrance: 404 - teleporter: [53, 8] - access: [] -- name: Mac Ship B1 Outer Ring - id: 188 - game_objects: - - name: "Mac's Ship B1 - Northwest Hook Platform Box" - object_id: 0xE4 - type: "Box" - access: ["DragonClaw"] - - name: "Mac's Ship B1 - Center Hook Platform Box" - object_id: 0xE5 - type: "Box" - access: ["DragonClaw"] - links: - - target_room: 187 - entrance: 405 - teleporter: [208, 0] - access: [] - - target_room: 187 - entrance: 406 - teleporter: [175, 0] - access: [] - - target_room: 187 - entrance: 407 - teleporter: [172, 0] - access: [] - - target_room: 193 - entrance: 408 - teleporter: [88, 0] - access: [] - - target_room: 193 - access: [] -- name: Mac Ship B1 Square Room - id: 189 - game_objects: [] - links: - - target_room: 187 - entrance: 409 - teleporter: [141, 0] - access: [] - - target_room: 192 - entrance: 410 - teleporter: [87, 0] - access: [] -- name: Mac Ship B1 Central Corridor - id: 190 - game_objects: - - name: "Mac's Ship B1 - Central Corridor Box" - object_id: 0xE6 - type: "Box" - access: [] - links: - - target_room: 192 - entrance: 413 - teleporter: [86, 0] - access: [] - - target_room: 191 - entrance: 412 - teleporter: [102, 0] - access: [] - - target_room: 193 - access: [] -- name: Mac Ship B2 South Corridor - id: 191 - game_objects: [] - links: - - target_room: 190 - entrance: 415 - teleporter: [55, 8] - access: [] - - target_room: 194 - entrance: 414 - teleporter: [57, 1] - access: [] -- name: Mac Ship B2 North Corridor - id: 192 - game_objects: [] - links: - - target_room: 190 - entrance: 416 - teleporter: [56, 8] - access: [] - - target_room: 189 - entrance: 417 - teleporter: [57, 8] - access: [] -- name: Mac Ship B2 Outer Ring - id: 193 - game_objects: - - name: "Mac's Ship B2 - Barrel Room South Box" - object_id: 0xDF - type: "Box" - access: [] - - name: "Mac's Ship B2 - Barrel Room North Box" - object_id: 0xE0 - type: "Box" - access: [] - - name: "Mac's Ship B2 - Southwest Room Box" - object_id: 0xE1 - type: "Box" - access: [] - - name: "Mac's Ship B2 - Southeast Room Box" - object_id: 0xE2 - type: "Box" - access: [] - links: - - target_room: 188 - entrance: 418 - teleporter: [58, 8] - access: [] -- name: Mac Ship B1 Mac Room - id: 194 - game_objects: - - name: "Mac's Ship B1 - Mac Room Chest" - object_id: 0x1B - type: "Chest" - access: [] - - name: "Captain Mac" - object_id: 0 - type: "Trigger" - on_trigger: ["ShipLoaned"] - access: ["CaptainCap"] - links: - - target_room: 191 - entrance: 424 - teleporter: [101, 0] - access: [] -- name: Doom Castle Corridor of Destiny - id: 195 - game_objects: [] - links: - - target_room: 201 - entrance: 428 - teleporter: [84, 0] - access: [] - - target_room: 196 - entrance: 429 - teleporter: [35, 2] - access: [] - - target_room: 197 - entrance: 430 - teleporter: [209, 0] - access: ["StoneGolem"] - - target_room: 198 - entrance: 431 - teleporter: [211, 0] - access: ["StoneGolem", "TwinheadWyvern"] - - target_room: 199 - entrance: 432 - teleporter: [13, 2] - access: ["StoneGolem", "TwinheadWyvern", "Zuh"] -- name: Doom Castle Ice Floor - id: 196 - game_objects: - - name: "Doom Castle 4F - Northwest Room Box" - object_id: 0xE7 - type: "Box" - access: ["Sword", "DragonClaw"] - - name: "Doom Castle 4F - Southwest Room Box" - object_id: 0xE8 - type: "Box" - access: ["Sword", "DragonClaw"] - - name: "Doom Castle 4F - Northeast Room Box" - object_id: 0xE9 - type: "Box" - access: ["Sword"] - - name: "Doom Castle 4F - Southeast Room Box" - object_id: 0xEA - type: "Box" - access: ["Sword", "DragonClaw"] - - name: "Stone Golem" - object_id: 0 - type: "Trigger" - on_trigger: ["StoneGolem"] - access: ["Sword", "DragonClaw"] - links: - - target_room: 195 - entrance: 433 - teleporter: [109, 3] - access: [] -- name: Doom Castle Lava Floor - id: 197 - game_objects: - - name: "Doom Castle 5F - North Left Box" - object_id: 0xEB - type: "Box" - access: ["DragonClaw"] - - name: "Doom Castle 5F - North Right Box" - object_id: 0xEC - type: "Box" - access: ["DragonClaw"] - - name: "Doom Castle 5F - South Left Box" - object_id: 0xED - type: "Box" - access: ["DragonClaw"] - - name: "Doom Castle 5F - South Right Box" - object_id: 0xEE - type: "Box" - access: ["DragonClaw"] - - name: "Twinhead Wyvern" - object_id: 0 - type: "Trigger" - on_trigger: ["TwinheadWyvern"] - access: ["DragonClaw"] - links: - - target_room: 195 - entrance: 434 - teleporter: [210, 0] - access: [] -- name: Doom Castle Sky Floor - id: 198 - game_objects: - - name: "Doom Castle 6F - West Box" - object_id: 0xEF - type: "Box" - access: [] - - name: "Doom Castle 6F - East Box" - object_id: 0xF0 - type: "Box" - access: [] - - name: "Zuh" - object_id: 0 - type: "Trigger" - on_trigger: ["Zuh"] - access: ["DragonClaw"] - links: - - target_room: 195 - entrance: 435 - teleporter: [212, 0] - access: [] - - target_room: 197 - access: [] -- name: Doom Castle Hero Room - id: 199 - game_objects: - - name: "Doom Castle Hero Chest 01" - object_id: 0xF2 - type: "Chest" - access: [] - - name: "Doom Castle Hero Chest 02" - object_id: 0xF3 - type: "Chest" - access: [] - - name: "Doom Castle Hero Chest 03" - object_id: 0xF4 - type: "Chest" - access: [] - - name: "Doom Castle Hero Chest 04" - object_id: 0xF5 - type: "Chest" - access: [] - links: - - target_room: 200 - entrance: 436 - teleporter: [54, 0] - access: [] - - target_room: 195 - entrance: 441 - teleporter: [110, 3] - access: [] -- name: Doom Castle Dark King Room - id: 200 - game_objects: [] - links: - - target_room: 199 - entrance: 442 - teleporter: [52, 0] - access: [] diff --git a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md index a652d4e5adc..4e093930739 100644 --- a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md +++ b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md @@ -1,5 +1,8 @@ # Final Fantasy Mystic Quest +## Game page in other languages: +* [Français](/games/Final%20Fantasy%20Mystic%20Quest/info/fr) + ## Where is the options page? The [player options page for this game](../player-options) contains all the options you need to configure and export a diff --git a/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md new file mode 100644 index 00000000000..70c2d938bfc --- /dev/null +++ b/worlds/ffmq/docs/fr_Final Fantasy Mystic Quest.md @@ -0,0 +1,36 @@ +# Final Fantasy Mystic Quest + +## Page d'info dans d'autres langues : +* [English](/games/Final%20Fantasy%20Mystic%20Quest/info/en) + +## Où se situe la page d'options? + +La [page de configuration](../player-options) contient toutes les options nécessaires pour créer un fichier de configuration. + +## Qu'est-ce qui est rendu aléatoire dans ce jeu? + +Outre les objets mélangés, il y a plusieurs options pour aussi mélanger les villes et donjons, les pièces dans les donjons, les téléporteurs et les champs de bataille. +Il y a aussi plusieurs autres options afin d'ajuster la difficulté du jeu et la vitesse d'une partie. + +## Quels objets et emplacements sont mélangés? + +Les objets normalement reçus des coffres rouges, des PNJ et des champs de bataille sont mélangés. Vous pouvez aussi +inclure les objets des coffres bruns (qui contiennent normalement des consommables) dans les objets mélangés. + +## Quels objets peuvent être dans les mondes des autres joueurs? + +Tous les objets qui ont été déterminés mélangés dans les options peuvent être placés dans d'autres mondes. + +## À quoi ressemblent les objets des autres joueurs dans Final Fantasy Mystic Quest? + +Les emplacements qui étaient à l'origine des coffres (rouges ou bruns si ceux-ci sont inclus) apparaîtront comme des coffres. +Les coffres rouges seront des objets utiles ou de progression, alors que les coffres bruns seront des objets de remplissage. +Les pièges peuvent apparaître comme des coffres rouges ou bruns. +Lorsque vous ouvrirez un coffre contenant un objet d'un autre joueur, vous recevrez l'icône d'Archipelago et +la boîte de dialogue vous indiquera avoir reçu un "Archipelago Item". + + +## Lorsqu'un joueur reçoit un objet, qu'arrive-t-il? + +Une boîte de dialogue apparaîtra pour vous montrer l'objet que vous avez reçu. Vous ne pourrez pas recevoir d'objet si vous êtes +en combat, dans la mappemonde ou dans les menus (à l'exception de lorsque vous fermez le menu). diff --git a/worlds/ffmq/docs/setup_en.md b/worlds/ffmq/docs/setup_en.md index 35d775f1bc9..77569c93f0c 100644 --- a/worlds/ffmq/docs/setup_en.md +++ b/worlds/ffmq/docs/setup_en.md @@ -17,6 +17,12 @@ The Archipelago community cannot supply you with this. ## Installation Procedures +### Linux Setup + +1. Download and install [Archipelago](). **The installer + file is located in the assets section at the bottom of the version information. You'll likely be looking for the `.AppImage`.** +2. It is recommended to use either RetroArch or BizHawk if you run on linux, as snes9x-rr isn't compatible. + ### Windows Setup 1. Download and install [Archipelago](). **The installer @@ -75,8 +81,7 @@ Manually launch the SNI Client, and run the patched ROM in your chosen software #### With an emulator -When the client launched automatically, SNI should have also automatically launched in the background. If this is its -first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +If this is the first time SNI launches, you may be prompted to allow it to communicate through the Windows Firewall. ##### snes9x-rr @@ -133,10 +138,10 @@ page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platfor ### Connect to the Archipelago Server -The patch file which launched your client should have automatically connected you to the AP Server. There are a few -reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the -client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it -into the "Server" input field then press enter. +SNI serves as the interface between your emulator and the server. Since you launched it manually, you need to tell it what server to connect to. +If the server is hosted on Archipelago.gg, get the port the server hosts your game on at the top of the game room (last line before the worlds are listed). +In the SNI client, either type `/connect address` (where `address` is the address of the server, for example `/connect archipelago.gg:12345`), or type the address and port on the "Server" input field, then press `Connect`. +If the server is hosted locally, simply ask the host for the address of the server, and copy/paste it into the "Server" input field then press `Connect`. The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". diff --git a/worlds/ffmq/docs/setup_fr.md b/worlds/ffmq/docs/setup_fr.md new file mode 100644 index 00000000000..12ea41c6b3a --- /dev/null +++ b/worlds/ffmq/docs/setup_fr.md @@ -0,0 +1,178 @@ +# Final Fantasy Mystic Quest Setup Guide + +## Logiciels requis + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES + - Un émulateur capable d'éxécuter des scripts Lua + - snes9x-rr de: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html), + - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Ou, + - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle + compatible +- Le fichier ROM de la v1.0 ou v1.1 NA de Final Fantasy Mystic Quest obtenu légalement, sûrement nommé `Final Fantasy - Mystic Quest (U) (V1.0).sfc` ou `Final Fantasy - Mystic Quest (U) (V1.1).sfc` +La communauté d'Archipelago ne peut vous fournir avec ce fichier. + +## Procédure d'installation + +### Installation sur Linux + +1. Téléchargez et installez [Archipelago](). +** Le fichier d'installation est situé dans la section "assets" dans le bas de la fenêtre d'information de la version. Vous voulez probablement le `.AppImage`** +2. L'utilisation de RetroArch ou BizHawk est recommandé pour les utilisateurs linux, puisque snes9x-rr n'est pas compatible. + +### Installation sur Windows + +1. Téléchargez et installez [Archipelago](). +** Le fichier d'installation est situé dans la section "assets" dans le bas de la fenêtre d'information de la version.** +2. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme + programme par défaut pour ouvrir vos ROMs. + 1. Extrayez votre dossier d'émulateur sur votre Bureau, ou à un endroit dont vous vous souviendrez. + 2. Faites un clic droit sur un fichier ROM et sélectionnez **Ouvrir avec...** + 3. Cochez la case à côté de **Toujours utiliser cette application pour ouvrir les fichiers `.sfc`** + 4. Descendez jusqu'en bas de la liste et sélectionnez **Rechercher une autre application sur ce PC** + 5. Naviguez dans les dossiers jusqu'au fichier `.exe` de votre émulateur et choisissez **Ouvrir**. Ce fichier + devrait se trouver dans le dossier que vous avez extrait à la première étape. + + +## Créer son fichier de configuration (.yaml) + +### Qu'est-ce qu'un fichier de configuration et pourquoi en ai-je besoin ? + +Votre fichier de configuration contient un ensemble d'options de configuration pour indiquer au générateur +comment il devrait générer votre seed. Chaque joueur d'un multiworld devra fournir son propre fichier de configuration. Cela permet +à chaque joueur d'apprécier une expérience personalisée. Les différents joueurs d'un même multiworld +pouront avoir des options de génération différentes. +Vous pouvez lire le [guide pour créer un YAML de base](/tutorial/Archipelago/setup/en) en anglais. + +### Où est-ce que j'obtiens un fichier de configuration ? + +La [page d'options sur le site](/games/Final%20Fantasy%20Mystic%20Quest/player-options) vous permet de choisir vos +options de génération et de les exporter vers un fichier de configuration. +Il vous est aussi possible de trouver le fichier de configuration modèle de Mystic Quest dans votre répertoire d'installation d'Archipelago, +dans le dossier Players/Templates. + +### Vérifier son fichier de configuration + +Si vous voulez valider votre fichier de configuration pour être sûr qu'il fonctionne, vous pouvez le vérifier sur la page du +[Validateur de YAML](/mysterycheck). + +## Générer une partie pour un joueur + +1. Aller sur la page [Génération de partie](/games/Final%20Fantasy%20Mystic%20Quest/player-options), configurez vos options, + et cliquez sur le bouton "Generate Game". +2. Il vous sera alors présenté une page d'informations sur la seed +3. Cliquez sur le lien "Create New Room". +4. Vous verrez s'afficher la page du server, de laquelle vous pourrez télécharger votre fichier patch `.apmq`. +5. Rendez-vous sur le [site FFMQR](https://ffmqrando.net/Archipelago). +Sur cette page, sélectionnez votre ROM Final Fantasy Mystic Quest original dans le boîte "ROM", puis votre ficher patch `.apmq` dans la boîte "Load Archipelago Config File". +Cliquez sur "Generate". Un téléchargement avec votre ROM aléatoire devrait s'amorcer. +6. Puisque cette partie est à un seul joueur, vous n'avez plus besoin du client Archipelago ni du serveur, sentez-vous libre de les fermer. + +## Rejoindre un MultiWorld + +### Obtenir son patch et créer sa ROM + +Quand vous rejoignez un multiworld, il vous sera demandé de fournir votre fichier de configuration à celui qui héberge la partie ou +s'occupe de la génération. Une fois cela fait, l'hôte vous fournira soit un lien pour télécharger votre patch, soit un +fichier `.zip` contenant les patchs de tous les joueurs. Votre patch devrait avoir l'extension `.apmq`. + +Allez au [site FFMQR](https://ffmqrando.net/Archipelago) et sélectionnez votre ROM Final Fantasy Mystic Quest original dans le boîte "ROM", puis votre ficher patch `.apmq` dans la boîte "Load Archipelago Config File". +Cliquez sur "Generate". Un téléchargement avec votre ROM aléatoire devrait s'amorcer. + +Ouvrez le client SNI (sur Windows ArchipelagoSNIClient.exe, sur Linux ouvrez le `.appImage` puis cliquez sur SNI Client), puis ouvrez le ROM téléchargé avec votre émulateur choisi. + +### Se connecter au client + +#### Avec un émulateur + +Quand le client se lance automatiquement, QUsb2Snes devrait également se lancer automatiquement en arrière-plan. Si +c'est la première fois qu'il démarre, il vous sera peut-être demandé de l'autoriser à communiquer à travers le pare-feu +Windows. + +##### snes9x-rr + +1. Chargez votre ROM si ce n'est pas déjà fait. +2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting** +3. Cliquez alors sur **New Lua Script Window...** +4. Dans la nouvelle fenêtre, sélectionnez **Browse...** +5. Sélectionnez le fichier connecteur lua fourni avec votre client + - Regardez dans le dossier Archipelago et cherchez `/SNI/lua/x64` ou `/SNI/lua/x86`, dépendemment de si votre emulateur + est 64-bit ou 32-bit. +6. Si vous obtenez une erreur `socket.dll missing` ou une erreur similaire lorsque vous chargez le script lua, vous devez naviguer dans le dossier +contenant le script lua, puis copier le fichier `socket.dll` dans le dossier d'installation de votre emulateur snes9x. + +##### BizHawk + +1. Assurez vous d'avoir le coeur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant + ces options de menu : + `Config --> Cores --> SNES --> BSNES` + Une fois le coeur changé, vous devez redémarrer BizHawk. +2. Chargez votre ROM si ce n'est pas déjà fait. +3. Cliquez sur le menu "Tools" et cliquez sur **Lua Console** +4. Cliquez sur le bouton pour ouvrir un nouveau script Lua, soit par le bouton avec un icône "Ouvrir un dossier", + en cliquant `Open Script...` dans le menu Script ou en appuyant sur `ctrl-O`. +5. Sélectionnez le fichier `Connector.lua` inclus avec le client + - Regardez dans le dossier Archipelago et cherchez `/SNI/lua/x64` ou `/SNI/lua/x86`, dépendemment de si votre emulateur + est 64-bit ou 32-bit. Notez que les versions les plus récentes de BizHawk ne sont que 64-bit. + +##### RetroArch 1.10.1 ou plus récent + +Vous ne devez faire ces étapes qu'une fois. À noter que RetroArch 1.9.x ne fonctionnera pas puisqu'il s'agit d'une version moins récente que 1.10.1. + +1. Entrez dans le menu principal de RetroArch. +2. Allez dans Settings --> User Interface. Activez l'option "Show Advanced Settings". +3. Allez dans Settings --> Network. Activez l'option "Network Commands", qui se trouve sous "Request Device 16". + Laissez le "Network Command Port" à sa valeur par defaut, qui devrait être 55355. + + +![Capture d'écran du menu Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Allez dans le Menu Principal --> Online Updater --> Core Downloader. Trouvez et sélectionnez "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +Lorsque vous chargez un ROM pour Archipelago, assurez vous de toujours sélectionner le coeur **bsnes-mercury**. +Ce sont les seuls coeurs qui permettent à des outils extérieurs de lire les données du ROM. + +#### Avec une solution matérielle + +Ce guide suppose que vous avez téléchargé le bon micro-logiciel pour votre appareil. Si ce n'est pas déjà le cas, faites +le maintenant. Les utilisateurs de SD2SNES et de FXPak Pro peuvent télécharger le micro-logiciel approprié +[ici](https://github.com/RedGuyyyy/sd2snes/releases). Pour les autres solutions, de l'aide peut être trouvée +[sur cette page](http://usb2snes.com/#supported-platforms). + +1. Fermez votre émulateur, qui s'est potentiellement lancé automatiquement. +2. Ouvrez votre appareil et chargez le ROM. + +### Se connecter au MultiServer + +Puisque vous avez lancé SNI manuellement, vous devrez probablement lui indiquer l'adresse à laquelle il doit se connecter. +Si le serveur est hébergé sur le site d'Archipelago, vous verrez l'adresse à laquelle vous connecter dans le haut de la page, dernière ligne avant la liste des mondes. +Tapez `/connect adresse` (ou le "adresse" est remplacé par l'adresse archipelago, par exemple `/connect archipelago.gg:12345`) dans la boîte de commande au bas de votre client SNI, ou encore écrivez l'adresse dans la boîte "server" dans le haut du client, puis cliquez `Connect`. +Si le serveur n'est pas hébergé sur le site d'Archipelago, demandez à l'hôte l'adresse du serveur, puis tapez `/connect adresse` (ou "adresse" est remplacé par l'adresse fourni par l'hôte) ou copiez/collez cette adresse dans le champ "Server" puis appuyez sur "Connect". + +Le client essaiera de vous reconnecter à la nouvelle adresse du serveur, et devrait mentionner "Server Status: +Connected". Si le client ne se connecte pas après quelques instants, il faudra peut-être rafraîchir la page de +l'interface Web. + +### Jouer au jeu + +Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations +pour avoir rejoint un multiworld ! + +## Héberger un MultiWorld + +La méthode recommandée pour héberger une partie est d'utiliser le service d'hébergement fourni par +Archipelago. Le processus est relativement simple : + +1. Récupérez les fichiers de configuration (.yaml) des joueurs. +2. Créez une archive zip contenant ces fichiers de configuration. +3. Téléversez l'archive zip sur le lien ci-dessous. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Attendez un moment que la seed soit générée. +5. Lorsque la seed est générée, vous serez redirigé vers une page d'informations "Seed Info". +6. Cliquez sur "Create New Room". Cela vous amènera à la page du serveur. Fournissez le lien de cette page aux autres + joueurs afin qu'ils puissent récupérer leurs patchs. +7. Remarquez qu'un lien vers le traqueur du MultiWorld est en haut de la page de la salle. Vous devriez également + fournir ce lien aux joueurs pour qu'ils puissent suivre la progression de la partie. N'importe quelle personne voulant + observer devrait avoir accès à ce lien. +8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer. diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index 37467eeb468..2197c0708e9 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -102,10 +102,10 @@ See the plando guide for more info on plando options. Plando guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en) * `accessibility` determines the level of access to the game the generation will expect you to have in order to reach - your completion goal. This supports `items`, `locations`, and `minimal` and is set to `locations` by default. - * `locations` will guarantee all locations are accessible in your world. + your completion goal. This supports `full`, `items`, and `minimal` and is set to `full` by default. + * `full` will guarantee all locations are accessible in your world. * `items` will guarantee you can acquire all logically relevant items in your world. Some items, such as keys, may - be self-locking. + be self-locking. This value only exists in and affects some worlds. * `minimal` will only guarantee that the seed is beatable. You will be guaranteed able to finish the seed logically but may not be able to access all locations or acquire all items. A good example of this is having a big key in the big chest in a dungeon in ALTTP making it impossible to get and finish the dungeon. diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index 38be2cd794a..c1206d41ee2 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -1,10 +1,12 @@ import typing import re +from dataclasses import dataclass, make_dataclass + from .ExtractedData import logic_options, starts, pool_options from .Rules import cost_terms from schema import And, Schema, Optional -from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink +from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink, PerGameCommonOptions from .Charms import vanilla_costs, names as charm_names if typing.TYPE_CHECKING: @@ -403,9 +405,20 @@ class Goal(Choice): option_radiance = 3 option_godhome = 4 option_godhome_flower = 5 + option_grub_hunt = 6 default = 0 +class GrubHuntGoal(NamedRange): + """The amount of grubs required to finish Grub Hunt. + On 'All' any grubs from item links replacements etc. will be counted""" + display_name = "Grub Hunt Goal" + range_start = 1 + range_end = 46 + special_range_names = {"all": -1} + default = 46 + + class WhitePalace(Choice): """ Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be @@ -520,7 +533,7 @@ class CostSanityHybridChance(Range): **{ option.__name__: option for option in ( - StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo, + StartLocation, Goal, GrubHuntGoal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo, DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms, MinimumGeoPrice, MaximumGeoPrice, MinimumGrubPrice, MaximumGrubPrice, @@ -538,3 +551,5 @@ class CostSanityHybridChance(Range): }, **cost_sanity_weights } + +HKOptions = make_dataclass("HKOptions", [(name, option) for name, option in hollow_knight_options.items()], bases=(PerGameCommonOptions,)) diff --git a/worlds/hk/Rules.py b/worlds/hk/Rules.py index a3c7e13cf02..e162e1dfa81 100644 --- a/worlds/hk/Rules.py +++ b/worlds/hk/Rules.py @@ -49,3 +49,42 @@ def set_rules(hk_world: World): if term == "GEO": # No geo logic! continue add_rule(location, lambda state, term=term, amount=amount: state.count(term, player) >= amount) + + +def _hk_nail_combat(state, player) -> bool: + return state.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player) + + +def _hk_can_beat_thk(state, player) -> bool: + return ( + state.has('Opened_Black_Egg_Temple', player) + and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1 + and _hk_nail_combat(state, player) + and ( + state.has_any({'LEFTDASH', 'RIGHTDASH'}, player) + or state._hk_option(player, 'ProficientCombat') + ) + and state.has('FOCUS', player) + ) + + +def _hk_siblings_ending(state, player) -> bool: + return _hk_can_beat_thk(state, player) and state.has('WHITEFRAGMENT', player, 3) + + +def _hk_can_beat_radiance(state, player) -> bool: + return ( + state.has('Opened_Black_Egg_Temple', player) + and _hk_nail_combat(state, player) + and state.has('WHITEFRAGMENT', player, 3) + and state.has('DREAMNAIL', player) + and ( + (state.has('LEFTCLAW', player) and state.has('RIGHTCLAW', player)) + or state.has('WINGS', player) + ) + and (state.count('FIREBALL', player) + state.count('SCREAM', player) + state.count('QUAKE', player)) > 1 + and ( + (state.has('LEFTDASH', player, 2) and state.has('RIGHTDASH', player, 2)) # Both Shade Cloaks + or (state._hk_option(player, 'ProficientCombat') and state.has('QUAKE', player)) # or Dive + ) + ) diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 78287305df5..cbb90960612 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -5,19 +5,20 @@ from copy import deepcopy import itertools import operator +from collections import defaultdict, Counter logger = logging.getLogger("Hollow Knight") from .Items import item_table, lookup_type_to_names, item_name_groups from .Regions import create_regions -from .Rules import set_rules, cost_terms +from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \ - shop_to_option + shop_to_option, HKOptions, GrubHuntGoal from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \ event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs from .Charms import names as charm_names -from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification +from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState from worlds.AutoWorld import World, LogicMixin, WebWorld path_of_pain_locations = { @@ -142,7 +143,8 @@ class HKWorld(World): As the enigmatic Knight, you’ll traverse the depths, unravel its mysteries and conquer its evils. """ # from https://www.hollowknight.com game: str = "Hollow Knight" - option_definitions = hollow_knight_options + options_dataclass = HKOptions + options: HKOptions web = HKWeb() @@ -154,40 +156,42 @@ class HKWorld(World): ranges: typing.Dict[str, typing.Tuple[int, int]] charm_costs: typing.List[int] cached_filler_items = {} + grub_count: int - def __init__(self, world, player): - super(HKWorld, self).__init__(world, player) + def __init__(self, multiworld, player): + super(HKWorld, self).__init__(multiworld, player) self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = { location: list() for location in multi_locations } self.ranges = {} self.created_shop_items = 0 self.vanilla_shop_costs = deepcopy(vanilla_shop_costs) + self.grub_count = 0 def generate_early(self): - world = self.multiworld - charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random) - self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs) - # world.exclude_locations[self.player].value.update(white_palace_locations) + options = self.options + charm_costs = options.RandomCharmCosts.get_costs(self.random) + self.charm_costs = options.PlandoCharmCosts.get_costs(charm_costs) + # options.exclude_locations.value.update(white_palace_locations) for term, data in cost_terms.items(): - mini = getattr(world, f"Minimum{data.option}Price")[self.player] - maxi = getattr(world, f"Maximum{data.option}Price")[self.player] + mini = getattr(options, f"Minimum{data.option}Price") + maxi = getattr(options, f"Maximum{data.option}Price") # if minimum > maximum, set minimum to maximum mini.value = min(mini.value, maxi.value) self.ranges[term] = mini.value, maxi.value - world.push_precollected(HKItem(starts[world.StartLocation[self.player].current_key], + self.multiworld.push_precollected(HKItem(starts[options.StartLocation.current_key], True, None, "Event", self.player)) def white_palace_exclusions(self): exclusions = set() - wp = self.multiworld.WhitePalace[self.player] + wp = self.options.WhitePalace if wp <= WhitePalace.option_nopathofpain: exclusions.update(path_of_pain_locations) if wp <= WhitePalace.option_kingfragment: exclusions.update(white_palace_checks) if wp == WhitePalace.option_exclude: exclusions.add("King_Fragment") - if self.multiworld.RandomizeCharms[self.player]: + if self.options.RandomizeCharms: # If charms are randomized, this will be junk-filled -- so transitions and events are not progression exclusions.update(white_palace_transitions) exclusions.update(white_palace_events) @@ -200,7 +204,7 @@ def create_regions(self): # check for any goal that godhome events are relevant to all_event_names = event_names.copy() - if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]: + if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower, Goal.option_any]: from .GodhomeData import godhome_event_names all_event_names.update(set(godhome_event_names)) @@ -230,12 +234,12 @@ def create_items(self): pool: typing.List[HKItem] = [] wp_exclusions = self.white_palace_exclusions() junk_replace: typing.Set[str] = set() - if self.multiworld.RemoveSpellUpgrades[self.player]: + if self.options.RemoveSpellUpgrades: junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark")) randomized_starting_items = set() for attr, items in randomizable_starting_items.items(): - if getattr(self.multiworld, attr)[self.player]: + if getattr(self.options, attr): randomized_starting_items.update(items) # noinspection PyShadowingNames @@ -257,7 +261,7 @@ def _add(item_name: str, location_name: str, randomized: bool): if item_name in junk_replace: item_name = self.get_filler_item_name() - item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.multiworld.AddUnshuffledLocations[self.player] else self.create_event(item_name) + item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.options.AddUnshuffledLocations else self.create_event(item_name) if location_name == "Start": if item_name in randomized_starting_items: @@ -281,55 +285,55 @@ def _add(item_name: str, location_name: str, randomized: bool): location.progress_type = LocationProgressType.EXCLUDED for option_key, option in hollow_knight_randomize_options.items(): - randomized = getattr(self.multiworld, option_key)[self.player] - if all([not randomized, option_key in logicless_options, not self.multiworld.AddUnshuffledLocations[self.player]]): + randomized = getattr(self.options, option_key) + if all([not randomized, option_key in logicless_options, not self.options.AddUnshuffledLocations]): continue for item_name, location_name in zip(option.items, option.locations): if item_name in junk_replace: item_name = self.get_filler_item_name() - if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \ - (item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]): + if (item_name == "Crystal_Heart" and self.options.SplitCrystalHeart) or \ + (item_name == "Mothwing_Cloak" and self.options.SplitMothwingCloak): _add("Left_" + item_name, location_name, randomized) _add("Right_" + item_name, "Split_" + location_name, randomized) continue - if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]: + if item_name == "Mantis_Claw" and self.options.SplitMantisClaw: _add("Left_" + item_name, "Left_" + location_name, randomized) _add("Right_" + item_name, "Right_" + location_name, randomized) continue - if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]: - if self.multiworld.random.randint(0, 1): + if item_name == "Shade_Cloak" and self.options.SplitMothwingCloak: + if self.random.randint(0, 1): item_name = "Left_Mothwing_Cloak" else: item_name = "Right_Mothwing_Cloak" - if item_name == "Grimmchild2" and self.multiworld.RandomizeGrimmkinFlames[self.player] and self.multiworld.RandomizeCharms[self.player]: + if item_name == "Grimmchild2" and self.options.RandomizeGrimmkinFlames and self.options.RandomizeCharms: _add("Grimmchild1", location_name, randomized) continue _add(item_name, location_name, randomized) - if self.multiworld.RandomizeElevatorPass[self.player]: + if self.options.RandomizeElevatorPass: randomized = True _add("Elevator_Pass", "Elevator_Pass", randomized) for shop, locations in self.created_multi_locations.items(): - for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value): + for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value): loc = self.create_location(shop) unfilled_locations += 1 # Balance the pool item_count = len(pool) - additional_shop_items = max(item_count - unfilled_locations, self.multiworld.ExtraShopSlots[self.player].value) + additional_shop_items = max(item_count - unfilled_locations, self.options.ExtraShopSlots.value) # Add additional shop items, as needed. if additional_shop_items > 0: shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16) - if not self.multiworld.EggShopSlots[self.player].value: # No eggshop, so don't place items there + if not self.options.EggShopSlots: # No eggshop, so don't place items there shops.remove('Egg_Shop') if shops: for _ in range(additional_shop_items): - shop = self.multiworld.random.choice(shops) + shop = self.random.choice(shops) loc = self.create_location(shop) unfilled_locations += 1 if len(self.created_multi_locations[shop]) >= 16: @@ -355,7 +359,7 @@ def sort_shops_by_cost(self): loc.costs = costs def apply_costsanity(self): - setting = self.multiworld.CostSanity[self.player].value + setting = self.options.CostSanity.value if not setting: return # noop @@ -369,10 +373,10 @@ def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: return {k: v for k, v in weights.items() if v} - random = self.multiworld.random - hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value + random = self.random + hybrid_chance = getattr(self.options, f"CostSanityHybridChance").value weights = { - data.term: getattr(self.multiworld, f"CostSanity{data.option}Weight")[self.player].value + data.term: getattr(self.options, f"CostSanity{data.option}Weight").value for data in cost_terms.values() } weights_geoless = dict(weights) @@ -427,31 +431,86 @@ def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: location.sort_costs() def set_rules(self): - world = self.multiworld + multiworld = self.multiworld player = self.player - goal = world.Goal[player] + goal = self.options.Goal if goal == Goal.option_hollowknight: - world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) + multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) elif goal == Goal.option_siblings: - world.completion_condition[player] = lambda state: state._hk_siblings_ending(player) + multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) elif goal == Goal.option_radiance: - world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player) + multiworld.completion_condition[player] = lambda state: _hk_can_beat_radiance(state, player) elif goal == Goal.option_godhome: - world.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) + multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) elif goal == Goal.option_godhome_flower: - world.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) + multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) + elif goal == Goal.option_grub_hunt: + pass # will set in stage_pre_fill() else: # Any goal - world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player) + multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) and \ + _hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player) set_rules(self) + @classmethod + def stage_pre_fill(cls, multiworld: "MultiWorld"): + def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]): + world = multiworld.worlds[player] + + if world.options.Goal == "grub_hunt": + multiworld.completion_condition[player] = grub_rule + else: + old_rule = multiworld.completion_condition[player] + multiworld.completion_condition[player] = lambda state: old_rule(state) and grub_rule(state) + + worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]] + if worlds: + grubs = [item for item in multiworld.get_items() if item.name == "Grub"] + all_grub_players = [world.player for world in worlds if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]] + + if all_grub_players: + group_lookup = defaultdict(set) + for group_id, group in multiworld.groups.items(): + for player in group["players"]: + group_lookup[group_id].add(player) + + grub_count_per_player = Counter() + per_player_grubs_per_player = defaultdict(Counter) + + for grub in grubs: + player = grub.player + if player in group_lookup: + for real_player in group_lookup[player]: + per_player_grubs_per_player[real_player][player] += 1 + else: + per_player_grubs_per_player[player][player] += 1 + + if grub.location and grub.location.player in group_lookup.keys(): + for real_player in group_lookup[grub.location.player]: + grub_count_per_player[real_player] += 1 + else: + grub_count_per_player[player] += 1 + + for player, count in grub_count_per_player.items(): + multiworld.worlds[player].grub_count = count + + for player, grub_player_count in per_player_grubs_per_player.items(): + if player in all_grub_players: + set_goal(player, lambda state, g=grub_player_count: all(state.has("Grub", owner, count) for owner, count in g.items())) + + for world in worlds: + if world.player not in all_grub_players: + world.grub_count = world.options.GrubHuntGoal.value + player = world.player + set_goal(player, lambda state, p=player, c=world.grub_count: state.has("Grub", p, c)) + def fill_slot_data(self): slot_data = {} options = slot_data["options"] = {} - for option_name in self.option_definitions: - option = getattr(self.multiworld, option_name)[self.player] + for option_name in hollow_knight_options: + option = getattr(self.options, option_name) try: optionvalue = int(option.value) except TypeError: @@ -460,10 +519,10 @@ def fill_slot_data(self): options[option_name] = optionvalue # 32 bit int - slot_data["seed"] = self.multiworld.per_slot_randoms[self.player].randint(-2147483647, 2147483646) + slot_data["seed"] = self.random.randint(-2147483647, 2147483646) # Backwards compatibility for shop cost data (HKAP < 0.1.0) - if not self.multiworld.CostSanity[self.player]: + if not self.options.CostSanity: for shop, terms in shop_cost_types.items(): unit = cost_terms[next(iter(terms))].option if unit == "Geo": @@ -483,6 +542,8 @@ def fill_slot_data(self): slot_data["notch_costs"] = self.charm_costs + slot_data["grub_count"] = self.grub_count + return slot_data def create_item(self, name: str) -> HKItem: @@ -498,7 +559,7 @@ def create_location(self, name: str, vanilla=False) -> HKLocation: basename = name if name in shop_cost_types: costs = { - term: self.multiworld.random.randint(*self.ranges[term]) + term: self.random.randint(*self.ranges[term]) for term in shop_cost_types[name] } elif name in vanilla_location_costs: @@ -512,7 +573,7 @@ def create_location(self, name: str, vanilla=False) -> HKLocation: region = self.multiworld.get_region("Menu", self.player) - if vanilla and not self.multiworld.AddUnshuffledLocations[self.player]: + if vanilla and not self.options.AddUnshuffledLocations: loc = HKLocation(self.player, name, None, region, costs=costs, vanilla=vanilla, basename=basename) @@ -554,31 +615,32 @@ def remove(self, state, item: HKItem) -> bool: for effect_name, effect_value in item_effects.get(item.name, {}).items(): if state.prog_items[item.player][effect_name] == effect_value: del state.prog_items[item.player][effect_name] - state.prog_items[item.player][effect_name] -= effect_value + else: + state.prog_items[item.player][effect_name] -= effect_value return change @classmethod - def stage_write_spoiler(cls, world: MultiWorld, spoiler_handle): - hk_players = world.get_game_players(cls.game) + def stage_write_spoiler(cls, multiworld: MultiWorld, spoiler_handle): + hk_players = multiworld.get_game_players(cls.game) spoiler_handle.write('\n\nCharm Notches:') for player in hk_players: - name = world.get_player_name(player) + name = multiworld.get_player_name(player) spoiler_handle.write(f'\n{name}\n') - hk_world: HKWorld = world.worlds[player] + hk_world: HKWorld = multiworld.worlds[player] for charm_number, cost in enumerate(hk_world.charm_costs): spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}") spoiler_handle.write('\n\nShop Prices:') for player in hk_players: - name = world.get_player_name(player) + name = multiworld.get_player_name(player) spoiler_handle.write(f'\n{name}\n') - hk_world: HKWorld = world.worlds[player] + hk_world: HKWorld = multiworld.worlds[player] - if world.CostSanity[player].value: + if hk_world.options.CostSanity: for loc in sorted( ( - loc for loc in itertools.chain(*(region.locations for region in world.get_regions(player))) + loc for loc in itertools.chain(*(region.locations for region in multiworld.get_regions(player))) if loc.costs ), key=operator.attrgetter('name') ): @@ -602,15 +664,15 @@ def get_filler_item_name(self) -> str: 'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests', 'RandomizeRancidEggs' ): - if getattr(self.multiworld, group): + if getattr(self.options, group): fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in exclusions) self.cached_filler_items[self.player] = fillers - return self.multiworld.random.choice(self.cached_filler_items[self.player]) + return self.random.choice(self.cached_filler_items[self.player]) -def create_region(world: MultiWorld, player: int, name: str, location_names=None) -> Region: - ret = Region(name, player, world) +def create_region(multiworld: MultiWorld, player: int, name: str, location_names=None) -> Region: + ret = Region(name, player, multiworld) if location_names: for location in location_names: loc_id = HKWorld.location_name_to_id.get(location, None) @@ -683,42 +745,7 @@ def _hk_notches(self, player: int, *notches: int) -> int: return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches) def _hk_option(self, player: int, option_name: str) -> int: - return getattr(self.multiworld, option_name)[player].value + return getattr(self.multiworld.worlds[player].options, option_name).value def _hk_start(self, player, start_location: str) -> bool: - return self.multiworld.StartLocation[player] == start_location - - def _hk_nail_combat(self, player: int) -> bool: - return self.has_any({'LEFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player) - - def _hk_can_beat_thk(self, player: int) -> bool: - return ( - self.has('Opened_Black_Egg_Temple', player) - and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1 - and self._hk_nail_combat(player) - and ( - self.has_any({'LEFTDASH', 'RIGHTDASH'}, player) - or self._hk_option(player, 'ProficientCombat') - ) - and self.has('FOCUS', player) - ) - - def _hk_siblings_ending(self, player: int) -> bool: - return self._hk_can_beat_thk(player) and self.has('WHITEFRAGMENT', player, 3) - - def _hk_can_beat_radiance(self, player: int) -> bool: - return ( - self.has('Opened_Black_Egg_Temple', player) - and self._hk_nail_combat(player) - and self.has('WHITEFRAGMENT', player, 3) - and self.has('DREAMNAIL', player) - and ( - (self.has('LEFTCLAW', player) and self.has('RIGHTCLAW', player)) - or self.has('WINGS', player) - ) - and (self.count('FIREBALL', player) + self.count('SCREAM', player) + self.count('QUAKE', player)) > 1 - and ( - (self.has('LEFTDASH', player, 2) and self.has('RIGHTDASH', player, 2)) # Both Shade Cloaks - or (self._hk_option(player, 'ProficientCombat') and self.has('QUAKE', player)) # or Dive - ) - ) + return self.multiworld.worlds[player].options.StartLocation == start_location diff --git a/worlds/kh2/Client.py b/worlds/kh2/Client.py index 513d85257b9..e2d2338b765 100644 --- a/worlds/kh2/Client.py +++ b/worlds/kh2/Client.py @@ -116,12 +116,19 @@ def __init__(self, server_address, password): # self.inBattle = 0x2A0EAC4 + 0x40 # self.onDeath = 0xAB9078 # PC Address anchors - self.Now = 0x0714DB8 - self.Save = 0x09A70B0 + # self.Now = 0x0714DB8 old address + # epic addresses + self.Now = 0x0716DF8 + self.Save = 0x09A92F0 + self.Journal = 0x743260 + self.Shop = 0x743350 + self.Slot1 = 0x2A22FD8 # self.Sys3 = 0x2A59DF0 # self.Bt10 = 0x2A74880 # self.BtlEnd = 0x2A0D3E0 - self.Slot1 = 0x2A20C98 + # self.Slot1 = 0x2A20C98 old address + + self.kh2_game_version = None # can be egs or steam self.chest_set = set(exclusion_table["Chests"]) self.keyblade_set = set(CheckDupingItems["Weapons"]["Keyblades"]) @@ -228,6 +235,9 @@ def kh2_read_int(self, address): def kh2_write_int(self, address, value): self.kh2.write_int(self.kh2.base_address + address, value) + def kh2_read_string(self, address, length): + return self.kh2.read_string(self.kh2.base_address + address, length) + def on_package(self, cmd: str, args: dict): if cmd in {"RoomInfo"}: self.kh2seedname = args['seed_name'] @@ -367,10 +377,26 @@ def on_package(self, cmd: str, args: dict): for weapon_location in all_weapon_slot: all_weapon_location_id.append(self.kh2_loc_name_to_id[weapon_location]) self.all_weapon_location_id = set(all_weapon_location_id) + try: self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") - logger.info("You are now auto-tracking") - self.kh2connected = True + if self.kh2_game_version is None: + if self.kh2_read_string(0x09A9830, 4) == "KH2J": + self.kh2_game_version = "STEAM" + self.Now = 0x0717008 + self.Save = 0x09A9830 + self.Slot1 = 0x2A23518 + self.Journal = 0x7434E0 + self.Shop = 0x7435D0 + + elif self.kh2_read_string(0x09A92F0, 4) == "KH2J": + self.kh2_game_version = "EGS" + else: + self.kh2_game_version = None + logger.info("Your game version is out of date. Please update your game via The Epic Games Store or Steam.") + if self.kh2_game_version is not None: + logger.info(f"You are now auto-tracking. {self.kh2_game_version}") + self.kh2connected = True except Exception as e: if self.kh2connected: @@ -589,8 +615,8 @@ async def IsInShop(self, sellable): # if journal=-1 and shop = 5 then in shop # if journal !=-1 and shop = 10 then journal - journal = self.kh2_read_short(0x741230) - shop = self.kh2_read_short(0x741320) + journal = self.kh2_read_short(self.Journal) + shop = self.kh2_read_short(self.Shop) if (journal == -1 and shop == 5) or (journal != -1 and shop == 10): # print("your in the shop") sellable_dict = {} @@ -599,8 +625,8 @@ async def IsInShop(self, sellable): amount = self.kh2_read_byte(self.Save + itemdata.memaddr) sellable_dict[itemName] = amount while (journal == -1 and shop == 5) or (journal != -1 and shop == 10): - journal = self.kh2_read_short(0x741230) - shop = self.kh2_read_short(0x741320) + journal = self.kh2_read_short(self.Journal) + shop = self.kh2_read_short(self.Shop) await asyncio.sleep(0.5) for item, amount in sellable_dict.items(): itemdata = self.item_name_to_data[item] @@ -750,7 +776,7 @@ async def verifyItems(self): item_data = self.item_name_to_data[item_name] amount_of_items = 0 amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["Magic"][item_name] - if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(0x741320) in {10, 8}: + if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(self.Shop) in {10, 8}: self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items) for item_name in master_stat: @@ -802,7 +828,7 @@ async def verifyItems(self): self.kh2_write_byte(self.Save + 0x2502, current_item_slots + 1) elif self.base_item_slots + amount_of_items < 8: self.kh2_write_byte(self.Save + 0x2502, self.base_item_slots + amount_of_items) - + # if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items \ # and self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and \ # self.kh2_read_byte(self.Save + 0x23DF) & 0x1 << 3 > 0 and self.kh2_read_byte(0x741320) in {10, 8}: @@ -905,8 +931,23 @@ async def kh2_watcher(ctx: KH2Context): await asyncio.sleep(15) ctx.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX") if ctx.kh2 is not None: - logger.info("You are now auto-tracking") - ctx.kh2connected = True + if ctx.kh2_game_version is None: + if ctx.kh2_read_string(0x09A9830, 4) == "KH2J": + ctx.kh2_game_version = "STEAM" + ctx.Now = 0x0717008 + ctx.Save = 0x09A9830 + ctx.Slot1 = 0x2A23518 + ctx.Journal = 0x7434E0 + ctx.Shop = 0x7435D0 + + elif ctx.kh2_read_string(0x09A92F0, 4) == "KH2J": + ctx.kh2_game_version = "EGS" + else: + ctx.kh2_game_version = None + logger.info("Your game version is out of date. Please update your game via The Epic Games Store or Steam.") + if ctx.kh2_game_version is not None: + logger.info(f"You are now auto-tracking {ctx.kh2_game_version}") + ctx.kh2connected = True except Exception as e: if ctx.kh2connected: ctx.kh2connected = False diff --git a/worlds/kh2/docs/setup_en.md b/worlds/kh2/docs/setup_en.md index c6fdb020b8a..ed4d90bb54f 100644 --- a/worlds/kh2/docs/setup_en.md +++ b/worlds/kh2/docs/setup_en.md @@ -1,22 +1,25 @@ # Kingdom Hearts 2 Archipelago Setup Guide +

Quick Links

- [Game Info Page](../../../../games/Kingdom%20Hearts%202/info/en) - [Player Options Page](../../../../games/Kingdom%20Hearts%202/player-options)

Required Software:

- `Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) -- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/)
- 1. `3.2.0 OpenKH Mod Manager with Panacea`
- 2. `Lua Backend from the OpenKH Mod Manager` - 3. `Install the mod KH2FM-Mods-Num/GoA-ROM-Edition using OpenKH Mod Manager`
+`Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) or [Steam](https://store.steampowered.com/app/2552430/KINGDOM_HEARTS_HD_1525_ReMIX/) + +- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) + 1. `Version 3.3.0 or greater OpenKH Mod Manager with Panacea` + 2. `Lua Backend from the OpenKH Mod Manager` + 3. `Install the mod KH2FM-Mods-Num/GoA-ROM-Edition using OpenKH Mod Manager` - Needed for Archipelago - 1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases)
- 2. `Install the Archipelago Companion mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`
- 3. `Install the Archipelago Quality Of Life mod from JaredWeakStrike/AP_QOL using OpenKH Mod Manager`
- 4. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`
+ 1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases) + 2. `Install the Archipelago Companion mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager` + 3. `Install the Archipelago Quality Of Life mod from JaredWeakStrike/AP_QOL using OpenKH Mod Manager` + 4. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager` 5. `AP Randomizer Seed` +

Required: Archipelago Companion Mod

Load this mod just like the GoA ROM you did during the KH2 Rando setup. `JaredWeakStrike/APCompanion`
@@ -24,6 +27,7 @@ Have this mod second-highest priority below the .zip seed.
This mod is based upon Num's Garden of Assemblege Mod and requires it to work. Without Num this could not be possible.

Required: Auto Save Mod

+ Load this mod just like the GoA ROM you did during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` Location doesn't matter, required in case of crashes. See [Best Practices](en#best-practices) on how to load the auto save

Installing A Seed

@@ -33,33 +37,33 @@ Make sure the seed is on the top of the list (Highest Priority)
After Installing the seed click `Mod Loader -> Build/Build and Run`. Every slot is a unique mod to install and will be needed be repatched for different slots/rooms.

What the Mod Manager Should Look Like.

+ ![image](https://i.imgur.com/Si4oZ8w.png)

Using the KH2 Client

-Once you have started the game through OpenKH Mod Manager and are on the title screen run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases).
+Once you have started the game through OpenKH Mod Manager and are on the title screen run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases).
When you successfully connect to the server the client will automatically hook into the game to send/receive checks.
If the client ever loses connection to the game, it will also disconnect from the server and you will need to reconnect.
`Make sure the game is open whenever you try to connect the client to the server otherwise it will immediately disconnect you.`
Most checks will be sent to you anywhere outside a load or cutscene.
`If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.` -
+

KH2 Client should look like this:

+ ![image](https://i.imgur.com/qP6CmV8.png) -
-Enter `The room's port number` into the top box where the x's are and press "Connect". Follow the prompts there and you should be connected +Enter `The room's port number` into the top box where the x's are and press "Connect". Follow the prompts there and you should be connected

Common Pitfalls

+ - Having an old GOA Lua Script in your `C:\Users\*YourName*\Documents\KINGDOM HEARTS HD 1.5+2.5 ReMIX\scripts\kh2` folder. - - Pressing F2 while in game should look like this. ![image](https://i.imgur.com/ABSdtPC.png) -
+ - Pressing F2 while in game should look like this. ![image](https://i.imgur.com/ABSdtPC.png) - Not having Lua Backend Configured Correctly. - - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Lua Backend Configuration Step. -
-- Loading into Simulated Twilight Town Instead of the GOA. - - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps. + - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Lua Backend Configuration Step. +- Loading into Simulated Twilight Town Instead of the GOA. + - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps.

Best Practices

@@ -70,8 +74,11 @@ Enter `The room's port number` into the top box where the x's are and pr - Make sure to save in a different save slot when playing in an async or disconnecting from the server to play a different seed

Logic Sheet

+ Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing) +

F.A.Q.

+ - Why is my Client giving me a "Cannot Open Process: " error? - Due to how the client reads kingdom hearts 2 memory some people's computer flags it as a virus. Run the client as admin. - Why is my HP/MP continuously increasing without stopping? @@ -83,11 +90,13 @@ Have any questions on what's in logic? This spreadsheet made by Bulcon has the a - Why did I not load into the correct visit? - You need to trigger a cutscene or visit The World That Never Was for it to register that you have received the item. - What versions of Kingdom Hearts 2 are supported? - - Currently `only` the most up to date version on the Epic Game Store is supported: version `1.0.0.8_WW`. + - Currently the `only` supported versions are `Epic Games Version 1.0.0.9_WW` and `Steam Build Version 14716933`. - Why am I getting wallpapered while going into a world for the first time? - - Your `Lua Backend` was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide. + - Your `Lua Backend` was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide. - Why am I not getting magic? - If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable. +- Why did I crash after picking my dream weapon? + - This is normally caused by having an outdated GOA mod or having an outdated panacea and/or luabackend. To fix this rerun the setup wizard and reinstall luabackend and panacea. Also make sure all your mods are up-to-date. - Why did I crash? - The port of Kingdom Hearts 2 can and will randomly crash, this is the fault of the game not the randomizer or the archipelago client. - If you have a continuous/constant crash (in the same area/event every time) you will want to reverify your installed files. This can be done by doing the following: Open Epic Game Store --> Library --> Click Triple Dots --> Manage --> Verify @@ -99,5 +108,3 @@ Have any questions on what's in logic? This spreadsheet made by Bulcon has the a - Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress. - How do I load an auto save? - To load an auto-save, hold down the Select or your equivalent on your prefered controller while choosing a file. Make sure to hold the button down the whole time. - - diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index 21876ed671e..c958ef212fe 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -98,9 +98,12 @@ class LinksAwakeningWorld(World): # Items can be grouped using their names to allow easy checking if any item # from that group has been collected. Group names can also be used for !hint - #item_name_groups = { - # "weapons": {"sword", "lance"} - #} + item_name_groups = { + "Instruments": { + "Full Moon Cello", "Conch Horn", "Sea Lily's Bell", "Surf Harp", + "Wind Marimba", "Coral Triangle", "Organ of Evening Calm", "Thunder Drum" + }, + } prefill_dungeon_items = None diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index 8d6a7fc4ebe..9853be73fa9 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -3,13 +3,13 @@ """ from logging import warning -from BaseClasses import Item, ItemClassification, Tutorial +from BaseClasses import CollectionState, Item, ItemClassification, Tutorial from Options import OptionError from worlds.AutoWorld import WebWorld, World from .datatypes import Room, RoomEntrance from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP -from .options import LingoOptions, lingo_option_groups +from .options import LingoOptions, lingo_option_groups, SunwarpAccess, VictoryCondition from .player_logic import LingoPlayerLogic from .regions import create_regions @@ -54,20 +54,54 @@ class LingoWorld(World): player_logic: LingoPlayerLogic def generate_early(self): - if not (self.options.shuffle_doors or self.options.shuffle_colors or self.options.shuffle_sunwarps): + if not (self.options.shuffle_doors or self.options.shuffle_colors or + (self.options.sunwarp_access >= SunwarpAccess.option_unlock and + self.options.victory_condition == VictoryCondition.option_pilgrimage)): if self.multiworld.players == 1: - warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression" - f" items. Please turn on Door Shuffle, Color Shuffle, or Sunwarp Shuffle if that doesn't seem" - f" right.") + warning(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on Door" + f" Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage victory condition" + f" if that doesn't seem right.") else: - raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any" - f" progression items. Please turn on Door Shuffle, Color Shuffle or Sunwarp Shuffle.") + raise OptionError(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on" + f" Door Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage" + f" victory condition.") self.player_logic = LingoPlayerLogic(self) def create_regions(self): create_regions(self) + if not self.options.shuffle_postgame: + state = CollectionState(self.multiworld) + state.collect(LingoItem("Prevent Victory", ItemClassification.progression, None, self.player), True) + + # Note: relies on the assumption that real_items is a definitive list of real progression items in this + # world, and is not modified after being created. + for item in self.player_logic.real_items: + state.collect(self.create_item(item), True) + + # Exception to the above: a forced good item is not considered a "real item", but needs to be here anyway. + if self.player_logic.forced_good_item != "": + state.collect(self.create_item(self.player_logic.forced_good_item), True) + + all_locations = self.multiworld.get_locations(self.player) + state.sweep_for_events(locations=all_locations) + + unreachable_locations = [location for location in all_locations + if not state.can_reach_location(location.name, self.player)] + + for location in unreachable_locations: + if location.name in self.player_logic.event_loc_to_item.keys(): + continue + + self.player_logic.real_locations.remove(location.name) + location.parent_region.locations.remove(location) + + if len(self.player_logic.real_items) > len(self.player_logic.real_locations): + raise OptionError(f"{self.player_name}'s Lingo world does not have enough locations to fit the number" + f" of required items without shuffling the postgame. Either enable postgame" + f" shuffling, or choose different options.") + def create_items(self): pool = [self.create_item(name) for name in self.player_logic.real_items] @@ -136,7 +170,8 @@ def fill_slot_data(self): slot_options = [ "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", "enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks", - "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps" + "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps", + "group_doors" ] slot_data = { diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 4d6771a7350..950fd326743 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -1,6 +1,13 @@ --- # This file is an associative array where the keys are region names. Rooms - # have four properties: entrances, panels, doors, and paintings. + # have a number of properties: + # - entrances + # - panels + # - doors + # - panel_doors + # - paintings + # - progression + # - sunwarps # # entrances is an array of regions from which this room can be accessed. The # key of each entry is the room that can access this one. The value is a list @@ -13,7 +20,7 @@ # room that the door is in. The room name may be omitted if the door is # located in the current room. # - # panels is an array of panels in the room. The key of the array is an + # panels is a named array of panels in the room. The key of the array is an # arbitrary name for the panel. Panels can have the following fields: # - id: The internal ID of the panel in the LINGO map # - required_room: In addition to having access to this room, the player must @@ -45,7 +52,7 @@ # - hunt: If True, the tracker will show this panel even when it is # not a check. Used for hunts like the Number Hunt. # - # doors is an array of doors associated with this room. When door + # doors is a named array of doors associated with this room. When door # randomization is enabled, each of these is an item. The key is a name that # will be displayed as part of the item's name. Doors can have the following # fields: @@ -78,6 +85,18 @@ # - event: Denotes that the door is event only. This is similar to # setting both skip_location and skip_item. # + # panel_doors is a named array of "panel doors" associated with this room. + # When panel door shuffle is enabled, each of these becomes an item, and those + # items block access to the listed panels. The key is a name for internal + # reference only. Panel doors can have the following fields: + # - panels: Required. This is the set of panels that are blocked by this + # panel door. + # - item_name: Overrides the name of the item generated for this panel + # door. If not specified, the item name will be generated from + # the room name and the name(s) of the panel(s). + # - panel_group: When region grouping is enabled, all panel doors with the + # same group will be covered by a single item. + # # paintings is an array of paintings in the room. This is used for painting # shuffling. # - id: The internal painting ID from the LINGO map. @@ -105,6 +124,14 @@ # fine in door shuffle mode. # - move: Denotes that the painting is able to move. # + # progression is a named array of items that define an ordered set of items. + # progression items do not have any true connection to the rooms that they + # are defined in, but it is best to place them in a thematically appropriate + # room. The key for a progression entry is the name of the item that will be + # created. A progression entry is a dictionary with one or both of a "doors" + # key and a "panel_doors" key. These fields should be lists of doors or + # panel doors that will be contained in this progressive item. + # # sunwarps is an array of sunwarps in the room. This is used for sunwarp # shuffling. # - dots: The number of dots on this sunwarp. @@ -140,6 +167,15 @@ painting: True The Colorful: painting: True + Welcome Back Area: + room: Welcome Back Area + door: Shortcut to Starting Room + Second Room: + door: Main Door + Hidden Room: + door: Back Right Door + Rhyme Room (Looped Square): + door: Rhyme Room Entrance panels: HI: id: Entry Room/Panel_hi_hi @@ -184,6 +220,10 @@ panel: RACECAR (Black) - room: The Tenacious panel: SOLOS (Black) + panel_doors: + HIDDEN: + panels: + - HIDDEN paintings: - id: arrows_painting exit_only: True @@ -294,6 +334,10 @@ panel: SOLOS (Black) - room: Hub Room panel: RAT + panel_doors: + OPEN: + panels: + - OPEN paintings: - id: owl_painting orientation: north @@ -308,7 +352,13 @@ panels: Achievement: id: Countdown Panels/Panel_seeker_seeker - required_room: Hidden Room + # The Seeker uniquely has the property that 1) it can be entered (through the Pilgrim Room) without opening the + # front door in panels mode door shuffle, and 2) the front door panel is part of the CDP. This necessitates this + # required_panel clause, because the entrance panel needs to be solvable for the achievement even if an + # alternate entrance to the room is used. + required_panel: + room: Hidden Room + panel: OPEN tag: forbid check: True achievement: The Seeker @@ -528,6 +578,23 @@ item_group: Achievement Room Entrances panels: - OPEN + panel_doors: + ORDER: + panels: + - ORDER + SLAUGHTER: + panel_group: Tenacious Entrance Panels + panels: + - SLAUGHTER + TRACE: + panels: + - TRACE + RAT: + panels: + - RAT + OPEN: + panels: + - OPEN paintings: - id: maze_painting orientation: west @@ -599,12 +666,13 @@ item_name: "6 Sunwarp" progression: Progressive Pilgrimage: - - 1 Sunwarp - - 2 Sunwarp - - 3 Sunwarp - - 4 Sunwarp - - 5 Sunwarp - - 6 Sunwarp + doors: + - 1 Sunwarp + - 2 Sunwarp + - 3 Sunwarp + - 4 Sunwarp + - 5 Sunwarp + - 6 Sunwarp Pilgrim Antechamber: # The entrances to this room are special. When pilgrimage is enabled, we use a special access rule to determine # whether a pilgrimage can succeed. When pilgrimage is disabled, the sun painting will be added to the pool. @@ -870,6 +938,26 @@ panel: DRAWL + RUNS - room: Owl Hallway panel: READS + RUST + - room: Ending Area + panel: THE END + panel_doors: + DECAY: + panel_group: Tenacious Entrance Panels + panels: + - DECAY + NOPE: + panels: + - NOPE + WE ROT: + panels: + - WE ROT + WORDS SWORD: + panels: + - WORDS + - SWORD + BEND HI: + panels: + - BEND HI paintings: - id: eye_painting disable: True @@ -884,6 +972,14 @@ direction: exit entrance_indicator_pos: [ -17, 2.5, -41.01 ] orientation: north + progression: + Progressive Suits Area: + panel_doors: + - WORDS SWORD + - room: Lost Area + panel_door: LOST + - room: Amen Name Area + panel_door: AMEN NAME Lost Area: entrances: Outside The Agreeable: @@ -909,6 +1005,11 @@ panels: - LOST (1) - LOST (2) + panel_doors: + LOST: + panels: + - LOST (1) + - LOST (2) Amen Name Area: entrances: Crossroads: @@ -942,6 +1043,11 @@ panels: - AMEN - NAME + panel_doors: + AMEN NAME: + panels: + - AMEN + - NAME Suits Area: entrances: Amen Name Area: @@ -1045,6 +1151,13 @@ - LEVEL (White) - RACECAR (White) - SOLOS (White) + panel_doors: + Black Palindromes: + item_name: The Tenacious - Black Palindromes (Panels) + panels: + - LEVEL (Black) + - RACECAR (Black) + - SOLOS (Black) Near Far Area: entrances: Hub Room: True @@ -1070,6 +1183,21 @@ panels: - NEAR - FAR + panel_doors: + NEAR FAR: + item_name: Symmetry Room - NEAR, FAR (Panels) + panel_group: Symmetry Room Panels + panels: + - NEAR + - FAR + progression: + Progressive Symmetry Room: + panel_doors: + - NEAR FAR + - room: Warts Straw Area + panel_door: WARTS STRAW + - room: Leaf Feel Area + panel_door: LEAF FEEL Warts Straw Area: entrances: Near Far Area: @@ -1097,6 +1225,13 @@ panels: - WARTS - STRAW + panel_doors: + WARTS STRAW: + item_name: Symmetry Room - WARTS, STRAW (Panels) + panel_group: Symmetry Room Panels + panels: + - WARTS + - STRAW Leaf Feel Area: entrances: Warts Straw Area: @@ -1124,6 +1259,13 @@ panels: - LEAF - FEEL + panel_doors: + LEAF FEEL: + item_name: Symmetry Room - LEAF, FEEL (Panels) + panel_group: Symmetry Room Panels + panels: + - LEAF + - FEEL Outside The Agreeable: entrances: Crossroads: @@ -1232,6 +1374,20 @@ panels: - room: Color Hunt panel: PURPLE + panel_doors: + MASSACRED: + panel_group: Tenacious Entrance Panels + panels: + - MASSACRED + BLACK: + panels: + - BLACK + CLOSE: + panels: + - CLOSE + RIGHT: + panels: + - RIGHT paintings: - id: eyes_yellow_painting orientation: east @@ -1283,6 +1439,14 @@ - WINTER - DIAMONDS - FIRE + panel_doors: + Lookout: + item_name: Compass Room Panels + panels: + - NORTH + - WINTER + - DIAMONDS + - FIRE paintings: - id: pencil_painting7 orientation: north @@ -1392,6 +1556,8 @@ room: Owl Hallway door: Shortcut to Hedge Maze Roof: True + The Incomparable: + door: Observant Entrance panels: DOWN: id: Maze Room/Panel_down_up @@ -1499,6 +1665,10 @@ - HIDE (3) - room: Outside The Agreeable panel: HIDE + panel_doors: + DOWN: + panels: + - DOWN The Perceptive: entrances: Starting Room: @@ -1520,6 +1690,10 @@ check: True exclude_reduce: True tag: botwhite + panel_doors: + GAZE: + panels: + - GAZE paintings: - id: garden_painting_tower orientation: north @@ -1561,9 +1735,10 @@ - EAT progression: Progressive Fearless: - - Second Floor - - room: The Fearless (Second Floor) - door: Third Floor + doors: + - Second Floor + - room: The Fearless (Second Floor) + door: Third Floor The Fearless (Second Floor): entrances: The Fearless (First Floor): @@ -1658,6 +1833,10 @@ tag: forbid required_door: door: Stairs + required_panel: + - panel: FOUR (1) + - panel: FOUR (2) + - panel: SIX achievement: The Observant FOUR (1): id: Look Room/Panel_four_back @@ -1771,6 +1950,16 @@ door_group: Observant Doors panels: - SIX + panel_doors: + BACKSIDE: + item_name: The Observant - Backside Entrance Panels + panel_group: Backside Entrance Panels + panels: + - FOUR (1) + - FOUR (2) + STAIRS: + panels: + - SIX The Incomparable: entrances: The Observant: @@ -1780,6 +1969,9 @@ door: Eight Door Orange Tower Sixth Floor: painting: True + Hedge Maze: + room: Hedge Maze + door: Observant Entrance panels: Achievement: id: Countdown Panels/Panel_incomparable_incomparable @@ -1787,9 +1979,12 @@ check: True tag: forbid required_room: - - Elements Area - - Courtyard - Eight Room + required_panel: + - room: Courtyard + panel: I + - room: Elements Area + panel: A achievement: The Incomparable A (One): id: Strand Room/Panel_blank_a @@ -1854,6 +2049,15 @@ panel: I - room: Elements Area panel: A + panel_doors: + Giant Sevens: + item_name: Giant Seven Panels + panels: + - I (Seven) + - room: Courtyard + panel: I + - room: Elements Area + panel: A paintings: - id: crown_painting orientation: east @@ -1961,14 +2165,31 @@ panel: DRAWL + RUNS - room: Owl Hallway panel: READS + RUST + panel_doors: + Access: + item_name: Orange Tower Panels + panels: + - room: Orange Tower First Floor + panel: DADS + ALE + - room: Outside The Undeterred + panel: ART + ART + - room: Orange Tower Third Floor + panel: DEER + WREN + - room: Orange Tower Fourth Floor + panel: LEARNS + UNSEW + - room: Orange Tower Fifth Floor + panel: DRAWL + RUNS + - room: Owl Hallway + panel: READS + RUST progression: Progressive Orange Tower: - - Second Floor - - Third Floor - - Fourth Floor - - Fifth Floor - - Sixth Floor - - Seventh Floor + doors: + - Second Floor + - Third Floor + - Fourth Floor + - Fifth Floor + - Sixth Floor + - Seventh Floor Orange Tower First Floor: entrances: Hub Room: @@ -2011,6 +2232,10 @@ - SALT - room: Directional Gallery panel: PEPPER + panel_doors: + SECRET: + panels: + - SECRET sunwarps: - dots: 4 direction: enter @@ -2163,6 +2388,10 @@ id: Shuffle Room Area Doors/Door_hotcrust_shortcuts panels: - HOT CRUSTS + panel_doors: + HOT CRUSTS: + panels: + - HOT CRUSTS sunwarps: - dots: 5 direction: enter @@ -2277,6 +2506,12 @@ panels: - SIZE (Small) - SIZE (Big) + panel_doors: + SIZE: + item_name: Orange Tower Fifth Floor - SIZE Panels + panels: + - SIZE (Small) + - SIZE (Big) paintings: - id: hi_solved_painting3 orientation: south @@ -2313,7 +2548,7 @@ orientation: east - id: hi_solved_painting orientation: west - Orange Tower Seventh Floor: + Ending Area: entrances: Orange Tower Sixth Floor: room: Orange Tower @@ -2325,6 +2560,18 @@ check: True tag: forbid non_counting: True + location_name: Orange Tower Seventh Floor - THE END + doors: + End: + event: True + panels: + - THE END + Orange Tower Seventh Floor: + entrances: + Ending Area: + room: Ending Area + door: End + panels: THE MASTER: # We will set up special rules for this in code. id: Countdown Panels/Panel_master_master @@ -2608,6 +2855,15 @@ - SECOND - THIRD - FOURTH + panel_doors: + FIRST SECOND THIRD FOURTH: + item_name: Courtyard - Ordinal Panels + panel_group: Backside Entrance Panels + panels: + - FIRST + - SECOND + - THIRD + - FOURTH The Colorful (White): entrances: Courtyard: True @@ -2625,6 +2881,12 @@ location_name: The Colorful - White panels: - BEGIN + panel_doors: + BEGIN: + item_name: The Colorful - BEGIN (Panel) + panel_group: Colorful Panels + panels: + - BEGIN The Colorful (Black): entrances: The Colorful (White): @@ -2645,6 +2907,12 @@ door_group: Colorful Doors panels: - FOUND + panel_doors: + FOUND: + item_name: The Colorful - FOUND (Panel) + panel_group: Colorful Panels + panels: + - FOUND The Colorful (Red): entrances: The Colorful (Black): @@ -2665,6 +2933,12 @@ door_group: Colorful Doors panels: - LOAF + panel_doors: + LOAF: + item_name: The Colorful - LOAF (Panel) + panel_group: Colorful Panels + panels: + - LOAF The Colorful (Yellow): entrances: The Colorful (Red): @@ -2685,6 +2959,12 @@ door_group: Colorful Doors panels: - CREAM + panel_doors: + CREAM: + item_name: The Colorful - CREAM (Panel) + panel_group: Colorful Panels + panels: + - CREAM The Colorful (Blue): entrances: The Colorful (Yellow): @@ -2705,6 +2985,12 @@ door_group: Colorful Doors panels: - SUN + panel_doors: + SUN: + item_name: The Colorful - SUN (Panel) + panel_group: Colorful Panels + panels: + - SUN The Colorful (Purple): entrances: The Colorful (Blue): @@ -2725,6 +3011,12 @@ door_group: Colorful Doors panels: - SPOON + panel_doors: + SPOON: + item_name: The Colorful - SPOON (Panel) + panel_group: Colorful Panels + panels: + - SPOON The Colorful (Orange): entrances: The Colorful (Purple): @@ -2745,6 +3037,12 @@ door_group: Colorful Doors panels: - LETTERS + panel_doors: + LETTERS: + item_name: The Colorful - LETTERS (Panel) + panel_group: Colorful Panels + panels: + - LETTERS The Colorful (Green): entrances: The Colorful (Orange): @@ -2765,6 +3063,12 @@ door_group: Colorful Doors panels: - WALLS + panel_doors: + WALLS: + item_name: The Colorful - WALLS (Panel) + panel_group: Colorful Panels + panels: + - WALLS The Colorful (Brown): entrances: The Colorful (Green): @@ -2785,6 +3089,12 @@ door_group: Colorful Doors panels: - IRON + panel_doors: + IRON: + item_name: The Colorful - IRON (Panel) + panel_group: Colorful Panels + panels: + - IRON The Colorful (Gray): entrances: The Colorful (Brown): @@ -2805,6 +3115,12 @@ door_group: Colorful Doors panels: - OBSTACLE + panel_doors: + OBSTACLE: + item_name: The Colorful - OBSTACLE (Panel) + panel_group: Colorful Panels + panels: + - OBSTACLE The Colorful: entrances: The Colorful (Gray): @@ -2843,26 +3159,48 @@ orientation: north progression: Progressive Colorful: - - room: The Colorful (White) - door: Progress Door - - room: The Colorful (Black) - door: Progress Door - - room: The Colorful (Red) - door: Progress Door - - room: The Colorful (Yellow) - door: Progress Door - - room: The Colorful (Blue) - door: Progress Door - - room: The Colorful (Purple) - door: Progress Door - - room: The Colorful (Orange) - door: Progress Door - - room: The Colorful (Green) - door: Progress Door - - room: The Colorful (Brown) - door: Progress Door - - room: The Colorful (Gray) - door: Progress Door + doors: + - room: The Colorful (White) + door: Progress Door + - room: The Colorful (Black) + door: Progress Door + - room: The Colorful (Red) + door: Progress Door + - room: The Colorful (Yellow) + door: Progress Door + - room: The Colorful (Blue) + door: Progress Door + - room: The Colorful (Purple) + door: Progress Door + - room: The Colorful (Orange) + door: Progress Door + - room: The Colorful (Green) + door: Progress Door + - room: The Colorful (Brown) + door: Progress Door + - room: The Colorful (Gray) + door: Progress Door + panel_doors: + - room: The Colorful (White) + panel_door: BEGIN + - room: The Colorful (Black) + panel_door: FOUND + - room: The Colorful (Red) + panel_door: LOAF + - room: The Colorful (Yellow) + panel_door: CREAM + - room: The Colorful (Blue) + panel_door: SUN + - room: The Colorful (Purple) + panel_door: SPOON + - room: The Colorful (Orange) + panel_door: LETTERS + - room: The Colorful (Green) + panel_door: WALLS + - room: The Colorful (Brown) + panel_door: IRON + - room: The Colorful (Gray) + panel_door: OBSTACLE Welcome Back Area: entrances: Starting Room: @@ -2935,6 +3273,10 @@ door_group: Hedge Maze Doors panels: - STRAYS + panel_doors: + STRAYS: + panels: + - STRAYS paintings: - id: arrows_painting_8 orientation: south @@ -3132,6 +3474,13 @@ panel: I - room: Elements Area panel: A + panel_doors: + UNCOVER: + panels: + - UNCOVER + OXEN: + panels: + - OXEN paintings: - id: clock_painting_5 orientation: east @@ -3265,7 +3614,6 @@ door: Traveled Entrance Color Hallways: door: Color Hallways Entrance - warp: True panels: Achievement: id: Countdown Panels/Panel_traveled_traveled @@ -3502,6 +3850,13 @@ - RISE (Sunrise) - ZEN - SON + panel_doors: + UNOPEN: + panels: + - UNOPEN + BEGIN: + panels: + - BEGIN paintings: - id: pencil_painting2 orientation: west @@ -3797,6 +4152,34 @@ item_group: Achievement Room Entrances panels: - ZERO + panel_doors: + ZERO: + panels: + - ZERO + PEN: + panels: + - PEN + TWO: + item_name: Two Panels + panels: + - TWO (1) + - TWO (2) + THREE: + item_name: Three Panels + panels: + - THREE (1) + - THREE (2) + - THREE (3) + FOUR: + item_name: Four Panels + panels: + - FOUR + - room: Hub Room + panel: FOUR + - room: Dead End Area + panel: FOUR + - room: The Traveled + panel: FOUR paintings: - id: maze_painting_3 enter_only: True @@ -3972,6 +4355,10 @@ panel: FIVE (1) - room: Directional Gallery panel: FIVE (2) + First Six: + event: True + panels: + - SIX Sevens: id: - Count Up Room Area Doors/Door_seven_hider @@ -4080,12 +4467,109 @@ panel: NINE - room: Elements Area panel: NINE + panel_doors: + FIVE: + item_name: Five Panels + panels: + - FIVE + - room: Outside The Agreeable + panel: FIVE (1) + - room: Outside The Agreeable + panel: FIVE (2) + - room: Directional Gallery + panel: FIVE (1) + - room: Directional Gallery + panel: FIVE (2) + SIX: + item_name: Six Panels + panels: + - SIX + - room: Outside The Bold + panel: SIX + - room: Directional Gallery + panel: SIX (1) + - room: Directional Gallery + panel: SIX (2) + - room: The Bearer (East) + panel: SIX + - room: The Bearer (South) + panel: SIX + SEVEN: + item_name: Seven Panels + panels: + - SEVEN + - room: Directional Gallery + panel: SEVEN + - room: Knight Night Exit + panel: SEVEN (1) + - room: Knight Night Exit + panel: SEVEN (2) + - room: Knight Night Exit + panel: SEVEN (3) + - room: Outside The Initiated + panel: SEVEN (1) + - room: Outside The Initiated + panel: SEVEN (2) + EIGHT: + item_name: Eight Panels + panels: + - EIGHT + - room: Directional Gallery + panel: EIGHT + - room: The Eyes They See + panel: EIGHT + - room: Dead End Area + panel: EIGHT + - room: Crossroads + panel: EIGHT + - room: Hot Crusts Area + panel: EIGHT + - room: Art Gallery + panel: EIGHT + - room: Outside The Initiated + panel: EIGHT + NINE: + item_name: Nine Panels + panels: + - NINE + - room: Directional Gallery + panel: NINE + - room: Amen Name Area + panel: NINE + - room: Yellow Backside Area + panel: NINE + - room: Outside The Initiated + panel: NINE + - room: Outside The Bold + panel: NINE + - room: Rhyme Room (Cross) + panel: NINE + - room: Orange Tower Fifth Floor + panel: NINE + - room: Elements Area + panel: NINE paintings: - id: smile_painting_5 enter_only: True orientation: east required_door: door: Eights + progression: + Progressive Number Hunt: + panel_doors: + - room: Outside The Undeterred + panel_door: TWO + - room: Outside The Undeterred + panel_door: THREE + - room: Outside The Undeterred + panel_door: FOUR + - FIVE + - SIX + - SEVEN + - EIGHT + - NINE + - room: Outside The Undeterred + panel_door: ZERO Directional Gallery: entrances: Outside The Agreeable: @@ -4173,7 +4657,7 @@ tag: midorange required_door: room: Number Hunt - door: Sixes + door: First Six PARANOID: id: Backside Room/Panel_paranoid_paranoid tag: midwhite @@ -4181,7 +4665,7 @@ exclude_reduce: True required_door: room: Number Hunt - door: Sixes + door: First Six YELLOW: id: Color Arrow Room/Panel_yellow_afar tag: midwhite @@ -4244,6 +4728,11 @@ panels: - room: Color Hunt panel: YELLOW + panel_doors: + TURN LEARN: + panels: + - TURN + - LEARN paintings: - id: smile_painting_7 orientation: south @@ -4255,7 +4744,7 @@ move: True required_door: room: Number Hunt - door: Sixes + door: First Six - id: boxes_painting orientation: south - id: cherry_painting @@ -4322,6 +4811,34 @@ id: Rock Room Doors/Door_hint panels: - EXIT + panel_doors: + EXIT: + panels: + - EXIT + RED: + panel_group: Color Hunt Panels + panels: + - RED + BLUE: + panel_group: Color Hunt Panels + panels: + - BLUE + YELLOW: + panel_group: Color Hunt Panels + panels: + - YELLOW + ORANGE: + panel_group: Color Hunt Panels + panels: + - ORANGE + PURPLE: + panel_group: Color Hunt Panels + panels: + - PURPLE + GREEN: + panel_group: Color Hunt Panels + panels: + - GREEN paintings: - id: arrows_painting_7 orientation: east @@ -4459,6 +4976,14 @@ event: True panels: - HEART + panel_doors: + FARTHER: + panel_group: Backside Entrance Panels + panels: + - FARTHER + MIDDLE: + panels: + - MIDDLE The Bearer (East): entrances: Cross Tower (East): True @@ -5311,6 +5836,11 @@ item_name: Knight Night Room - Exit panels: - TRUSTED + panel_doors: + TRUSTED: + item_name: Knight Night Room - TRUSTED (Panel) + panels: + - TRUSTED Knight Night Exit: entrances: Knight Night (Outer Ring): @@ -5995,6 +6525,10 @@ item_group: Achievement Room Entrances panels: - SHRINK + panel_doors: + SHRINK: + panels: + - SHRINK The Wondrous (Doorknob): entrances: Outside The Wondrous: @@ -6206,18 +6740,36 @@ - KEEP - BAILEY - TOWER + panel_doors: + CASTLE: + item_name: Hallway Room - First Room Panels + panel_group: Hallway Room Panels + panels: + - WALL + - KEEP + - BAILEY + - TOWER paintings: - id: panda_painting orientation: south progression: Progressive Hallway Room: - - Exit - - room: Hallway Room (2) - door: Exit - - room: Hallway Room (3) - door: Exit - - room: Hallway Room (4) - door: Exit + doors: + - Exit + - room: Hallway Room (2) + door: Exit + - room: Hallway Room (3) + door: Exit + - room: Hallway Room (4) + door: Exit + panel_doors: + - CASTLE + - room: Hallway Room (2) + panel_door: COUNTERCLOCKWISE + - room: Hallway Room (3) + panel_door: TRANSFORMATION + - room: Hallway Room (4) + panel_door: WHEELBARROW Hallway Room (2): entrances: Hallway Room (1): @@ -6256,6 +6808,15 @@ - CLOCK - ER - COUNT + panel_doors: + COUNTERCLOCKWISE: + item_name: Hallway Room - Second Room Panels + panel_group: Hallway Room Panels + panels: + - WISE + - CLOCK + - ER + - COUNT Hallway Room (3): entrances: Hallway Room (2): @@ -6294,6 +6855,15 @@ - FORM - A - SHUN + panel_doors: + TRANSFORMATION: + item_name: Hallway Room - Third Room Panels + panel_group: Hallway Room Panels + panels: + - TRANCE + - FORM + - A + - SHUN Hallway Room (4): entrances: Hallway Room (3): @@ -6316,6 +6886,12 @@ panels: - WHEEL include_reduce: True + panel_doors: + WHEELBARROW: + item_name: Hallway Room - WHEEL + panel_group: Hallway Room Panels + panels: + - WHEEL Elements Area: entrances: Roof: True @@ -6390,6 +6966,10 @@ panels: - room: The Wanderer panel: Achievement + panel_doors: + WANDERLUST: + panels: + - WANDERLUST The Wanderer: entrances: Outside The Wanderer: @@ -6531,6 +7111,10 @@ item_group: Achievement Room Entrances panels: - ORDER + panel_doors: + ORDER: + panels: + - ORDER paintings: - id: smile_painting_3 orientation: west @@ -6544,10 +7128,11 @@ orientation: south progression: Progressive Art Gallery: - - Second Floor - - Third Floor - - Fourth Floor - - Fifth Floor + doors: + - Second Floor + - Third Floor + - Fourth Floor + - Fifth Floor Art Gallery (Second Floor): entrances: Art Gallery: @@ -7069,6 +7654,8 @@ LEAP: id: Double Room/Panel_leap_leap tag: midwhite + required_door: + door: Door to Cross doors: Door to Cross: id: Double Room Area Doors/Door_room_4a @@ -7259,8 +7846,8 @@ id: Panel Room/Panel_broomed_bedroom colors: yellow tag: midyellow - required_door: - door: Excavation + required_panel: + panel: WALL (1) LAYS: id: Panel Room/Panel_lays_maze colors: purple @@ -7287,13 +7874,24 @@ Excavation: event: True panels: - - WALL (1) + - STAIRS Cellar Exit: id: - Tower Room Area Doors/Door_panel_basement - Tower Room Area Doors/Door_panel_basement2 panels: - BASE + panel_doors: + STAIRS: + panel_group: Room Room Panels + panels: + - STAIRS + Colors: + panel_group: Room Room Panels + panels: + - BROOMED + - LAYS + - BASE Cellar: entrances: Room Room: @@ -7332,6 +7930,11 @@ panels: - KITTEN - CAT + panel_doors: + KITTEN CAT: + panels: + - KITTEN + - CAT paintings: - id: arrows_painting_2 orientation: east @@ -7586,6 +8189,10 @@ item_group: Achievement Room Entrances panels: - OPEN + panel_doors: + OPEN: + panels: + - OPEN The Scientific: entrances: Outside The Scientific: diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat index 6c8c925138a..9a49d3d9d4b 100644 Binary files a/worlds/lingo/data/generated.dat and b/worlds/lingo/data/generated.dat differ diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 1fa06d24254..b46f1d36ec1 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -272,8 +272,9 @@ panels: PAINTING (4): 445081 PAINTING (5): 445082 ROOM: 445083 - Orange Tower Seventh Floor: + Ending Area: THE END: 444620 + Orange Tower Seventh Floor: THE MASTER: 444621 MASTERY: 444622 Behind A Smile: @@ -1477,3 +1478,145 @@ progression: Progressive Art Gallery: 444563 Progressive Colorful: 444580 Progressive Pilgrimage: 444583 + Progressive Suits Area: 444602 + Progressive Symmetry Room: 444608 + Progressive Number Hunt: 444654 +panel_doors: + Starting Room: + HIDDEN: 444589 + Hidden Room: + OPEN: 444590 + Hub Room: + ORDER: 444591 + SLAUGHTER: 444592 + TRACE: 444594 + RAT: 444595 + OPEN: 444596 + Crossroads: + DECAY: 444597 + NOPE: 444598 + WE ROT: 444599 + WORDS SWORD: 444600 + BEND HI: 444601 + Lost Area: + LOST: 444603 + Amen Name Area: + AMEN NAME: 444604 + The Tenacious: + Black Palindromes: 444605 + Near Far Area: + NEAR FAR: 444606 + Warts Straw Area: + WARTS STRAW: 444609 + Leaf Feel Area: + LEAF FEEL: 444610 + Outside The Agreeable: + MASSACRED: 444611 + BLACK: 444612 + CLOSE: 444613 + RIGHT: 444614 + Compass Room: + Lookout: 444615 + Hedge Maze: + DOWN: 444617 + The Perceptive: + GAZE: 444618 + The Observant: + BACKSIDE: 444619 + STAIRS: 444621 + The Incomparable: + Giant Sevens: 444622 + Orange Tower: + Access: 444623 + Orange Tower First Floor: + SECRET: 444624 + Orange Tower Fourth Floor: + HOT CRUSTS: 444625 + Orange Tower Fifth Floor: + SIZE: 444626 + First Second Third Fourth: + FIRST SECOND THIRD FOURTH: 444627 + The Colorful (White): + BEGIN: 444628 + The Colorful (Black): + FOUND: 444630 + The Colorful (Red): + LOAF: 444631 + The Colorful (Yellow): + CREAM: 444632 + The Colorful (Blue): + SUN: 444633 + The Colorful (Purple): + SPOON: 444634 + The Colorful (Orange): + LETTERS: 444635 + The Colorful (Green): + WALLS: 444636 + The Colorful (Brown): + IRON: 444637 + The Colorful (Gray): + OBSTACLE: 444638 + Owl Hallway: + STRAYS: 444639 + Outside The Initiated: + UNCOVER: 444640 + OXEN: 444641 + Outside The Bold: + UNOPEN: 444642 + BEGIN: 444643 + Outside The Undeterred: + ZERO: 444644 + PEN: 444645 + TWO: 444646 + THREE: 444647 + FOUR: 444648 + Number Hunt: + FIVE: 444649 + SIX: 444650 + SEVEN: 444651 + EIGHT: 444652 + NINE: 444653 + Color Hunt: + EXIT: 444655 + RED: 444656 + BLUE: 444658 + YELLOW: 444659 + ORANGE: 444660 + PURPLE: 444661 + GREEN: 444662 + The Bearer: + FARTHER: 444663 + MIDDLE: 444664 + Knight Night (Final): + TRUSTED: 444665 + Outside The Wondrous: + SHRINK: 444666 + Hallway Room (1): + CASTLE: 444667 + Hallway Room (2): + COUNTERCLOCKWISE: 444669 + Hallway Room (3): + TRANSFORMATION: 444670 + Hallway Room (4): + WHEELBARROW: 444671 + Outside The Wanderer: + WANDERLUST: 444672 + Art Gallery: + ORDER: 444673 + Room Room: + STAIRS: 444674 + Colors: 444676 + Outside The Wise: + KITTEN CAT: 444677 + Outside The Scientific: + OPEN: 444678 + Directional Gallery: + TURN LEARN: 444679 +panel_groups: + Tenacious Entrance Panels: 444593 + Symmetry Room Panels: 444607 + Backside Entrance Panels: 444620 + Colorful Panels: 444629 + Color Hunt Panels: 444657 + Hallway Room Panels: 444668 + Room Room Panels: 444675 diff --git a/worlds/lingo/datatypes.py b/worlds/lingo/datatypes.py index 36141daa410..9521422ab15 100644 --- a/worlds/lingo/datatypes.py +++ b/worlds/lingo/datatypes.py @@ -12,6 +12,11 @@ class RoomAndPanel(NamedTuple): panel: str +class RoomAndPanelDoor(NamedTuple): + room: Optional[str] + panel_door: str + + class EntranceType(Flag): NORMAL = auto() PAINTING = auto() @@ -63,9 +68,15 @@ class Panel(NamedTuple): exclude_reduce: bool achievement: bool non_counting: bool + panel_door: Optional[RoomAndPanelDoor] # This will always be fully specified. location_name: Optional[str] +class PanelDoor(NamedTuple): + item_name: str + panel_group: Optional[str] + + class Painting(NamedTuple): id: str room: str diff --git a/worlds/lingo/items.py b/worlds/lingo/items.py index 67eaceab10f..78b288e7c2d 100644 --- a/worlds/lingo/items.py +++ b/worlds/lingo/items.py @@ -3,7 +3,7 @@ from BaseClasses import Item, ItemClassification from .static_logic import DOORS_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, get_door_item_id, \ - get_progressive_item_id, get_special_item_id + get_progressive_item_id, get_special_item_id, PANEL_DOORS_BY_ROOM, get_panel_door_item_id, get_panel_group_item_id class ItemType(Enum): @@ -65,6 +65,21 @@ def load_item_data(): ItemClassification.progression, ItemType.NORMAL, True, []) ITEMS_BY_GROUP.setdefault("Doors", []).append(group) + panel_groups: Set[str] = set() + for room_name, panel_doors in PANEL_DOORS_BY_ROOM.items(): + for panel_door_name, panel_door in panel_doors.items(): + if panel_door.panel_group is not None: + panel_groups.add(panel_door.panel_group) + + ALL_ITEM_TABLE[panel_door.item_name] = ItemData(get_panel_door_item_id(room_name, panel_door_name), + ItemClassification.progression, ItemType.NORMAL, False, []) + ITEMS_BY_GROUP.setdefault("Panels", []).append(panel_door.item_name) + + for group in panel_groups: + ALL_ITEM_TABLE[group] = ItemData(get_panel_group_item_id(group), ItemClassification.progression, + ItemType.NORMAL, False, []) + ITEMS_BY_GROUP.setdefault("Panels", []).append(group) + special_items: Dict[str, ItemClassification] = { ":)": ItemClassification.filler, "The Feeling of Being Lost": ItemClassification.filler, diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py index 333b3e1ef08..2fd57ff5ede 100644 --- a/worlds/lingo/options.py +++ b/worlds/lingo/options.py @@ -8,21 +8,31 @@ class ShuffleDoors(Choice): - """If on, opening doors will require their respective "keys". + """This option specifies how doors open. - - **Simple:** Doors are sorted into logical groups, which are all opened by - receiving an item. - - **Complex:** The items are much more granular, and will usually only open - a single door each. + - **None:** Doors in the game will open the way they do in vanilla. + - **Panels:** Doors still open as in vanilla, but the panels that open the + doors will be locked, and an item will be required to unlock the panels. + - **Doors:** the doors themselves are locked behind items, and will open + automatically without needing to solve a panel once the key is obtained. """ display_name = "Shuffle Doors" option_none = 0 - option_simple = 1 - option_complex = 2 + option_panels = 1 + option_doors = 2 + alias_simple = 2 + alias_complex = 2 + + +class GroupDoors(Toggle): + """By default, door shuffle in either panels or doors mode will create individual keys for every panel or door to be locked. + + When group doors is on, some panels and doors are sorted into logical groups, which are opened together by receiving an item.""" + display_name = "Group Doors" class ProgressiveOrangeTower(DefaultOnToggle): - """When "Shuffle Doors" is on, this setting governs the manner in which the Orange Tower floors open up. + """When "Shuffle Doors" is on doors mode, this setting governs the manner in which the Orange Tower floors open up. - **Off:** There is an item for each floor of the tower, and each floor's item is the only one needed to access that floor. @@ -33,7 +43,7 @@ class ProgressiveOrangeTower(DefaultOnToggle): class ProgressiveColorful(DefaultOnToggle): - """When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up. + """When "Shuffle Doors" is on either panels or doors mode and "Group Doors" is off, this setting governs the manner in which The Colorful opens up. - **Off:** There is an item for each room of The Colorful, meaning that random rooms in the middle of the sequence can open up without giving you @@ -194,6 +204,11 @@ class EarlyColorHallways(Toggle): display_name = "Early Color Hallways" +class ShufflePostgame(Toggle): + """When off, locations that could not be reached without also reaching your victory condition are removed.""" + display_name = "Shuffle Postgame" + + class TrapPercentage(Range): """Replaces junk items with traps, at the specified rate.""" display_name = "Trap Percentage" @@ -248,6 +263,7 @@ class DeathLink(Toggle): @dataclass class LingoOptions(PerGameCommonOptions): shuffle_doors: ShuffleDoors + group_doors: GroupDoors progressive_orange_tower: ProgressiveOrangeTower progressive_colorful: ProgressiveColorful location_checks: LocationChecks @@ -263,6 +279,7 @@ class LingoOptions(PerGameCommonOptions): mastery_achievements: MasteryAchievements level_2_requirement: Level2Requirement early_color_hallways: EarlyColorHallways + shuffle_postgame: ShufflePostgame trap_percentage: TrapPercentage trap_weights: TrapWeights puzzle_skip_percentage: PuzzleSkipPercentage diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index 1621620e1e1..b21735c1f53 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -7,8 +7,8 @@ from .locations import ALL_LOCATION_TABLE, LocationClassification from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \ - PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, \ - SUNWARP_ENTRANCES, SUNWARP_EXITS + PANELS_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, PROGRESSIVE_DOORS_BY_ROOM, \ + PANEL_DOORS_BY_ROOM, PROGRESSIVE_PANELS_BY_ROOM, SUNWARP_ENTRANCES, SUNWARP_EXITS if TYPE_CHECKING: from . import LingoWorld @@ -18,23 +18,35 @@ class AccessRequirements: rooms: Set[str] doors: Set[RoomAndDoor] colors: Set[str] + items: Set[str] + progression: Dict[str, int] the_master: bool + postgame: bool def __init__(self): self.rooms = set() self.doors = set() self.colors = set() + self.items = set() + self.progression = dict() self.the_master = False + self.postgame = False def merge(self, other: "AccessRequirements"): self.rooms |= other.rooms self.doors |= other.doors self.colors |= other.colors + self.items |= other.items self.the_master |= other.the_master + self.postgame |= other.postgame + + for progression, index in other.progression.items(): + if progression not in self.progression or index > self.progression[progression]: + self.progression[progression] = index def __str__(self): - return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors})," \ - f" the_master={self.the_master}" + return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}, items={self.items}," \ + f" progression={self.progression}), the_master={self.the_master}, postgame={self.postgame}" class PlayerLocation(NamedTuple): @@ -114,15 +126,15 @@ def set_door_item(self, room: str, door: str, item: str): self.item_by_door.setdefault(room, {})[door] = item 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]: - progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name + if room_name in PROGRESSIVE_DOORS_BY_ROOM and door_data.name in PROGRESSIVE_DOORS_BY_ROOM[room_name]: + progression_name = PROGRESSIVE_DOORS_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) 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 + progressive_item_name = PROGRESSIVE_DOORS_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) else: @@ -153,17 +165,31 @@ def __init__(self, world: "LingoWorld"): victory_condition = world.options.victory_condition early_color_hallways = world.options.early_color_hallways - if location_checks == LocationChecks.option_reduced and door_shuffle != ShuffleDoors.option_none: - raise OptionError("You cannot have reduced location checks when door shuffle is on, because there would not" - " be enough locations for all of the door items.") + if location_checks == LocationChecks.option_reduced: + if door_shuffle == ShuffleDoors.option_doors: + raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks when door shuffle" + f" is on, because there would not be enough locations for all of the door items.") + if door_shuffle == ShuffleDoors.option_panels: + if not world.options.group_doors: + raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks when ungrouped" + f" panels mode door shuffle is on, because there would not be enough locations for" + f" all of the panel items.") + if color_shuffle: + raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks with both" + f" panels mode door shuffle and color shuffle because there would not be enough" + f" locations for all of the items.") + if world.options.sunwarp_access >= SunwarpAccess.option_individual: + raise OptionError(f"Slot \"{world.player_name}\" cannot have reduced location checks with both" + f" panels mode door shuffle and individual or progressive sunwarp access because" + f" there would not be enough locations for all of the items.") # Create door items, where needed. door_groups: Set[str] = set() for room_name, room_data in DOORS_BY_ROOM.items(): for door_name, door_data in room_data.items(): if door_data.skip_item is False and door_data.event is False: - if door_data.type == DoorType.NORMAL and door_shuffle != ShuffleDoors.option_none: - if door_data.door_group is not None and door_shuffle == ShuffleDoors.option_simple: + if door_data.type == DoorType.NORMAL and door_shuffle == ShuffleDoors.option_doors: + if door_data.door_group is not None and world.options.group_doors: # Grouped doors are handled differently if shuffle doors is on simple. self.set_door_item(room_name, door_name, door_data.door_group) door_groups.add(door_data.door_group) @@ -185,21 +211,33 @@ def __init__(self, world: "LingoWorld"): self.real_items.append(door_data.item_name) self.real_items += door_groups - + + # Create panel items, where needed. + if world.options.shuffle_doors == ShuffleDoors.option_panels: + panel_groups: Set[str] = set() + + for room_name, room_data in PANEL_DOORS_BY_ROOM.items(): + for panel_door_name, panel_door_data in room_data.items(): + if panel_door_data.panel_group is not None and world.options.group_doors: + panel_groups.add(panel_door_data.panel_group) + elif room_name in PROGRESSIVE_PANELS_BY_ROOM \ + and panel_door_name in PROGRESSIVE_PANELS_BY_ROOM[room_name]: + progression_obj = PROGRESSIVE_PANELS_BY_ROOM[room_name][panel_door_name] + progression_handling = should_split_progression(progression_obj.item_name, world) + + if progression_handling == ProgressiveItemBehavior.SPLIT: + self.real_items.append(panel_door_data.item_name) + elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE: + self.real_items.append(progression_obj.item_name) + else: + self.real_items.append(panel_door_data.item_name) + + self.real_items += panel_groups + # Create color items, if needed. if color_shuffle: self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR] - # Create events for each achievement panel, so that we can determine when THE MASTER is accessible. - for room_name, room_data in PANELS_BY_ROOM.items(): - for panel_name, panel_data in room_data.items(): - if panel_data.achievement: - access_req = AccessRequirements() - access_req.merge(self.calculate_panel_requirements(room_name, panel_name, world)) - access_req.rooms.add(room_name) - - self.mastery_reqs.append(access_req) - # Handle the victory condition. Victory conditions other than the chosen one become regular checks, so we need # to prevent the actual victory condition from becoming a check. self.mastery_location = "Orange Tower Seventh Floor - THE MASTER" @@ -207,7 +245,7 @@ def __init__(self, world: "LingoWorld"): if victory_condition == VictoryCondition.option_the_end: self.victory_condition = "Orange Tower Seventh Floor - THE END" - self.add_location("Orange Tower Seventh Floor", "The End (Solved)", None, [], world) + self.add_location("Ending Area", "The End (Solved)", None, [], world) self.event_loc_to_item["The End (Solved)"] = "Victory" elif victory_condition == VictoryCondition.option_the_master: self.victory_condition = "Orange Tower Seventh Floor - THE MASTER" @@ -231,6 +269,16 @@ def __init__(self, world: "LingoWorld"): [RoomAndPanel("Pilgrim Antechamber", "PILGRIM")], world) self.event_loc_to_item["PILGRIM (Solved)"] = "Victory" + # Create events for each achievement panel, so that we can determine when THE MASTER is accessible. + for room_name, room_data in PANELS_BY_ROOM.items(): + for panel_name, panel_data in room_data.items(): + if panel_data.achievement: + access_req = AccessRequirements() + access_req.merge(self.calculate_panel_requirements(room_name, panel_name, world)) + access_req.rooms.add(room_name) + + self.mastery_reqs.append(access_req) + # Create groups of counting panel access requirements for the LEVEL 2 check. self.create_panel_hunt_events(world) @@ -241,7 +289,7 @@ def __init__(self, world: "LingoWorld"): elif location_checks == LocationChecks.option_insanity: location_classification = LocationClassification.insanity - if door_shuffle != ShuffleDoors.option_none and not early_color_hallways: + if door_shuffle == ShuffleDoors.option_doors and not early_color_hallways: location_classification |= LocationClassification.small_sphere_one for location_name, location_data in ALL_LOCATION_TABLE.items(): @@ -283,7 +331,7 @@ def __init__(self, world: "LingoWorld"): "iterations. This is very unlikely to happen on its own, and probably indicates some " "kind of logic error.") - if door_shuffle != ShuffleDoors.option_none and location_checks != LocationChecks.option_insanity \ + if door_shuffle == ShuffleDoors.option_doors and location_checks != LocationChecks.option_insanity \ 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 @@ -298,19 +346,19 @@ def __init__(self, world: "LingoWorld"): # 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 and not world.options.enable_pilgrimage: - # HOT CRUST and THIS. - good_item_options.append("Pilgrim Room - Sun Painting") - if not color_shuffle: - if door_shuffle == ShuffleDoors.option_simple: + if not world.options.enable_pilgrimage: + # HOT CRUST and THIS. + good_item_options.append("Pilgrim Room - Sun Painting") + + if world.options.group_doors: # 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 door_shuffle == ShuffleDoors.option_simple: + if world.options.group_doors: # Color hallways access (NOTE: reconsider when sunwarp shuffling exists). good_item_options.append("Rhyme Room Doors") @@ -356,13 +404,11 @@ def __init__(self, world: "LingoWorld"): def randomize_paintings(self, world: "LingoWorld") -> bool: self.painting_mapping.clear() - door_shuffle = world.options.shuffle_doors - # First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to # required paintings. req_exits = [] required_painting_rooms = REQUIRED_PAINTING_ROOMS - if door_shuffle == ShuffleDoors.option_none: + if world.options.shuffle_doors != ShuffleDoors.option_doors: required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors] @@ -429,7 +475,7 @@ def is_req_enterable(painting_id: str, painting: Painting) -> bool: for painting_id, painting in PAINTINGS.items(): if painting_id not in self.painting_mapping.values() \ and (painting.required or (painting.required_when_no_doors and - door_shuffle == ShuffleDoors.option_none)): + world.options.shuffle_doors != ShuffleDoors.option_doors)): return False return True @@ -444,12 +490,31 @@ def calculate_panel_requirements(self, room: str, panel: str, world: "LingoWorld access_reqs = AccessRequirements() panel_object = PANELS_BY_ROOM[room][panel] + if world.options.shuffle_doors == ShuffleDoors.option_panels and panel_object.panel_door is not None: + panel_door_room = panel_object.panel_door.room + panel_door_name = panel_object.panel_door.panel_door + panel_door = PANEL_DOORS_BY_ROOM[panel_door_room][panel_door_name] + + if panel_door.panel_group is not None and world.options.group_doors: + access_reqs.items.add(panel_door.panel_group) + elif panel_door_room in PROGRESSIVE_PANELS_BY_ROOM\ + and panel_door_name in PROGRESSIVE_PANELS_BY_ROOM[panel_door_room]: + progression_obj = PROGRESSIVE_PANELS_BY_ROOM[panel_door_room][panel_door_name] + progression_handling = should_split_progression(progression_obj.item_name, world) + + if progression_handling == ProgressiveItemBehavior.SPLIT: + access_reqs.items.add(panel_door.item_name) + elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE: + access_reqs.progression[progression_obj.item_name] = progression_obj.index + else: + access_reqs.items.add(panel_door.item_name) + for req_room in panel_object.required_rooms: access_reqs.rooms.add(req_room) for req_door in panel_object.required_doors: door_object = DOORS_BY_ROOM[room if req_door.room is None else req_door.room][req_door.door] - if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none: + if door_object.event or world.options.shuffle_doors != ShuffleDoors.option_doors: sub_access_reqs = self.calculate_door_requirements( room if req_door.room is None else req_door.room, req_door.door, world) access_reqs.merge(sub_access_reqs) @@ -470,6 +535,11 @@ def calculate_panel_requirements(self, room: str, panel: str, world: "LingoWorld if panel == "THE MASTER": access_reqs.the_master = True + # Evil python magic (so sayeth NewSoupVi): this checks victory_condition against the panel's location name + # override if it exists, or the auto-generated location name if it's None. + if self.victory_condition == (panel_object.location_name or f"{room} - {panel}"): + access_reqs.postgame = True + self.panel_reqs[room][panel] = access_reqs return self.panel_reqs[room][panel] @@ -514,11 +584,14 @@ def create_panel_hunt_events(self, world: "LingoWorld"): continue # We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will - # only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. THE MASTER has - # special access rules and is handled separately. + # only coalesce single-color panels. Chains/stacks/combo puzzles will be separate. Panel door locked + # puzzles will be separate if panels mode is on. THE MASTER has special access rules and is handled + # separately. if len(panel_data.required_panels) > 0 or len(panel_data.required_doors) > 0\ or len(panel_data.required_rooms) > 0\ or (world.options.shuffle_colors and len(panel_data.colors) > 1)\ + or (world.options.shuffle_doors == ShuffleDoors.option_panels + and panel_data.panel_door is not None)\ or panel_name == "THE MASTER": self.counting_panel_reqs.setdefault(room_name, []).append( (self.calculate_panel_requirements(room_name, panel_name, world), 1)) diff --git a/worlds/lingo/regions.py b/worlds/lingo/regions.py index 9834f04f9de..9773f22d919 100644 --- a/worlds/lingo/regions.py +++ b/worlds/lingo/regions.py @@ -159,7 +159,7 @@ def create_regions(world: "LingoWorld") -> None: RoomAndDoor("Pilgrim Antechamber", "Sun Painting"), EntranceType.PAINTING, False, world) if early_color_hallways: - connect_entrance(regions, regions["Starting Room"], regions["Outside The Undeterred"], "Early Color Hallways", + connect_entrance(regions, regions["Starting Room"], regions["Color Hallways"], "Early Color Hallways", None, EntranceType.PAINTING, False, world) if painting_shuffle: diff --git a/worlds/lingo/rules.py b/worlds/lingo/rules.py index d91c53f05b4..e0bb08fa1f7 100644 --- a/worlds/lingo/rules.py +++ b/worlds/lingo/rules.py @@ -3,7 +3,7 @@ from BaseClasses import CollectionState from .datatypes import RoomAndDoor from .player_logic import AccessRequirements, PlayerLocation -from .static_logic import PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS +from .static_logic import PROGRESSIVE_DOORS_BY_ROOM, PROGRESSIVE_ITEMS if TYPE_CHECKING: from . import LingoWorld @@ -59,9 +59,18 @@ def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequir if not state.has(color.capitalize(), world.player): return False + if not all(state.has(item, world.player) for item in access.items): + return False + + if not all(state.has(item, world.player, index) for item, index in access.progression.items()): + return False + if access.the_master and not lingo_can_use_mastery_location(state, world): return False + if access.postgame and state.has("Prevent Victory", world.player): + return False + return True @@ -74,7 +83,7 @@ def _lingo_can_open_door(state: CollectionState, room: str, door: str, world: "L item_name = world.player_logic.item_by_door[room][door] if item_name in PROGRESSIVE_ITEMS: - progression = PROGRESSION_BY_ROOM[room][door] + progression = PROGRESSIVE_DOORS_BY_ROOM[room][door] return state.has(item_name, world.player, progression.index) return state.has(item_name, world.player) diff --git a/worlds/lingo/static_logic.py b/worlds/lingo/static_logic.py index ff820dd0cb1..74eea449f22 100644 --- a/worlds/lingo/static_logic.py +++ b/worlds/lingo/static_logic.py @@ -4,15 +4,17 @@ from io import BytesIO from typing import Dict, List, Set -from .datatypes import Door, Painting, Panel, Progression, Room +from .datatypes import Door, Painting, Panel, PanelDoor, Progression, Room ALL_ROOMS: List[Room] = [] DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {} PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {} +PANEL_DOORS_BY_ROOM: Dict[str, Dict[str, PanelDoor]] = {} PAINTINGS: Dict[str, Painting] = {} -PROGRESSIVE_ITEMS: List[str] = [] -PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {} +PROGRESSIVE_ITEMS: Set[str] = set() +PROGRESSIVE_DOORS_BY_ROOM: Dict[str, Dict[str, Progression]] = {} +PROGRESSIVE_PANELS_BY_ROOM: Dict[str, Dict[str, Progression]] = {} PAINTING_ENTRANCES: int = 0 PAINTING_EXIT_ROOMS: Set[str] = set() @@ -28,6 +30,8 @@ DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} DOOR_GROUP_ITEM_IDS: Dict[str, int] = {} +PANEL_DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} +PANEL_GROUP_ITEM_IDS: Dict[str, int] = {} PROGRESSIVE_ITEM_IDS: Dict[str, int] = {} HASHES: Dict[str, str] = {} @@ -68,6 +72,20 @@ def get_door_group_item_id(name: str): return DOOR_GROUP_ITEM_IDS[name] +def get_panel_door_item_id(room: str, name: str): + if room not in PANEL_DOOR_ITEM_IDS or name not in PANEL_DOOR_ITEM_IDS[room]: + raise Exception(f"Item ID for panel door {room} - {name} not found in ids.yaml.") + + return PANEL_DOOR_ITEM_IDS[room][name] + + +def get_panel_group_item_id(name: str): + if name not in PANEL_GROUP_ITEM_IDS: + raise Exception(f"Item ID for panel group {name} not found in ids.yaml.") + + return PANEL_GROUP_ITEM_IDS[name] + + def get_progressive_item_id(name: str): if name not in PROGRESSIVE_ITEM_IDS: raise Exception(f"Item ID for progressive item {name} not found in ids.yaml.") @@ -97,8 +115,10 @@ def find_class(self, module, name): ALL_ROOMS.extend(pickdata["ALL_ROOMS"]) DOORS_BY_ROOM.update(pickdata["DOORS_BY_ROOM"]) PANELS_BY_ROOM.update(pickdata["PANELS_BY_ROOM"]) - PROGRESSIVE_ITEMS.extend(pickdata["PROGRESSIVE_ITEMS"]) - PROGRESSION_BY_ROOM.update(pickdata["PROGRESSION_BY_ROOM"]) + PANEL_DOORS_BY_ROOM.update(pickdata["PANEL_DOORS_BY_ROOM"]) + PROGRESSIVE_ITEMS.update(pickdata["PROGRESSIVE_ITEMS"]) + PROGRESSIVE_DOORS_BY_ROOM.update(pickdata["PROGRESSIVE_DOORS_BY_ROOM"]) + PROGRESSIVE_PANELS_BY_ROOM.update(pickdata["PROGRESSIVE_PANELS_BY_ROOM"]) PAINTING_ENTRANCES = pickdata["PAINTING_ENTRANCES"] PAINTING_EXIT_ROOMS.update(pickdata["PAINTING_EXIT_ROOMS"]) PAINTING_EXITS = pickdata["PAINTING_EXITS"] @@ -111,6 +131,8 @@ def find_class(self, module, name): DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"]) DOOR_ITEM_IDS.update(pickdata["DOOR_ITEM_IDS"]) DOOR_GROUP_ITEM_IDS.update(pickdata["DOOR_GROUP_ITEM_IDS"]) + PANEL_DOOR_ITEM_IDS.update(pickdata["PANEL_DOOR_ITEM_IDS"]) + PANEL_GROUP_ITEM_IDS.update(pickdata["PANEL_GROUP_ITEM_IDS"]) PROGRESSIVE_ITEM_IDS.update(pickdata["PROGRESSIVE_ITEM_IDS"]) diff --git a/worlds/lingo/test/TestDoors.py b/worlds/lingo/test/TestDoors.py index f496c5f5785..cfbd7f30278 100644 --- a/worlds/lingo/test/TestDoors.py +++ b/worlds/lingo/test/TestDoors.py @@ -3,7 +3,7 @@ class TestRequiredRoomLogic(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "shuffle_colors": "false", } @@ -50,7 +50,7 @@ def test_hidden_first(self) -> None: class TestRequiredDoorLogic(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "shuffle_colors": "false", } @@ -78,7 +78,8 @@ def test_through_hidden(self) -> None: class TestSimpleDoors(LingoTestBase): options = { - "shuffle_doors": "simple", + "shuffle_doors": "doors", + "group_doors": "true", "shuffle_colors": "false", } @@ -90,3 +91,52 @@ def test_requirement(self): self.assertTrue(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + +class TestPanels(LingoTestBase): + options = { + "shuffle_doors": "panels" + } + + def test_requirement(self): + self.assertFalse(self.can_reach_location("Starting Room - HIDDEN")) + self.assertFalse(self.can_reach_location("Hidden Room - OPEN")) + self.assertFalse(self.can_reach_location("The Seeker - Achievement")) + + self.collect_by_name("Starting Room - HIDDEN (Panel)") + self.assertTrue(self.can_reach_location("Starting Room - HIDDEN")) + self.assertFalse(self.can_reach_location("Hidden Room - OPEN")) + self.assertFalse(self.can_reach_location("The Seeker - Achievement")) + + self.collect_by_name("Hidden Room - OPEN (Panel)") + self.assertTrue(self.can_reach_location("Starting Room - HIDDEN")) + self.assertTrue(self.can_reach_location("Hidden Room - OPEN")) + self.assertTrue(self.can_reach_location("The Seeker - Achievement")) + + +class TestGroupedPanels(LingoTestBase): + options = { + "shuffle_doors": "panels", + "group_doors": "true", + "shuffle_colors": "false", + } + + def test_requirement(self): + self.assertFalse(self.can_reach_location("Hub Room - SLAUGHTER")) + self.assertFalse(self.can_reach_location("Dread Hallway - DREAD")) + self.assertFalse(self.can_reach_location("The Tenacious - Achievement")) + + self.collect_by_name("Tenacious Entrance Panels") + self.assertTrue(self.can_reach_location("Hub Room - SLAUGHTER")) + self.assertFalse(self.can_reach_location("Dread Hallway - DREAD")) + self.assertFalse(self.can_reach_location("The Tenacious - Achievement")) + + self.collect_by_name("Outside The Agreeable - BLACK (Panel)") + self.assertTrue(self.can_reach_location("Hub Room - SLAUGHTER")) + self.assertTrue(self.can_reach_location("Dread Hallway - DREAD")) + self.assertFalse(self.can_reach_location("The Tenacious - Achievement")) + + self.collect_by_name("The Tenacious - Black Palindromes (Panels)") + self.assertTrue(self.can_reach_location("Hub Room - SLAUGHTER")) + self.assertTrue(self.can_reach_location("Dread Hallway - DREAD")) + self.assertTrue(self.can_reach_location("The Tenacious - Achievement")) + diff --git a/worlds/lingo/test/TestMastery.py b/worlds/lingo/test/TestMastery.py index 3ebe40aa22d..c9c79a9d065 100644 --- a/worlds/lingo/test/TestMastery.py +++ b/worlds/lingo/test/TestMastery.py @@ -5,7 +5,8 @@ class TestMasteryWhenVictoryIsTheEnd(LingoTestBase): options = { "mastery_achievements": "22", "victory_condition": "the_end", - "shuffle_colors": "true" + "shuffle_colors": "true", + "shuffle_postgame": "true", } def test_requirement(self): @@ -43,7 +44,8 @@ class TestMasteryBlocksDependents(LingoTestBase): options = { "mastery_achievements": "24", "shuffle_colors": "true", - "location_checks": "insanity" + "location_checks": "insanity", + "victory_condition": "level_2", } def test_requirement(self): diff --git a/worlds/lingo/test/TestOptions.py b/worlds/lingo/test/TestOptions.py index fce07431163..bd8ed81d7a1 100644 --- a/worlds/lingo/test/TestOptions.py +++ b/worlds/lingo/test/TestOptions.py @@ -3,7 +3,7 @@ class TestMultiShuffleOptions(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "progressive_orange_tower": "true", "shuffle_colors": "true", "shuffle_paintings": "true", @@ -13,7 +13,7 @@ class TestMultiShuffleOptions(LingoTestBase): class TestPanelsanity(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "progressive_orange_tower": "true", "location_checks": "insanity", "shuffle_colors": "true" @@ -22,7 +22,18 @@ class TestPanelsanity(LingoTestBase): class TestAllPanelHunt(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", + "progressive_orange_tower": "true", + "shuffle_colors": "true", + "victory_condition": "level_2", + "level_2_requirement": "800", + "early_color_hallways": "true" + } + + +class TestAllPanelHuntPanelsMode(LingoTestBase): + options = { + "shuffle_doors": "panels", "progressive_orange_tower": "true", "shuffle_colors": "true", "victory_condition": "level_2", diff --git a/worlds/lingo/test/TestOrangeTower.py b/worlds/lingo/test/TestOrangeTower.py index 7b0c3bb5251..444264a5896 100644 --- a/worlds/lingo/test/TestOrangeTower.py +++ b/worlds/lingo/test/TestOrangeTower.py @@ -3,7 +3,7 @@ class TestProgressiveOrangeTower(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "progressive_orange_tower": "true" } diff --git a/worlds/lingo/test/TestPanelsanity.py b/worlds/lingo/test/TestPanelsanity.py index 34c1b3815a4..f8330ae7820 100644 --- a/worlds/lingo/test/TestPanelsanity.py +++ b/worlds/lingo/test/TestPanelsanity.py @@ -3,7 +3,7 @@ class TestPanelHunt(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "location_checks": "insanity", "victory_condition": "level_2", "level_2_requirement": "15" diff --git a/worlds/lingo/test/TestPilgrimage.py b/worlds/lingo/test/TestPilgrimage.py index 3cc91940017..328156da2d1 100644 --- a/worlds/lingo/test/TestPilgrimage.py +++ b/worlds/lingo/test/TestPilgrimage.py @@ -18,7 +18,7 @@ class TestPilgrimageWithRoofAndPaintings(LingoTestBase): options = { "enable_pilgrimage": "true", "shuffle_colors": "false", - "shuffle_doors": "complex", + "shuffle_doors": "doors", "pilgrimage_allows_roof_access": "true", "pilgrimage_allows_paintings": "true", "early_color_hallways": "false" @@ -29,7 +29,6 @@ def test_access(self): "Outside The Undeterred - Green Painting"] for door in doors: - print(door) self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) self.collect_by_name(door) @@ -40,7 +39,7 @@ class TestPilgrimageNoRoofYesPaintings(LingoTestBase): options = { "enable_pilgrimage": "true", "shuffle_colors": "false", - "shuffle_doors": "complex", + "shuffle_doors": "doors", "pilgrimage_allows_roof_access": "false", "pilgrimage_allows_paintings": "true", "early_color_hallways": "false" @@ -53,7 +52,6 @@ def test_access(self): "Starting Room - Street Painting"] for door in doors: - print(door) self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) self.collect_by_name(door) @@ -64,7 +62,7 @@ class TestPilgrimageNoRoofNoPaintings(LingoTestBase): options = { "enable_pilgrimage": "true", "shuffle_colors": "false", - "shuffle_doors": "complex", + "shuffle_doors": "doors", "pilgrimage_allows_roof_access": "false", "pilgrimage_allows_paintings": "false", "early_color_hallways": "false" @@ -81,18 +79,45 @@ def test_access(self): "Orange Tower Fourth Floor - Hot Crusts Door"] for door in doors: - print(door) self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) self.collect_by_name(door) self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) -class TestPilgrimageYesRoofNoPaintings(LingoTestBase): +class TestPilgrimageRequireStartingRoom(LingoTestBase): options = { "enable_pilgrimage": "true", "shuffle_colors": "false", "shuffle_doors": "complex", + "pilgrimage_allows_roof_access": "false", + "pilgrimage_allows_paintings": "false", + "early_color_hallways": "false" + } + + def test_access(self): + doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance", + "Outside The Undeterred - Green Painting", "Outside The Undeterred - Number Hunt", + "Starting Room - Street Painting", "Outside The Initiated - Shortcut to Hub Room", + "Directional Gallery - Shortcut to The Undeterred", "Orange Tower First Floor - Salt Pepper Door", + "Color Hunt - Shortcut to The Steady", "The Bearer - Entrance", + "Orange Tower Fifth Floor - Quadruple Intersection", "The Tenacious - Shortcut to Hub Room", + "Outside The Agreeable - Tenacious Entrance", "Crossroads - Tower Entrance", + "Orange Tower Fourth Floor - Hot Crusts Door", "Challenge Room - Welcome Door", + "Number Hunt - Challenge Entrance", "Welcome Back Area - Shortcut to Starting Room"] + + for door in doors: + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect_by_name(door) + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestPilgrimageYesRoofNoPaintings(LingoTestBase): + options = { + "enable_pilgrimage": "true", + "shuffle_colors": "false", + "shuffle_doors": "doors", "pilgrimage_allows_roof_access": "true", "pilgrimage_allows_paintings": "false", "early_color_hallways": "false" @@ -107,7 +132,6 @@ def test_access(self): "Orange Tower Fifth Floor - Quadruple Intersection"] for door in doors: - print(door) self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) self.collect_by_name(door) diff --git a/worlds/lingo/test/TestPostgame.py b/worlds/lingo/test/TestPostgame.py new file mode 100644 index 00000000000..d2e2232ff76 --- /dev/null +++ b/worlds/lingo/test/TestPostgame.py @@ -0,0 +1,62 @@ +from . import LingoTestBase + + +class TestPostgameVanillaTheEnd(LingoTestBase): + options = { + "shuffle_doors": "none", + "victory_condition": "the_end", + "shuffle_postgame": "false", + } + + def test_requirement(self): + location_names = [location.name for location in self.multiworld.get_locations(self.player)] + + self.assertTrue("The End (Solved)" in location_names) + self.assertTrue("Champion's Rest - YOU" in location_names) + self.assertFalse("Orange Tower Seventh Floor - THE MASTER" in location_names) + self.assertFalse("The Red - Achievement" in location_names) + + +class TestPostgameComplexDoorsTheEnd(LingoTestBase): + options = { + "shuffle_doors": "complex", + "victory_condition": "the_end", + "shuffle_postgame": "false", + } + + def test_requirement(self): + location_names = [location.name for location in self.multiworld.get_locations(self.player)] + + self.assertTrue("The End (Solved)" in location_names) + self.assertFalse("Orange Tower Seventh Floor - THE MASTER" in location_names) + self.assertTrue("The Red - Achievement" in location_names) + + +class TestPostgameLateColorHunt(LingoTestBase): + options = { + "shuffle_doors": "none", + "victory_condition": "the_end", + "sunwarp_access": "disabled", + "shuffle_postgame": "false", + } + + def test_requirement(self): + location_names = [location.name for location in self.multiworld.get_locations(self.player)] + + self.assertFalse("Champion's Rest - YOU" in location_names) + + +class TestPostgameVanillaTheMaster(LingoTestBase): + options = { + "shuffle_doors": "none", + "victory_condition": "the_master", + "shuffle_postgame": "false", + } + + def test_requirement(self): + location_names = [location.name for location in self.multiworld.get_locations(self.player)] + + self.assertTrue("Orange Tower Seventh Floor - THE END" in location_names) + self.assertTrue("Orange Tower Seventh Floor - Mastery Achievements" in location_names) + self.assertTrue("The Red - Achievement" in location_names) + self.assertFalse("Mastery Panels" in location_names) diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index e79fd6bc908..2c837f53f34 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -3,7 +3,7 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase): options = { - "shuffle_doors": "complex" + "shuffle_doors": "doors" } def test_item(self): @@ -54,7 +54,8 @@ def test_item(self): class TestSimpleHallwayRoom(LingoTestBase): options = { - "shuffle_doors": "simple" + "shuffle_doors": "doors", + "group_doors": "true", } def test_item(self): @@ -81,7 +82,7 @@ def test_item(self): class TestProgressiveArtGallery(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", "shuffle_colors": "false", } diff --git a/worlds/lingo/test/TestSunwarps.py b/worlds/lingo/test/TestSunwarps.py index e8e913c4f49..66ba3afd6e9 100644 --- a/worlds/lingo/test/TestSunwarps.py +++ b/worlds/lingo/test/TestSunwarps.py @@ -19,7 +19,8 @@ def test_access(self): class TestSimpleDoorsNormalSunwarps(LingoTestBase): options = { - "shuffle_doors": "simple", + "shuffle_doors": "doors", + "group_doors": "true", "sunwarp_access": "normal" } @@ -37,7 +38,8 @@ def test_access(self): class TestSimpleDoorsDisabledSunwarps(LingoTestBase): options = { - "shuffle_doors": "simple", + "shuffle_doors": "doors", + "group_doors": "true", "sunwarp_access": "disabled" } @@ -56,7 +58,8 @@ def test_access(self): class TestSimpleDoorsUnlockSunwarps(LingoTestBase): options = { - "shuffle_doors": "simple", + "shuffle_doors": "doors", + "group_doors": "true", "sunwarp_access": "unlock" } @@ -78,7 +81,8 @@ def test_access(self): class TestComplexDoorsNormalSunwarps(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", + "group_doors": "false", "sunwarp_access": "normal" } @@ -96,7 +100,8 @@ def test_access(self): class TestComplexDoorsDisabledSunwarps(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", + "group_doors": "false", "sunwarp_access": "disabled" } @@ -115,7 +120,8 @@ def test_access(self): class TestComplexDoorsIndividualSunwarps(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", + "group_doors": "false", "sunwarp_access": "individual" } @@ -142,7 +148,8 @@ def test_access(self): class TestComplexDoorsProgressiveSunwarps(LingoTestBase): options = { - "shuffle_doors": "complex", + "shuffle_doors": "doors", + "group_doors": "false", "sunwarp_access": "progressive" } diff --git a/worlds/lingo/utils/assign_ids.rb b/worlds/lingo/utils/assign_ids.rb index 9e1ce67bd2d..f7de3d03f58 100644 --- a/worlds/lingo/utils/assign_ids.rb +++ b/worlds/lingo/utils/assign_ids.rb @@ -73,6 +73,22 @@ end end end +if old_generated.include? "panel_doors" then + old_generated["panel_doors"].each do |room, panel_doors| + panel_doors.each do |name, id| + if id >= next_item_id then + next_item_id = id + 1 + end + end + end +end +if old_generated.include? "panel_groups" then + old_generated["panel_groups"].each do |name, id| + if id >= next_item_id then + next_item_id = id + 1 + end + end +end if old_generated.include? "progression" then old_generated["progression"].each do |name, id| if id >= next_item_id then @@ -82,6 +98,7 @@ end door_groups = Set[] +panel_groups = Set[] config = YAML.load_file(configpath) config.each do |room_name, room_data| @@ -163,6 +180,29 @@ end end + if room_data.include? "panel_doors" + room_data["panel_doors"].each do |panel_door_name, panel_door| + unless old_generated.include? "panel_doors" and old_generated["panel_doors"].include? room_name and old_generated["panel_doors"][room_name].include? panel_door_name then + old_generated["panel_doors"] ||= {} + old_generated["panel_doors"][room_name] ||= {} + old_generated["panel_doors"][room_name][panel_door_name] = next_item_id + + next_item_id += 1 + end + + if panel_door.include? "panel_group" and not panel_groups.include? panel_door["panel_group"] then + panel_groups.add(panel_door["panel_group"]) + + unless old_generated.include? "panel_groups" and old_generated["panel_groups"].include? panel_door["panel_group"] then + old_generated["panel_groups"] ||= {} + old_generated["panel_groups"][panel_door["panel_group"]] = next_item_id + + next_item_id += 1 + end + end + end + end + if room_data.include? "progression" room_data["progression"].each do |progression_name, pdata| unless old_generated.include? "progression" and old_generated["progression"].include? progression_name then diff --git a/worlds/lingo/utils/pickle_static_data.py b/worlds/lingo/utils/pickle_static_data.py index e40c21ce3e6..92bcb7a859e 100644 --- a/worlds/lingo/utils/pickle_static_data.py +++ b/worlds/lingo/utils/pickle_static_data.py @@ -6,8 +6,8 @@ sys.path.append(os.path.join("worlds", "lingo")) sys.path.append(".") sys.path.append("..") -from datatypes import Door, DoorType, EntranceType, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel,\ - RoomEntrance +from datatypes import Door, DoorType, EntranceType, Painting, Panel, PanelDoor, Progression, Room, RoomAndDoor,\ + RoomAndPanel, RoomAndPanelDoor, RoomEntrance import hashlib import pickle @@ -18,10 +18,12 @@ ALL_ROOMS: List[Room] = [] DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {} PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {} +PANEL_DOORS_BY_ROOM: Dict[str, Dict[str, PanelDoor]] = {} PAINTINGS: Dict[str, Painting] = {} -PROGRESSIVE_ITEMS: List[str] = [] -PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {} +PROGRESSIVE_ITEMS: Set[str] = set() +PROGRESSIVE_DOORS_BY_ROOM: Dict[str, Dict[str, Progression]] = {} +PROGRESSIVE_PANELS_BY_ROOM: Dict[str, Dict[str, Progression]] = {} PAINTING_ENTRANCES: int = 0 PAINTING_EXIT_ROOMS: Set[str] = set() @@ -37,8 +39,13 @@ DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} DOOR_GROUP_ITEM_IDS: Dict[str, int] = {} +PANEL_DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {} +PANEL_GROUP_ITEM_IDS: Dict[str, int] = {} PROGRESSIVE_ITEM_IDS: Dict[str, int] = {} +# This doesn't need to be stored in the datafile. +PANEL_DOOR_BY_PANEL_BY_ROOM: Dict[str, Dict[str, str]] = {} + def hash_file(path): md5 = hashlib.md5() @@ -53,7 +60,7 @@ def hash_file(path): def load_static_data(ll1_path, ids_path): global PAINTING_EXITS, SPECIAL_ITEM_IDS, PANEL_LOCATION_IDS, DOOR_LOCATION_IDS, DOOR_ITEM_IDS, \ - DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS + DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS, PANEL_DOOR_ITEM_IDS, PANEL_GROUP_ITEM_IDS # Load in all item and location IDs. These are broken up into groups based on the type of item/location. with open(ids_path, "r") as file: @@ -86,6 +93,17 @@ def load_static_data(ll1_path, ids_path): for item_name, item_id in config["door_groups"].items(): DOOR_GROUP_ITEM_IDS[item_name] = item_id + if "panel_doors" in config: + for room_name, panel_doors in config["panel_doors"].items(): + PANEL_DOOR_ITEM_IDS[room_name] = {} + + for panel_door, item_id in panel_doors.items(): + PANEL_DOOR_ITEM_IDS[room_name][panel_door] = item_id + + if "panel_groups" in config: + for item_name, item_id in config["panel_groups"].items(): + PANEL_GROUP_ITEM_IDS[item_name] = item_id + if "progression" in config: for item_name, item_id in config["progression"].items(): PROGRESSIVE_ITEM_IDS[item_name] = item_id @@ -147,6 +165,46 @@ def process_entrance(source_room, doors, room_obj): room_obj.entrances.append(RoomEntrance(source_room, door, entrance_type)) +def process_panel_door(room_name, panel_door_name, panel_door_data): + global PANEL_DOORS_BY_ROOM, PANEL_DOOR_BY_PANEL_BY_ROOM + + panels: List[RoomAndPanel] = list() + for panel in panel_door_data["panels"]: + if isinstance(panel, dict): + panels.append(RoomAndPanel(panel["room"], panel["panel"])) + else: + panels.append(RoomAndPanel(room_name, panel)) + + for panel in panels: + PANEL_DOOR_BY_PANEL_BY_ROOM.setdefault(panel.room, {})[panel.panel] = RoomAndPanelDoor(room_name, + panel_door_name) + + if "item_name" in panel_door_data: + item_name = panel_door_data["item_name"] + else: + panel_per_room = dict() + for panel in panels: + panel_room_name = room_name if panel.room is None else panel.room + panel_per_room.setdefault(panel_room_name, []).append(panel.panel) + + room_strs = list() + for door_room_str, door_panels_str in panel_per_room.items(): + room_strs.append(door_room_str + " - " + ", ".join(door_panels_str)) + + if len(panels) == 1: + item_name = f"{room_strs[0]} (Panel)" + else: + item_name = " and ".join(room_strs) + " (Panels)" + + if "panel_group" in panel_door_data: + panel_group = panel_door_data["panel_group"] + else: + panel_group = None + + panel_door_obj = PanelDoor(item_name, panel_group) + PANEL_DOORS_BY_ROOM[room_name][panel_door_name] = panel_door_obj + + def process_panel(room_name, panel_name, panel_data): global PANELS_BY_ROOM @@ -227,13 +285,18 @@ def process_panel(room_name, panel_name, panel_data): else: non_counting = False + if room_name in PANEL_DOOR_BY_PANEL_BY_ROOM and panel_name in PANEL_DOOR_BY_PANEL_BY_ROOM[room_name]: + panel_door = PANEL_DOOR_BY_PANEL_BY_ROOM[room_name][panel_name] + else: + panel_door = None + if "location_name" in panel_data: location_name = panel_data["location_name"] else: location_name = None panel_obj = Panel(required_rooms, required_doors, required_panels, colors, check, event, exclude_reduce, - achievement, non_counting, location_name) + achievement, non_counting, panel_door, location_name) PANELS_BY_ROOM[room_name][panel_name] = panel_obj @@ -325,7 +388,7 @@ def process_door(room_name, door_name, door_data): painting_ids = [] door_type = DoorType.NORMAL - if door_name.endswith(" Sunwarp"): + if room_name == "Sunwarps": door_type = DoorType.SUNWARP elif room_name == "Pilgrim Antechamber" and door_name == "Sun Painting": door_type = DoorType.SUN_PAINTING @@ -404,11 +467,11 @@ def process_sunwarp(room_name, sunwarp_data): SUNWARP_EXITS[sunwarp_data["dots"] - 1] = room_name -def process_progression(room_name, progression_name, progression_doors): - global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM +def process_progressive_door(room_name, progression_name, progression_doors): + global PROGRESSIVE_ITEMS, PROGRESSIVE_DOORS_BY_ROOM # Progressive items are configured as a list of doors. - PROGRESSIVE_ITEMS.append(progression_name) + PROGRESSIVE_ITEMS.add(progression_name) progression_index = 1 for door in progression_doors: @@ -419,11 +482,31 @@ def process_progression(room_name, progression_name, progression_doors): door_room = room_name door_door = door - room_progressions = PROGRESSION_BY_ROOM.setdefault(door_room, {}) + room_progressions = PROGRESSIVE_DOORS_BY_ROOM.setdefault(door_room, {}) room_progressions[door_door] = Progression(progression_name, progression_index) progression_index += 1 +def process_progressive_panel(room_name, progression_name, progression_panel_doors): + global PROGRESSIVE_ITEMS, PROGRESSIVE_PANELS_BY_ROOM + + # Progressive items are configured as a list of panel doors. + PROGRESSIVE_ITEMS.add(progression_name) + + progression_index = 1 + for panel_door in progression_panel_doors: + if isinstance(panel_door, Dict): + panel_door_room = panel_door["room"] + panel_door_door = panel_door["panel_door"] + else: + panel_door_room = room_name + panel_door_door = panel_door + + room_progressions = PROGRESSIVE_PANELS_BY_ROOM.setdefault(panel_door_room, {}) + room_progressions[panel_door_door] = Progression(progression_name, progression_index) + progression_index += 1 + + def process_room(room_name, room_data): global ALL_ROOMS @@ -433,6 +516,12 @@ def process_room(room_name, room_data): for source_room, doors in room_data["entrances"].items(): process_entrance(source_room, doors, room_obj) + if "panel_doors" in room_data: + PANEL_DOORS_BY_ROOM[room_name] = dict() + + for panel_door_name, panel_door_data in room_data["panel_doors"].items(): + process_panel_door(room_name, panel_door_name, panel_door_data) + if "panels" in room_data: PANELS_BY_ROOM[room_name] = dict() @@ -454,8 +543,11 @@ def process_room(room_name, room_data): process_sunwarp(room_name, sunwarp_data) if "progression" in room_data: - for progression_name, progression_doors in room_data["progression"].items(): - process_progression(room_name, progression_name, progression_doors) + for progression_name, pdata in room_data["progression"].items(): + if "doors" in pdata: + process_progressive_door(room_name, progression_name, pdata["doors"]) + if "panel_doors" in pdata: + process_progressive_panel(room_name, progression_name, pdata["panel_doors"]) ALL_ROOMS.append(room_obj) @@ -492,8 +584,10 @@ def process_room(room_name, room_data): "ALL_ROOMS": ALL_ROOMS, "DOORS_BY_ROOM": DOORS_BY_ROOM, "PANELS_BY_ROOM": PANELS_BY_ROOM, + "PANEL_DOORS_BY_ROOM": PANEL_DOORS_BY_ROOM, "PROGRESSIVE_ITEMS": PROGRESSIVE_ITEMS, - "PROGRESSION_BY_ROOM": PROGRESSION_BY_ROOM, + "PROGRESSIVE_DOORS_BY_ROOM": PROGRESSIVE_DOORS_BY_ROOM, + "PROGRESSIVE_PANELS_BY_ROOM": PROGRESSIVE_PANELS_BY_ROOM, "PAINTING_ENTRANCES": PAINTING_ENTRANCES, "PAINTING_EXIT_ROOMS": PAINTING_EXIT_ROOMS, "PAINTING_EXITS": PAINTING_EXITS, @@ -506,6 +600,8 @@ def process_room(room_name, room_data): "DOOR_LOCATION_IDS": DOOR_LOCATION_IDS, "DOOR_ITEM_IDS": DOOR_ITEM_IDS, "DOOR_GROUP_ITEM_IDS": DOOR_GROUP_ITEM_IDS, + "PANEL_DOOR_ITEM_IDS": PANEL_DOOR_ITEM_IDS, + "PANEL_GROUP_ITEM_IDS": PANEL_GROUP_ITEM_IDS, "PROGRESSIVE_ITEM_IDS": PROGRESSIVE_ITEM_IDS, } diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb index 498980bb719..70f7fc2cf65 100644 --- a/worlds/lingo/utils/validate_config.rb +++ b/worlds/lingo/utils/validate_config.rb @@ -33,19 +33,23 @@ configured_rooms = Set["Menu"] configured_doors = Set[] configured_panels = Set[] +configured_panel_doors = Set[] mentioned_rooms = Set[] mentioned_doors = Set[] mentioned_panels = Set[] +mentioned_panel_doors = Set[] mentioned_sunwarp_entrances = Set[] mentioned_sunwarp_exits = Set[] mentioned_paintings = Set[] door_groups = {} +panel_groups = {} -directives = Set["entrances", "panels", "doors", "paintings", "sunwarps", "progression"] +directives = Set["entrances", "panels", "doors", "panel_doors", "paintings", "sunwarps", "progression"] panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt", "location_name"] door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"] +panel_door_directives = Set["panels", "item_name", "panel_group"] painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] non_counting = 0 @@ -253,6 +257,43 @@ end end + (room["panel_doors"] || {}).each do |panel_door_name, panel_door| + configured_panel_doors.add("#{room_name} - #{panel_door_name}") + + if panel_door.include?("panels") + panel_door["panels"].each do |panel| + if panel.kind_of? Hash then + other_room = panel.include?("room") ? panel["room"] : room_name + mentioned_panels.add("#{other_room} - #{panel["panel"]}") + else + other_room = panel.include?("room") ? panel["room"] : room_name + mentioned_panels.add("#{room_name} - #{panel}") + end + end + else + puts "#{room_name} - #{panel_door_name} :::: Missing panels field" + end + + if panel_door.include?("panel_group") + panel_groups[panel_door["panel_group"]] ||= 0 + panel_groups[panel_door["panel_group"]] += 1 + end + + bad_subdirectives = [] + panel_door.keys.each do |key| + unless panel_door_directives.include?(key) then + bad_subdirectives << key + end + end + unless bad_subdirectives.empty? then + puts "#{room_name} - #{panel_door_name} :::: Panel door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" + end + + unless ids.include?("panel_doors") and ids["panel_doors"].include?(room_name) and ids["panel_doors"][room_name].include?(panel_door_name) + puts "#{room_name} - #{panel_door_name} :::: Panel door is missing an item ID" + end + end + (room["paintings"] || []).each do |painting| if painting.include?("id") and painting["id"].kind_of? String then unless paintings.include? painting["id"] then @@ -327,12 +368,24 @@ end end - (room["progression"] || {}).each do |progression_name, door_list| - door_list.each do |door| - if door.kind_of? Hash then - mentioned_doors.add("#{door["room"]} - #{door["door"]}") - else - mentioned_doors.add("#{room_name} - #{door}") + (room["progression"] || {}).each do |progression_name, pdata| + if pdata.include? "doors" then + pdata["doors"].each do |door| + if door.kind_of? Hash then + mentioned_doors.add("#{door["room"]} - #{door["door"]}") + else + mentioned_doors.add("#{room_name} - #{door}") + end + end + end + + if pdata.include? "panel_doors" then + pdata["panel_doors"].each do |panel_door| + if panel_door.kind_of? Hash then + mentioned_panel_doors.add("#{panel_door["room"]} - #{panel_door["panel_door"]}") + else + mentioned_panel_doors.add("#{room_name} - #{panel_door}") + end end end @@ -344,17 +397,22 @@ errored_rooms = mentioned_rooms - configured_rooms unless errored_rooms.empty? then - puts "The folloring rooms are mentioned but do not exist: " + errored_rooms.to_s + puts "The following rooms are mentioned but do not exist: " + errored_rooms.to_s end errored_panels = mentioned_panels - configured_panels unless errored_panels.empty? then - puts "The folloring panels are mentioned but do not exist: " + errored_panels.to_s + puts "The following panels are mentioned but do not exist: " + errored_panels.to_s end errored_doors = mentioned_doors - configured_doors unless errored_doors.empty? then - puts "The folloring doors are mentioned but do not exist: " + errored_doors.to_s + puts "The following doors are mentioned but do not exist: " + errored_doors.to_s +end + +errored_panel_doors = mentioned_panel_doors - configured_panel_doors +unless errored_panel_doors.empty? then + puts "The following panel doors are mentioned but do not exist: " + errored_panel_doors.to_s end door_groups.each do |group,num| @@ -367,6 +425,16 @@ end end +panel_groups.each do |group,num| + if num == 1 then + puts "Panel group \"#{group}\" only has one panel in it" + end + + unless ids.include?("panel_groups") and ids["panel_groups"].include?(group) + puts "#{group} :::: Panel group is missing an item ID" + end +end + slashed_rooms = configured_rooms.select do |room| room.include? "/" end diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py index 1f76dba4894..59e694cd396 100644 --- a/worlds/messenger/options.py +++ b/worlds/messenger/options.py @@ -3,15 +3,15 @@ from schema import And, Optional, Or, Schema -from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, \ +from Options import Choice, DeathLinkMixin, DefaultOnToggle, ItemsAccessibility, OptionDict, PerGameCommonOptions, \ PlandoConnections, Range, StartInventoryPool, Toggle, Visibility from .portals import CHECKPOINTS, PORTALS, SHOP_POINTS -class MessengerAccessibility(Accessibility): - default = Accessibility.option_locations +class MessengerAccessibility(ItemsAccessibility): # defaulting to locations accessibility since items makes certain items self-locking - __doc__ = Accessibility.__doc__.replace(f"default {Accessibility.default}", f"default {default}") + default = ItemsAccessibility.option_full + __doc__ = ItemsAccessibility.__doc__ class PortalPlando(PlandoConnections): diff --git a/worlds/minecraft/docs/minecraft_es.md b/worlds/minecraft/docs/minecraft_es.md index 3f2df6e7ba7..4f489921224 100644 --- a/worlds/minecraft/docs/minecraft_es.md +++ b/worlds/minecraft/docs/minecraft_es.md @@ -29,7 +29,7 @@ name: TuNombre game: Minecraft # Opciones compartidas por todos los juegos: -accessibility: locations +accessibility: full progression_balancing: 50 # Opciones Especficicas para Minecraft diff --git a/worlds/minecraft/docs/minecraft_sv.md b/worlds/minecraft/docs/minecraft_sv.md index fd89d681ee3..ab8c1b5d8ea 100644 --- a/worlds/minecraft/docs/minecraft_sv.md +++ b/worlds/minecraft/docs/minecraft_sv.md @@ -79,7 +79,7 @@ description: Template Name # Ditt spelnamn. Mellanslag kommer bli omplacerad med understräck och det är en 16-karaktärsgräns. name: YourName game: Minecraft -accessibility: locations +accessibility: full progression_balancing: 0 advancement_goal: few: 0 diff --git a/worlds/osrs/Items.py b/worlds/osrs/Items.py new file mode 100644 index 00000000000..0679c964e77 --- /dev/null +++ b/worlds/osrs/Items.py @@ -0,0 +1,85 @@ +import typing + +from BaseClasses import Item, ItemClassification +from .Names import ItemNames + + +class ItemRow(typing.NamedTuple): + name: str + amount: int + progression: ItemClassification + + +class OSRSItem(Item): + game: str = "Old School Runescape" + + +QP_Items: typing.List[str] = [ + ItemNames.QP_Cooks_Assistant, + ItemNames.QP_Demon_Slayer, + ItemNames.QP_Restless_Ghost, + ItemNames.QP_Romeo_Juliet, + ItemNames.QP_Sheep_Shearer, + ItemNames.QP_Shield_of_Arrav, + ItemNames.QP_Ernest_the_Chicken, + ItemNames.QP_Vampyre_Slayer, + ItemNames.QP_Imp_Catcher, + ItemNames.QP_Prince_Ali_Rescue, + ItemNames.QP_Dorics_Quest, + ItemNames.QP_Black_Knights_Fortress, + ItemNames.QP_Witchs_Potion, + ItemNames.QP_Knights_Sword, + ItemNames.QP_Goblin_Diplomacy, + ItemNames.QP_Pirates_Treasure, + ItemNames.QP_Rune_Mysteries, + ItemNames.QP_Misthalin_Mystery, + ItemNames.QP_Corsair_Curse, + ItemNames.QP_X_Marks_the_Spot, + ItemNames.QP_Below_Ice_Mountain +] + +starting_area_dict: typing.Dict[int, str] = { + 0: ItemNames.Lumbridge, + 1: ItemNames.Al_Kharid, + 2: ItemNames.Central_Varrock, + 3: ItemNames.West_Varrock, + 4: ItemNames.Edgeville, + 5: ItemNames.Falador, + 6: ItemNames.Draynor_Village, + 7: ItemNames.Wilderness, +} + +chunksanity_starting_chunks: typing.List[str] = [ + ItemNames.Lumbridge, + ItemNames.Lumbridge_Swamp, + ItemNames.Lumbridge_Farms, + ItemNames.HAM_Hideout, + ItemNames.Draynor_Village, + ItemNames.Draynor_Manor, + ItemNames.Wizards_Tower, + ItemNames.Al_Kharid, + ItemNames.Citharede_Abbey, + ItemNames.South_Of_Varrock, + ItemNames.Central_Varrock, + ItemNames.Varrock_Palace, + ItemNames.East_Of_Varrock, + ItemNames.West_Varrock, + ItemNames.Edgeville, + ItemNames.Barbarian_Village, + ItemNames.Monastery, + ItemNames.Ice_Mountain, + ItemNames.Dwarven_Mines, + ItemNames.Falador, + ItemNames.Falador_Farm, + ItemNames.Crafting_Guild, + ItemNames.Rimmington, + ItemNames.Port_Sarim, + ItemNames.Mudskipper_Point, + ItemNames.Wilderness +] + +# Some starting areas contain multiple regions, so if that area is rolled for Chunksanity, we need to map it to one +chunksanity_special_region_names: typing.Dict[str, str] = { + ItemNames.Lumbridge_Farms: 'Lumbridge Farms East', + ItemNames.Crafting_Guild: 'Crafting Guild Outskirts', +} diff --git a/worlds/osrs/Locations.py b/worlds/osrs/Locations.py new file mode 100644 index 00000000000..b5827d60f2f --- /dev/null +++ b/worlds/osrs/Locations.py @@ -0,0 +1,21 @@ +import typing + +from BaseClasses import Location + + +class SkillRequirement(typing.NamedTuple): + skill: str + level: int + + +class LocationRow(typing.NamedTuple): + name: str + category: str + regions: typing.List[str] + skills: typing.List[SkillRequirement] + items: typing.List[str] + qp: int + + +class OSRSLocation(Location): + game: str = "Old School Runescape" diff --git a/worlds/osrs/LogicCSV/LogicCSVToPython.py b/worlds/osrs/LogicCSV/LogicCSVToPython.py new file mode 100644 index 00000000000..ed8bd8172a0 --- /dev/null +++ b/worlds/osrs/LogicCSV/LogicCSVToPython.py @@ -0,0 +1,144 @@ +""" +This is a utility file that converts logic in the form of CSV files into Python files that can be imported and used +directly by the world implementation. Whenever the logic files are updated, this script should be run to re-generate +the python files containing the data. +""" +import requests + +# The CSVs are updated at this repository to be shared between generator and client. +data_repository_address = "https://raw.githubusercontent.com/digiholic/osrs-archipelago-logic/" +# The Github tag of the CSVs this was generated with +data_csv_tag = "v1.5" + +if __name__ == "__main__": + import sys + import os + import csv + import typing + + # makes this module runnable from its world folder. Shamelessly stolen from Subnautica + 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) + + + def load_location_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "locations_generated.py"), 'w+') as locPyFile: + locPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + locPyFile.write("from ..Locations import LocationRow, SkillRequirement\n") + locPyFile.write("\n") + locPyFile.write("location_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/locations.csv") as req: + locations_reader = csv.reader(req.text.splitlines()) + for row in locations_reader: + row_line = "LocationRow(" + row_line += str_format(row[0]) + row_line += str_format(row[1].lower()) + + region_strings = row[2].split(", ") if row[2] else [] + row_line += f"{str_list_to_py(region_strings)}, " + + skill_strings = row[3].split(", ") + row_line += "[" + if skill_strings: + split_skills = [skill.split(" ") for skill in skill_strings if skill != ""] + if split_skills: + for split in split_skills: + row_line += f"SkillRequirement('{split[0]}', {split[1]}), " + row_line += "], " + + item_strings = row[4].split(", ") if row[4] else [] + row_line += f"{str_list_to_py(item_strings)}, " + row_line += f"{row[5]})" if row[5] != "" else "0)" + locPyFile.write(f"\t{row_line},\n") + locPyFile.write("]\n") + + def load_region_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "regions_generated.py"), 'w+') as regPyFile: + regPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + regPyFile.write("from ..Regions import RegionRow\n") + regPyFile.write("\n") + regPyFile.write("region_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/regions.csv") as req: + regions_reader = csv.reader(req.text.splitlines()) + for row in regions_reader: + row_line = "RegionRow(" + row_line += str_format(row[0]) + row_line += str_format(row[1]) + connections = row[2].replace("'", "\\'") + row_line += f"{str_list_to_py(connections.split(', '))}, " + resources = row[3].replace("'", "\\'") + row_line += f"{str_list_to_py(resources.split(', '))})" + regPyFile.write(f"\t{row_line},\n") + regPyFile.write("]\n") + + def load_resource_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "resources_generated.py"), 'w+') as resPyFile: + resPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + resPyFile.write("from ..Regions import ResourceRow\n") + resPyFile.write("\n") + resPyFile.write("resource_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/resources.csv") as req: + resource_reader = csv.reader(req.text.splitlines()) + for row in resource_reader: + name = row[0].replace("'", "\\'") + row_line = f"ResourceRow('{name}')" + resPyFile.write(f"\t{row_line},\n") + resPyFile.write("]\n") + + + def load_item_csv(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + with open(os.path.join(this_dir, "items_generated.py"), 'w+') as itemPyfile: + itemPyfile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n') + itemPyfile.write("from BaseClasses import ItemClassification\n") + itemPyfile.write("from ..Items import ItemRow\n") + itemPyfile.write("\n") + itemPyfile.write("item_rows = [\n") + + with requests.get(data_repository_address + "/" + data_csv_tag + "/items.csv") as req: + item_reader = csv.reader(req.text.splitlines()) + for row in item_reader: + row_line = "ItemRow(" + row_line += str_format(row[0]) + row_line += f"{row[1]}, " + + row_line += f"ItemClassification.{row[2]})" + + itemPyfile.write(f"\t{row_line},\n") + itemPyfile.write("]\n") + + + def str_format(s) -> str: + ret_str = s.replace("'", "\\'") + return f"'{ret_str}', " + + + def str_list_to_py(str_list) -> str: + ret_str = "[" + for s in str_list: + ret_str += f"'{s}', " + ret_str += "]" + return ret_str + + + + load_location_csv() + print("Generated locations py") + load_region_csv() + print("Generated regions py") + load_resource_csv() + print("Generated resource py") + load_item_csv() + print("Generated item py") diff --git a/worlds/osrs/LogicCSV/items_generated.py b/worlds/osrs/LogicCSV/items_generated.py new file mode 100644 index 00000000000..b5e610a6e3a --- /dev/null +++ b/worlds/osrs/LogicCSV/items_generated.py @@ -0,0 +1,43 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from BaseClasses import ItemClassification +from ..Items import ItemRow + +item_rows = [ + ItemRow('Area: Lumbridge', 1, ItemClassification.progression), + ItemRow('Area: Lumbridge Swamp', 1, ItemClassification.progression), + ItemRow('Area: HAM Hideout', 1, ItemClassification.progression), + ItemRow('Area: Lumbridge Farms', 1, ItemClassification.progression), + ItemRow('Area: South of Varrock', 1, ItemClassification.progression), + ItemRow('Area: East Varrock', 1, ItemClassification.progression), + ItemRow('Area: Central Varrock', 1, ItemClassification.progression), + ItemRow('Area: Varrock Palace', 1, ItemClassification.progression), + ItemRow('Area: West Varrock', 1, ItemClassification.progression), + ItemRow('Area: Edgeville', 1, ItemClassification.progression), + ItemRow('Area: Barbarian Village', 1, ItemClassification.progression), + ItemRow('Area: Draynor Manor', 1, ItemClassification.progression), + ItemRow('Area: Falador', 1, ItemClassification.progression), + ItemRow('Area: Dwarven Mines', 1, ItemClassification.progression), + ItemRow('Area: Ice Mountain', 1, ItemClassification.progression), + ItemRow('Area: Monastery', 1, ItemClassification.progression), + ItemRow('Area: Falador Farms', 1, ItemClassification.progression), + ItemRow('Area: Port Sarim', 1, ItemClassification.progression), + ItemRow('Area: Mudskipper Point', 1, ItemClassification.progression), + ItemRow('Area: Karamja', 1, ItemClassification.progression), + ItemRow('Area: Crandor', 1, ItemClassification.progression), + ItemRow('Area: Rimmington', 1, ItemClassification.progression), + ItemRow('Area: Crafting Guild', 1, ItemClassification.progression), + ItemRow('Area: Draynor Village', 1, ItemClassification.progression), + ItemRow('Area: Wizard Tower', 1, ItemClassification.progression), + ItemRow('Area: Corsair Cove', 1, ItemClassification.progression), + ItemRow('Area: Al Kharid', 1, ItemClassification.progression), + ItemRow('Area: Citharede Abbey', 1, ItemClassification.progression), + ItemRow('Area: Wilderness', 1, ItemClassification.progression), + ItemRow('Progressive Armor', 6, ItemClassification.progression), + ItemRow('Progressive Weapons', 6, ItemClassification.progression), + ItemRow('Progressive Tools', 6, ItemClassification.useful), + ItemRow('Progressive Ranged Weapons', 3, ItemClassification.useful), + ItemRow('Progressive Ranged Armor', 3, ItemClassification.useful), + ItemRow('Progressive Magic', 2, ItemClassification.useful), +] diff --git a/worlds/osrs/LogicCSV/locations_generated.py b/worlds/osrs/LogicCSV/locations_generated.py new file mode 100644 index 00000000000..073e505ad8f --- /dev/null +++ b/worlds/osrs/LogicCSV/locations_generated.py @@ -0,0 +1,127 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from ..Locations import LocationRow, SkillRequirement + +location_rows = [ + LocationRow('Quest: Cook\'s Assistant', 'quest', ['Lumbridge', 'Wheat', 'Windmill', 'Egg', 'Milk', ], [], [], 0), + LocationRow('Quest: Demon Slayer', 'quest', ['Central Varrock', 'Varrock Palace', 'Wizard Tower', 'South of Varrock', ], [], [], 0), + LocationRow('Quest: The Restless Ghost', 'quest', ['Lumbridge', 'Lumbridge Swamp', 'Wizard Tower', ], [], [], 0), + LocationRow('Quest: Romeo & Juliet', 'quest', ['Central Varrock', 'Varrock Palace', 'South of Varrock', 'West Varrock', ], [], [], 0), + LocationRow('Quest: Sheep Shearer', 'quest', ['Lumbridge Farms West', 'Spinning Wheel', ], [], [], 0), + LocationRow('Quest: Shield of Arrav', 'quest', ['Central Varrock', 'Varrock Palace', 'South of Varrock', 'West Varrock', ], [], [], 0), + LocationRow('Quest: Ernest the Chicken', 'quest', ['Draynor Manor', ], [], [], 0), + LocationRow('Quest: Vampyre Slayer', 'quest', ['Draynor Village', 'Central Varrock', 'Draynor Manor', ], [], [], 0), + LocationRow('Quest: Imp Catcher', 'quest', ['Wizard Tower', 'Imps', ], [], [], 0), + LocationRow('Quest: Prince Ali Rescue', 'quest', ['Al Kharid', 'Central Varrock', 'Bronze Ores', 'Clay Ore', 'Sheep', 'Spinning Wheel', 'Draynor Village', ], [], [], 0), + LocationRow('Quest: Doric\'s Quest', 'quest', ['Dwarven Mountain Pass', 'Clay Ore', 'Iron Ore', 'Bronze Ores', ], [SkillRequirement('Mining', 15), ], [], 0), + LocationRow('Quest: Black Knights\' Fortress', 'quest', ['Dwarven Mines', 'Falador', 'Monastery', 'Ice Mountain', 'Falador Farms', ], [], ['Progressive Armor', ], 12), + LocationRow('Quest: Witch\'s Potion', 'quest', ['Rimmington', 'Port Sarim', ], [], [], 0), + LocationRow('Quest: The Knight\'s Sword', 'quest', ['Falador', 'Varrock Palace', 'Mudskipper Point', 'South of Varrock', 'Windmill', 'Pie Dish', 'Port Sarim', ], [SkillRequirement('Cooking', 10), SkillRequirement('Mining', 10), ], [], 0), + LocationRow('Quest: Goblin Diplomacy', 'quest', ['Goblin Village', 'Draynor Village', 'Falador', 'South of Varrock', 'Onion', ], [], [], 0), + LocationRow('Quest: Pirate\'s Treasure', 'quest', ['Port Sarim', 'Karamja', 'Falador', ], [], [], 0), + LocationRow('Quest: Rune Mysteries', 'quest', ['Lumbridge', 'Wizard Tower', 'Central Varrock', ], [], [], 0), + LocationRow('Quest: Misthalin Mystery', 'quest', ['Lumbridge Swamp', ], [], [], 0), + LocationRow('Quest: The Corsair Curse', 'quest', ['Rimmington', 'Falador Farms', 'Corsair Cove', ], [], [], 0), + LocationRow('Quest: X Marks the Spot', 'quest', ['Lumbridge', 'Draynor Village', 'Port Sarim', ], [], [], 0), + LocationRow('Quest: Below Ice Mountain', 'quest', ['Dwarven Mines', 'Dwarven Mountain Pass', 'Ice Mountain', 'Barbarian Village', 'Falador', 'Central Varrock', 'Edgeville', ], [], [], 16), + LocationRow('Quest: Dragon Slayer', 'goal', ['Crandor', 'South of Varrock', 'Edgeville', 'Lumbridge', 'Rimmington', 'Monastery', 'Dwarven Mines', 'Port Sarim', 'Draynor Village', ], [], [], 32), + LocationRow('Activate the "Rock Skin" Prayer', 'prayer', [], [SkillRequirement('Prayer', 10), ], [], 0), + LocationRow('Activate the "Protect Item" Prayer', 'prayer', [], [SkillRequirement('Prayer', 25), ], [], 2), + LocationRow('Pray at the Edgeville Monastery', 'prayer', ['Monastery', ], [SkillRequirement('Prayer', 31), ], [], 6), + LocationRow('Cast Bones To Bananas', 'magic', ['Nature Runes', ], [SkillRequirement('Magic', 15), ], [], 0), + LocationRow('Teleport to Varrock', 'magic', ['Central Varrock', 'Law Runes', ], [SkillRequirement('Magic', 25), ], [], 0), + LocationRow('Teleport to Lumbridge', 'magic', ['Lumbridge', 'Law Runes', ], [SkillRequirement('Magic', 31), ], [], 2), + LocationRow('Teleport to Falador', 'magic', ['Falador', 'Law Runes', ], [SkillRequirement('Magic', 37), ], [], 6), + LocationRow('Craft an Air Rune', 'runecraft', ['Rune Essence', 'Falador Farms', ], [SkillRequirement('Runecraft', 1), ], [], 0), + LocationRow('Craft runes with a Mind Core', 'runecraft', ['Camdozaal', 'Goblin Village', ], [SkillRequirement('Runecraft', 2), ], [], 0), + LocationRow('Craft runes with a Body Core', 'runecraft', ['Camdozaal', 'Dwarven Mountain Pass', ], [SkillRequirement('Runecraft', 20), ], [], 0), + LocationRow('Make an Unblessed Symbol', 'crafting', ['Silver Ore', 'Furnace', 'Al Kharid', 'Sheep', 'Spinning Wheel', ], [SkillRequirement('Crafting', 16), ], [], 0), + LocationRow('Cut a Sapphire', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 20), ], [], 0), + LocationRow('Cut an Emerald', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 27), ], [], 0), + LocationRow('Cut a Ruby', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 34), ], [], 4), + LocationRow('Cut a Diamond', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 43), ], [], 8), + LocationRow('Mine a Blurite Ore', 'mining', ['Mudskipper Point', 'Port Sarim', ], [SkillRequirement('Mining', 10), ], [], 0), + LocationRow('Crush a Barronite Deposit', 'mining', ['Camdozaal', ], [SkillRequirement('Mining', 14), ], [], 0), + LocationRow('Mine Silver', 'mining', ['Silver Ore', ], [SkillRequirement('Mining', 20), ], [], 0), + LocationRow('Mine Coal', 'mining', ['Coal Ore', ], [SkillRequirement('Mining', 30), ], [], 2), + LocationRow('Mine Gold', 'mining', ['Gold Ore', ], [SkillRequirement('Mining', 40), ], [], 6), + LocationRow('Smelt an Iron Bar', 'smithing', ['Iron Ore', 'Furnace', ], [SkillRequirement('Smithing', 15), SkillRequirement('Mining', 15), ], [], 0), + LocationRow('Smelt a Silver Bar', 'smithing', ['Silver Ore', 'Furnace', ], [SkillRequirement('Smithing', 20), SkillRequirement('Mining', 20), ], [], 0), + LocationRow('Smelt a Steel Bar', 'smithing', ['Coal Ore', 'Iron Ore', 'Furnace', ], [SkillRequirement('Smithing', 30), SkillRequirement('Mining', 30), ], [], 2), + LocationRow('Smelt a Gold Bar', 'smithing', ['Gold Ore', 'Furnace', ], [SkillRequirement('Smithing', 40), SkillRequirement('Mining', 40), ], [], 6), + LocationRow('Catch some Anchovies', 'fishing', ['Shrimp Spot', ], [SkillRequirement('Fishing', 15), ], [], 0), + LocationRow('Catch a Trout', 'fishing', ['Fly Fishing Spot', ], [SkillRequirement('Fishing', 20), ], [], 0), + LocationRow('Prepare a Tetra', 'fishing', ['Camdozaal', ], [SkillRequirement('Fishing', 33), SkillRequirement('Cooking', 33), ], [], 2), + LocationRow('Catch a Lobster', 'fishing', ['Lobster Spot', ], [SkillRequirement('Fishing', 40), ], [], 6), + LocationRow('Catch a Swordfish', 'fishing', ['Lobster Spot', ], [SkillRequirement('Fishing', 50), ], [], 12), + LocationRow('Bake a Redberry Pie', 'cooking', ['Redberry Bush', 'Wheat', 'Windmill', 'Pie Dish', ], [SkillRequirement('Cooking', 10), ], [], 0), + LocationRow('Cook some Stew', 'cooking', ['Bowl', 'Meat', 'Potato', ], [SkillRequirement('Cooking', 25), ], [], 0), + LocationRow('Bake an Apple Pie', 'cooking', ['Cooking Apple', 'Wheat', 'Windmill', 'Pie Dish', ], [SkillRequirement('Cooking', 30), ], [], 2), + LocationRow('Bake a Cake', 'cooking', ['Wheat', 'Windmill', 'Egg', 'Milk', 'Cake Tin', ], [SkillRequirement('Cooking', 40), ], [], 6), + LocationRow('Bake a Meat Pizza', 'cooking', ['Wheat', 'Windmill', 'Cheese', 'Tomato', 'Meat', ], [SkillRequirement('Cooking', 45), ], [], 8), + LocationRow('Burn some Oak Logs', 'firemaking', ['Oak Tree', ], [SkillRequirement('Firemaking', 15), ], [], 0), + LocationRow('Burn some Willow Logs', 'firemaking', ['Willow Tree', ], [SkillRequirement('Firemaking', 30), ], [], 0), + LocationRow('Travel on a Canoe', 'woodcutting', ['Canoe Tree', ], [SkillRequirement('Woodcutting', 12), ], [], 0), + LocationRow('Cut an Oak Log', 'woodcutting', ['Oak Tree', ], [SkillRequirement('Woodcutting', 15), ], [], 0), + LocationRow('Cut a Willow Log', 'woodcutting', ['Willow Tree', ], [SkillRequirement('Woodcutting', 30), ], [], 0), + LocationRow('Kill Jeff', 'combat', ['Dwarven Mountain Pass', ], [SkillRequirement('Combat', 2), ], [], 0), + LocationRow('Kill a Goblin', 'combat', ['Goblin', ], [SkillRequirement('Combat', 2), ], [], 0), + LocationRow('Kill a Monkey', 'combat', ['Karamja', ], [SkillRequirement('Combat', 3), ], [], 0), + LocationRow('Kill a Barbarian', 'combat', ['Barbarian', ], [SkillRequirement('Combat', 10), ], [], 0), + LocationRow('Kill a Giant Frog', 'combat', ['Lumbridge Swamp', ], [SkillRequirement('Combat', 13), ], [], 0), + LocationRow('Kill a Zombie', 'combat', ['Zombie', ], [SkillRequirement('Combat', 13), ], [], 0), + LocationRow('Kill a Guard', 'combat', ['Guard', ], [SkillRequirement('Combat', 21), ], [], 0), + LocationRow('Kill a Hill Giant', 'combat', ['Hill Giant', ], [SkillRequirement('Combat', 28), ], [], 2), + LocationRow('Kill a Deadly Red Spider', 'combat', ['Deadly Red Spider', ], [SkillRequirement('Combat', 34), ], [], 2), + LocationRow('Kill a Moss Giant', 'combat', ['Moss Giant', ], [SkillRequirement('Combat', 42), ], [], 2), + LocationRow('Kill a Catablepon', 'combat', ['Barbarian Village', ], [SkillRequirement('Combat', 49), ], [], 4), + LocationRow('Kill an Ice Giant', 'combat', ['Ice Giant', ], [SkillRequirement('Combat', 53), ], [], 4), + LocationRow('Kill a Lesser Demon', 'combat', ['Lesser Demon', ], [SkillRequirement('Combat', 82), ], [], 8), + LocationRow('Kill an Ogress Shaman', 'combat', ['Corsair Cove', ], [SkillRequirement('Combat', 82), ], [], 8), + LocationRow('Kill Obor', 'combat', ['Edgeville', ], [SkillRequirement('Combat', 106), ], [], 28), + LocationRow('Kill Bryophyta', 'combat', ['Central Varrock', ], [SkillRequirement('Combat', 128), ], [], 28), + LocationRow('Total XP 5,000', 'general', [], [], [], 0), + LocationRow('Combat Level 5', 'general', [], [], [], 0), + LocationRow('Total XP 10,000', 'general', [], [], [], 0), + LocationRow('Total Level 50', 'general', [], [], [], 0), + LocationRow('Total XP 25,000', 'general', [], [], [], 0), + LocationRow('Total Level 100', 'general', [], [], [], 0), + LocationRow('Total XP 50,000', 'general', [], [], [], 0), + LocationRow('Combat Level 15', 'general', [], [], [], 0), + LocationRow('Total Level 150', 'general', [], [], [], 2), + LocationRow('Total XP 75,000', 'general', [], [], [], 2), + LocationRow('Combat Level 25', 'general', [], [], [], 2), + LocationRow('Total XP 100,000', 'general', [], [], [], 6), + LocationRow('Total Level 200', 'general', [], [], [], 6), + LocationRow('Total XP 125,000', 'general', [], [], [], 6), + LocationRow('Combat Level 30', 'general', [], [], [], 10), + LocationRow('Total Level 250', 'general', [], [], [], 10), + LocationRow('Total XP 150,000', 'general', [], [], [], 10), + LocationRow('Total Level 300', 'general', [], [], [], 16), + LocationRow('Combat Level 40', 'general', [], [], [], 16), + LocationRow('Open a Simple Lockbox', 'general', ['Camdozaal', ], [], [], 0), + LocationRow('Open an Elaborate Lockbox', 'general', ['Camdozaal', ], [], [], 0), + LocationRow('Open an Ornate Lockbox', 'general', ['Camdozaal', ], [], [], 0), + LocationRow('Points: Cook\'s Assistant', 'points', [], [], [], 0), + LocationRow('Points: Demon Slayer', 'points', [], [], [], 0), + LocationRow('Points: The Restless Ghost', 'points', [], [], [], 0), + LocationRow('Points: Romeo & Juliet', 'points', [], [], [], 0), + LocationRow('Points: Sheep Shearer', 'points', [], [], [], 0), + LocationRow('Points: Shield of Arrav', 'points', [], [], [], 0), + LocationRow('Points: Ernest the Chicken', 'points', [], [], [], 0), + LocationRow('Points: Vampyre Slayer', 'points', [], [], [], 0), + LocationRow('Points: Imp Catcher', 'points', [], [], [], 0), + LocationRow('Points: Prince Ali Rescue', 'points', [], [], [], 0), + LocationRow('Points: Doric\'s Quest', 'points', [], [], [], 0), + LocationRow('Points: Black Knights\' Fortress', 'points', [], [], [], 0), + LocationRow('Points: Witch\'s Potion', 'points', [], [], [], 0), + LocationRow('Points: The Knight\'s Sword', 'points', [], [], [], 0), + LocationRow('Points: Goblin Diplomacy', 'points', [], [], [], 0), + LocationRow('Points: Pirate\'s Treasure', 'points', [], [], [], 0), + LocationRow('Points: Rune Mysteries', 'points', [], [], [], 0), + LocationRow('Points: Misthalin Mystery', 'points', [], [], [], 0), + LocationRow('Points: The Corsair Curse', 'points', [], [], [], 0), + LocationRow('Points: X Marks the Spot', 'points', [], [], [], 0), + LocationRow('Points: Below Ice Mountain', 'points', [], [], [], 0), +] diff --git a/worlds/osrs/LogicCSV/regions_generated.py b/worlds/osrs/LogicCSV/regions_generated.py new file mode 100644 index 00000000000..87b3747d938 --- /dev/null +++ b/worlds/osrs/LogicCSV/regions_generated.py @@ -0,0 +1,47 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from ..Regions import RegionRow + +region_rows = [ + RegionRow('Lumbridge', 'Area: Lumbridge', ['Lumbridge Farms East', 'Lumbridge Farms West', 'Al Kharid', 'Lumbridge Swamp', 'HAM Hideout', 'South of Varrock', 'Barbarian Village', 'Edgeville', 'Wilderness', ], ['Mind Runes', 'Spinning Wheel', 'Furnace', 'Chisel', 'Bronze Anvil', 'Fly Fishing Spot', 'Bowl', 'Cake Tin', 'Oak Tree', 'Willow Tree', 'Canoe Tree', 'Goblin', 'Imps', ]), + RegionRow('Lumbridge Swamp', 'Area: Lumbridge Swamp', ['Lumbridge', 'HAM Hideout', ], ['Bronze Ores', 'Coal Ore', 'Shrimp Spot', 'Meat', 'Goblin', 'Imps', ]), + RegionRow('HAM Hideout', 'Area: HAM Hideout', ['Lumbridge Farms West', 'Lumbridge', 'Lumbridge Swamp', 'Draynor Village', ], ['Goblin', ]), + RegionRow('Lumbridge Farms West', 'Area: Lumbridge Farms', ['Sourhog\'s Lair', 'HAM Hideout', 'Draynor Village', ], ['Sheep', 'Meat', 'Wheat', 'Windmill', 'Egg', 'Milk', 'Willow Tree', 'Imps', 'Potato', ]), + RegionRow('Lumbridge Farms East', 'Area: Lumbridge Farms', ['South of Varrock', 'Lumbridge', ], ['Meat', 'Egg', 'Milk', 'Willow Tree', 'Goblin', 'Imps', 'Potato', ]), + RegionRow('Sourhog\'s Lair', 'Area: South of Varrock', ['Lumbridge Farms West', 'Draynor Manor Outskirts', ], ['', ]), + RegionRow('South of Varrock', 'Area: South of Varrock', ['Al Kharid', 'West Varrock', 'Central Varrock', 'East Varrock', 'Lumbridge Farms East', 'Lumbridge', 'Barbarian Village', 'Edgeville', 'Wilderness', ], ['Sheep', 'Bronze Ores', 'Iron Ore', 'Silver Ore', 'Redberry Bush', 'Meat', 'Wheat', 'Oak Tree', 'Willow Tree', 'Canoe Tree', 'Guard', 'Imps', 'Clay Ore', ]), + RegionRow('East Varrock', 'Area: East Varrock', ['Wilderness', 'South of Varrock', 'Central Varrock', 'Varrock Palace', ], ['Guard', ]), + RegionRow('Central Varrock', 'Area: Central Varrock', ['Varrock Palace', 'East Varrock', 'South of Varrock', 'West Varrock', ], ['Mind Runes', 'Chisel', 'Anvil', 'Bowl', 'Cake Tin', 'Oak Tree', 'Barbarian', 'Guard', 'Rune Essence', 'Imps', ]), + RegionRow('Varrock Palace', 'Area: Varrock Palace', ['Wilderness', 'East Varrock', 'Central Varrock', 'West Varrock', ], ['Pie Dish', 'Oak Tree', 'Zombie', 'Guard', 'Deadly Red Spider', 'Moss Giant', 'Nature Runes', 'Law Runes', ]), + RegionRow('West Varrock', 'Area: West Varrock', ['Wilderness', 'Varrock Palace', 'South of Varrock', 'Barbarian Village', 'Edgeville', 'Cook\'s Guild', ], ['Anvil', 'Wheat', 'Oak Tree', 'Goblin', 'Guard', 'Onion', ]), + RegionRow('Cook\'s Guild', 'Area: West Varrock*', ['West Varrock', ], ['Bowl', 'Cooking Apple', 'Pie Dish', 'Cake Tin', 'Windmill', ]), + RegionRow('Edgeville', 'Area: Edgeville', ['Wilderness', 'West Varrock', 'Barbarian Village', 'South of Varrock', 'Lumbridge', ], ['Furnace', 'Chisel', 'Bronze Ores', 'Iron Ore', 'Coal Ore', 'Bowl', 'Meat', 'Cake Tin', 'Willow Tree', 'Canoe Tree', 'Zombie', 'Guard', 'Hill Giant', 'Nature Runes', 'Law Runes', 'Imps', ]), + RegionRow('Barbarian Village', 'Area: Barbarian Village', ['Edgeville', 'West Varrock', 'Draynor Manor Outskirts', 'Dwarven Mountain Pass', ], ['Spinning Wheel', 'Coal Ore', 'Anvil', 'Fly Fishing Spot', 'Meat', 'Canoe Tree', 'Barbarian', 'Zombie', 'Law Runes', ]), + RegionRow('Draynor Manor Outskirts', 'Area: Draynor Manor', ['Barbarian Village', 'Sourhog\'s Lair', 'Draynor Village', 'Falador East Outskirts', ], ['Goblin', ]), + RegionRow('Draynor Manor', 'Area: Draynor Manor', ['Draynor Village', ], ['', ]), + RegionRow('Falador East Outskirts', 'Area: Falador', ['Dwarven Mountain Pass', 'Draynor Manor Outskirts', 'Falador Farms', ], ['', ]), + RegionRow('Dwarven Mountain Pass', 'Area: Dwarven Mines', ['Goblin Village', 'Monastery', 'Barbarian Village', 'Falador East Outskirts', 'Falador', ], ['Anvil*', 'Wheat', ]), + RegionRow('Dwarven Mines', 'Area: Dwarven Mines', ['Monastery', 'Ice Mountain', 'Falador', ], ['Chisel', 'Bronze Ores', 'Iron Ore', 'Coal Ore', 'Gold Ore', 'Anvil', 'Pie Dish', 'Clay Ore', ]), + RegionRow('Goblin Village', 'Area: Ice Mountain', ['Wilderness', 'Dwarven Mountain Pass', ], ['Meat', ]), + RegionRow('Ice Mountain', 'Area: Ice Mountain', ['Wilderness', 'Monastery', 'Dwarven Mines', 'Camdozaal*', ], ['', ]), + RegionRow('Camdozaal', 'Area: Ice Mountain', ['Ice Mountain', ], ['Clay Ore', ]), + RegionRow('Monastery', 'Area: Monastery', ['Wilderness', 'Dwarven Mountain Pass', 'Dwarven Mines', 'Ice Mountain', ], ['Sheep', ]), + RegionRow('Falador', 'Area: Falador', ['Dwarven Mountain Pass', 'Falador Farms', 'Dwarven Mines', ], ['Furnace', 'Chisel', 'Bowl', 'Cake Tin', 'Oak Tree', 'Guard', 'Imps', ]), + RegionRow('Falador Farms', 'Area: Falador Farms', ['Falador', 'Falador East Outskirts', 'Draynor Village', 'Port Sarim', 'Rimmington', 'Crafting Guild Outskirts', ], ['Spinning Wheel', 'Meat', 'Egg', 'Milk', 'Oak Tree', 'Imps', ]), + RegionRow('Port Sarim', 'Area: Port Sarim', ['Falador Farms', 'Mudskipper Point', 'Rimmington', 'Karamja Docks', 'Crandor', ], ['Mind Runes', 'Shrimp Spot', 'Meat', 'Cheese', 'Tomato', 'Oak Tree', 'Willow Tree', 'Goblin', 'Potato', ]), + RegionRow('Karamja Docks', 'Area: Mudskipper Point', ['Port Sarim', 'Karamja', ], ['', ]), + RegionRow('Mudskipper Point', 'Area: Mudskipper Point', ['Rimmington', 'Port Sarim', ], ['Anvil', 'Ice Giant', 'Nature Runes', 'Law Runes', ]), + RegionRow('Karamja', 'Area: Karamja', ['Karamja Docks', 'Crandor', ], ['Gold Ore', 'Lobster Spot', 'Bowl', 'Cake Tin', 'Deadly Red Spider', 'Imps', ]), + RegionRow('Crandor', 'Area: Crandor', ['Karamja', 'Port Sarim', ], ['Coal Ore', 'Gold Ore', 'Moss Giant', 'Lesser Demon', 'Nature Runes', 'Law Runes', ]), + RegionRow('Rimmington', 'Area: Rimmington', ['Falador Farms', 'Port Sarim', 'Mudskipper Point', 'Crafting Guild Peninsula', 'Corsair Cove', ], ['Chisel', 'Bronze Ores', 'Iron Ore', 'Gold Ore', 'Bowl', 'Cake Tin', 'Wheat', 'Oak Tree', 'Willow Tree', 'Crafting Moulds', 'Imps', 'Clay Ore', 'Onion', ]), + RegionRow('Crafting Guild Peninsula', 'Area: Crafting Guild', ['Falador Farms', 'Rimmington', ], ['', ]), + RegionRow('Crafting Guild Outskirts', 'Area: Crafting Guild', ['Falador Farms', 'Crafting Guild', ], ['Sheep', 'Willow Tree', 'Oak Tree', ]), + RegionRow('Crafting Guild', 'Area: Crafting Guild*', ['Crafting Guild', ], ['Spinning Wheel', 'Chisel', 'Silver Ore', 'Gold Ore', 'Meat', 'Milk', 'Clay Ore', ]), + RegionRow('Draynor Village', 'Area: Draynor Village', ['Draynor Manor', 'Lumbridge Farms West', 'HAM Hideout', 'Wizard Tower', ], ['Anvil', 'Shrimp Spot', 'Wheat', 'Cheese', 'Tomato', 'Willow Tree', 'Goblin', 'Zombie', 'Nature Runes', 'Law Runes', 'Imps', ]), + RegionRow('Wizard Tower', 'Area: Wizard Tower', ['Draynor Village', ], ['Lesser Demon', 'Rune Essence', ]), + RegionRow('Corsair Cove', 'Area: Corsair Cove*', ['Rimmington', ], ['Anvil', 'Meat', ]), + RegionRow('Al Kharid', 'Area: Al Kharid', ['South of Varrock', 'Citharede Abbey', 'Lumbridge', 'Port Sarim', ], ['Furnace', 'Chisel', 'Bronze Ores', 'Iron Ore', 'Silver Ore', 'Coal Ore', 'Gold Ore', 'Shrimp Spot', 'Bowl', 'Cake Tin', 'Cheese', 'Crafting Moulds', 'Imps', ]), + RegionRow('Citharede Abbey', 'Area: Citharede Abbey', ['Al Kharid', ], ['Iron Ore', 'Coal Ore', 'Anvil', 'Hill Giant', 'Nature Runes', 'Law Runes', ]), + RegionRow('Wilderness', 'Area: Wilderness', ['East Varrock', 'Varrock Palace', 'West Varrock', 'Edgeville', 'Monastery', 'Ice Mountain', 'Goblin Village', 'South of Varrock', 'Lumbridge', ], ['Furnace', 'Chisel', 'Iron Ore', 'Coal Ore', 'Anvil', 'Meat', 'Cake Tin', 'Cheese', 'Tomato', 'Oak Tree', 'Canoe Tree', 'Zombie', 'Hill Giant', 'Deadly Red Spider', 'Moss Giant', 'Ice Giant', 'Lesser Demon', 'Nature Runes', 'Law Runes', ]), +] diff --git a/worlds/osrs/LogicCSV/resources_generated.py b/worlds/osrs/LogicCSV/resources_generated.py new file mode 100644 index 00000000000..18c2ebe2f31 --- /dev/null +++ b/worlds/osrs/LogicCSV/resources_generated.py @@ -0,0 +1,54 @@ +""" +This file was auto generated by LogicCSVToPython.py +""" +from ..Regions import ResourceRow + +resource_rows = [ + ResourceRow('Mind Runes'), + ResourceRow('Spinning Wheel'), + ResourceRow('Sheep'), + ResourceRow('Furnace'), + ResourceRow('Chisel'), + ResourceRow('Bronze Ores'), + ResourceRow('Iron Ore'), + ResourceRow('Silver Ore'), + ResourceRow('Coal Ore'), + ResourceRow('Gold Ore'), + ResourceRow('Bronze Anvil'), + ResourceRow('Anvil'), + ResourceRow('Shrimp Spot'), + ResourceRow('Fly Fishing Spot'), + ResourceRow('Lobster Spot'), + ResourceRow('Redberry Bush'), + ResourceRow('Bowl'), + ResourceRow('Meat'), + ResourceRow('Cooking Apple'), + ResourceRow('Pie Dish'), + ResourceRow('Cake Tin'), + ResourceRow('Wheat'), + ResourceRow('Windmill'), + ResourceRow('Egg'), + ResourceRow('Milk'), + ResourceRow('Cheese'), + ResourceRow('Tomato'), + ResourceRow('Oak Tree'), + ResourceRow('Willow Tree'), + ResourceRow('Canoe Tree'), + ResourceRow('Goblin'), + ResourceRow('Barbarian'), + ResourceRow('Zombie'), + ResourceRow('Guard'), + ResourceRow('Hill Giant'), + ResourceRow('Deadly Red Spider'), + ResourceRow('Moss Giant'), + ResourceRow('Ice Giant'), + ResourceRow('Lesser Demon'), + ResourceRow('Rune Essence'), + ResourceRow('Crafting Moulds'), + ResourceRow('Nature Runes'), + ResourceRow('Law Runes'), + ResourceRow('Imps'), + ResourceRow('Clay Ore'), + ResourceRow('Onion'), + ResourceRow('Potato'), +] diff --git a/worlds/osrs/Names.py b/worlds/osrs/Names.py new file mode 100644 index 00000000000..95aed742b6f --- /dev/null +++ b/worlds/osrs/Names.py @@ -0,0 +1,212 @@ +from enum import Enum + + +class RegionNames(str, Enum): + Lumbridge = "Lumbridge" + Lumbridge_Swamp = "Lumbridge Swamp" + Lumbridge_Farms_East = "Lumbridge Farms East" + Lumbridge_Farms_West = "Lumbridge Farms West" + HAM_Hideout = "HAM Hideout" + Draynor_Village = "Draynor Village" + Draynor_Manor = "Draynor Manor" + Wizards_Tower = "Wizard Tower" + Al_Kharid = "Al Kharid" + Citharede_Abbey = "Citharede Abbey" + South_Of_Varrock = "South of Varrock" + Central_Varrock = "Central Varrock" + Varrock_Palace = "Varrock Palace" + East_Of_Varrock = "East Varrock" + West_Varrock = "West Varrock" + Edgeville = "Edgeville" + Barbarian_Village = "Barbarian Village" + Monastery = "Monastery" + Ice_Mountain = "Ice Mountain" + Dwarven_Mines = "Dwarven Mines" + Falador = "Falador" + Falador_Farm = "Falador Farms" + Crafting_Guild = "Crafting Guild" + Cooks_Guild = "Cook's Guild" + Rimmington = "Rimmington" + Port_Sarim = "Port Sarim" + Mudskipper_Point = "Mudskipper Point" + Karamja = "Karamja" + Corsair_Cove = "Corsair Cove" + Wilderness = "The Wilderness" + Crandor = "Crandor" + # Resource Regions + Egg = "Egg" + Sheep = "Sheep" + Milk = "Milk" + Wheat = "Wheat" + Windmill = "Windmill" + Spinning_Wheel = "Spinning Wheel" + Imp = "Imp" + Bronze_Ores = "Bronze Ores" + Clay_Rock = "Clay Ore" + Coal_Rock = "Coal Ore" + Iron_Rock = "Iron Ore" + Silver_Rock = "Silver Ore" + Gold_Rock = "Gold Ore" + Furnace = "Furnace" + Anvil = "Anvil" + Oak_Tree = "Oak Tree" + Willow_Tree = "Willow Tree" + Shrimp = "Shrimp Spot" + Fly_Fish = "Fly Fishing Spot" + Lobster = "Lobster Spot" + Mind_Runes = "Mind Runes" + Canoe_Tree = "Canoe Tree" + + __str__ = str.__str__ + + +class ItemNames(str, Enum): + Lumbridge = "Area: Lumbridge" + Lumbridge_Swamp = "Area: Lumbridge Swamp" + Lumbridge_Farms = "Area: Lumbridge Farms" + HAM_Hideout = "Area: HAM Hideout" + Draynor_Village = "Area: Draynor Village" + Draynor_Manor = "Area: Draynor Manor" + Wizards_Tower = "Area: Wizard Tower" + Al_Kharid = "Area: Al Kharid" + Citharede_Abbey = "Area: Citharede Abbey" + South_Of_Varrock = "Area: South of Varrock" + Central_Varrock = "Area: Central Varrock" + Varrock_Palace = "Area: Varrock Palace" + East_Of_Varrock = "Area: East Varrock" + West_Varrock = "Area: West Varrock" + Edgeville = "Area: Edgeville" + Barbarian_Village = "Area: Barbarian Village" + Monastery = "Area: Monastery" + Ice_Mountain = "Area: Ice Mountain" + Dwarven_Mines = "Area: Dwarven Mines" + Falador = "Area: Falador" + Falador_Farm = "Area: Falador Farms" + Crafting_Guild = "Area: Crafting Guild" + Rimmington = "Area: Rimmington" + Port_Sarim = "Area: Port Sarim" + Mudskipper_Point = "Area: Mudskipper Point" + Karamja = "Area: Karamja" + Crandor = "Area: Crandor" + Corsair_Cove = "Area: Corsair Cove" + Wilderness = "Area: Wilderness" + Progressive_Armor = "Progressive Armor" + Progressive_Weapons = "Progressive Weapons" + Progressive_Tools = "Progressive Tools" + Progressive_Range_Armor = "Progressive Range Armor" + Progressive_Range_Weapon = "Progressive Range Weapon" + Progressive_Magic = "Progressive Magic Spell" + Lobsters = "10 Lobsters" + Swordfish = "5 Swordfish" + Energy_Potions = "10 Energy Potions" + Coins = "5,000 Coins" + Mind_Runes = "50 Mind Runes" + Chaos_Runes = "25 Chaos Runes" + Death_Runes = "10 Death Runes" + Law_Runes = "10 Law Runes" + QP_Cooks_Assistant = "1 QP (Cook's Assistant)" + QP_Demon_Slayer = "3 QP (Demon Slayer)" + QP_Restless_Ghost = "1 QP (The Restless Ghost)" + QP_Romeo_Juliet = "5 QP (Romeo & Juliet)" + QP_Sheep_Shearer = "1 QP (Sheep Shearer)" + QP_Shield_of_Arrav = "1 QP (Shield of Arrav)" + QP_Ernest_the_Chicken = "4 QP (Ernest the Chicken)" + QP_Vampyre_Slayer = "3 QP (Vampyre Slayer)" + QP_Imp_Catcher = "1 QP (Imp Catcher)" + QP_Prince_Ali_Rescue = "3 QP (Prince Ali Rescue)" + QP_Dorics_Quest = "1 QP (Doric's Quest)" + QP_Black_Knights_Fortress = "3 QP (Black Knights' Fortress)" + QP_Witchs_Potion = "1 QP (Witch's Potion)" + QP_Knights_Sword = "1 QP (The Knight's Sword)" + QP_Goblin_Diplomacy = "5 QP (Goblin Diplomacy)" + QP_Pirates_Treasure = "2 QP (Pirate's Treasure)" + QP_Rune_Mysteries = "1 QP (Rune Mysteries)" + QP_Misthalin_Mystery = "1 QP (Misthalin Mystery)" + QP_Corsair_Curse = "2 QP (The Corsair Curse)" + QP_X_Marks_the_Spot = "1 QP (X Marks The Spot)" + QP_Below_Ice_Mountain = "1 QP (Below Ice Mountain)" + + __str__ = str.__str__ + + +class LocationNames(str, Enum): + Q_Cooks_Assistant = "Quest: Cook's Assistant" + Q_Demon_Slayer = "Quest: Demon Slayer" + Q_Restless_Ghost = "Quest: The Restless Ghost" + Q_Romeo_Juliet = "Quest: Romeo & Juliet" + Q_Sheep_Shearer = "Quest: Sheep Shearer" + Q_Shield_of_Arrav = "Quest: Shield of Arrav" + Q_Ernest_the_Chicken = "Quest: Ernest the Chicken" + Q_Vampyre_Slayer = "Quest: Vampyre Slayer" + Q_Imp_Catcher = "Quest: Imp Catcher" + Q_Prince_Ali_Rescue = "Quest: Prince Ali Rescue" + Q_Dorics_Quest = "Quest: Doric's Quest" + Q_Black_Knights_Fortress = "Quest: Black Knights' Fortress" + Q_Witchs_Potion = "Quest: Witch's Potion" + Q_Knights_Sword = "Quest: The Knight's Sword" + Q_Goblin_Diplomacy = "Quest: Goblin Diplomacy" + Q_Pirates_Treasure = "Quest: Pirate's Treasure" + Q_Rune_Mysteries = "Quest: Rune Mysteries" + Q_Misthalin_Mystery = "Quest: Misthalin Mystery" + Q_Corsair_Curse = "Quest: The Corsair Curse" + Q_X_Marks_the_Spot = "Quest: X Marks the Spot" + Q_Below_Ice_Mountain = "Quest: Below Ice Mountain" + QP_Cooks_Assistant = "Points: Cook's Assistant" + QP_Demon_Slayer = "Points: Demon Slayer" + QP_Restless_Ghost = "Points: The Restless Ghost" + QP_Romeo_Juliet = "Points: Romeo & Juliet" + QP_Sheep_Shearer = "Points: Sheep Shearer" + QP_Shield_of_Arrav = "Points: Shield of Arrav" + QP_Ernest_the_Chicken = "Points: Ernest the Chicken" + QP_Vampyre_Slayer = "Points: Vampyre Slayer" + QP_Imp_Catcher = "Points: Imp Catcher" + QP_Prince_Ali_Rescue = "Points: Prince Ali Rescue" + QP_Dorics_Quest = "Points: Doric's Quest" + QP_Black_Knights_Fortress = "Points: Black Knights' Fortress" + QP_Witchs_Potion = "Points: Witch's Potion" + QP_Knights_Sword = "Points: The Knight's Sword" + QP_Goblin_Diplomacy = "Points: Goblin Diplomacy" + QP_Pirates_Treasure = "Points: Pirate's Treasure" + QP_Rune_Mysteries = "Points: Rune Mysteries" + QP_Misthalin_Mystery = "Points: Misthalin Mystery" + QP_Corsair_Curse = "Points: The Corsair Curse" + QP_X_Marks_the_Spot = "Points: X Marks the Spot" + QP_Below_Ice_Mountain = "Points: Below Ice Mountain" + Guppy = "Prepare a Guppy" + Cavefish = "Prepare a Cavefish" + Tetra = "Prepare a Tetra" + Barronite_Deposit = "Crush a Barronite Deposit" + Oak_Log = "Cut an Oak Log" + Willow_Log = "Cut a Willow Log" + Catch_Lobster = "Catch a Lobster" + Mine_Silver = "Mine Silver" + Mine_Coal = "Mine Coal" + Mine_Gold = "Mine Gold" + Smelt_Silver = "Smelt a Silver Bar" + Smelt_Steel = "Smelt a Steel Bar" + Smelt_Gold = "Smelt a Gold Bar" + Cut_Sapphire = "Cut a Sapphire" + Cut_Emerald = "Cut an Emerald" + Cut_Ruby = "Cut a Ruby" + Cut_Diamond = "Cut a Diamond" + K_Lesser_Demon = "Kill a Lesser Demon" + K_Ogress_Shaman = "Kill an Ogress Shaman" + Bake_Apple_Pie = "Bake an Apple Pie" + Bake_Cake = "Bake a Cake" + Bake_Meat_Pizza = "Bake a Meat Pizza" + Total_XP_5000 = "5,000 Total XP" + Total_XP_10000 = "10,000 Total XP" + Total_XP_25000 = "25,000 Total XP" + Total_XP_50000 = "50,000 Total XP" + Total_XP_100000 = "100,000 Total XP" + Total_Level_50 = "Total Level 50" + Total_Level_100 = "Total Level 100" + Total_Level_150 = "Total Level 150" + Total_Level_200 = "Total Level 200" + Combat_Level_5 = "Combat Level 5" + Combat_Level_15 = "Combat Level 15" + Combat_Level_25 = "Combat Level 25" + Travel_on_a_Canoe = "Travel on a Canoe" + Q_Dragon_Slayer = "Quest: Dragon Slayer" + + __str__ = str.__str__ diff --git a/worlds/osrs/Options.py b/worlds/osrs/Options.py new file mode 100644 index 00000000000..520cd8e8b06 --- /dev/null +++ b/worlds/osrs/Options.py @@ -0,0 +1,474 @@ +from dataclasses import dataclass + +from Options import Choice, Toggle, Range, PerGameCommonOptions + +MAX_COMBAT_TASKS = 16 +MAX_PRAYER_TASKS = 3 +MAX_MAGIC_TASKS = 4 +MAX_RUNECRAFT_TASKS = 3 +MAX_CRAFTING_TASKS = 5 +MAX_MINING_TASKS = 5 +MAX_SMITHING_TASKS = 4 +MAX_FISHING_TASKS = 5 +MAX_COOKING_TASKS = 5 +MAX_FIREMAKING_TASKS = 2 +MAX_WOODCUTTING_TASKS = 3 + +NON_QUEST_LOCATION_COUNT = 22 + + +class StartingArea(Choice): + """ + Which chunks are available at the start. The player may need to move through locked chunks to reach the starting + area, but any areas that require quests, skills, or coins are not available as a starting location. + + "Any Bank" rolls a random region that contains a bank. + Chunksanity can start you in any chunk. Hope you like woodcutting! + """ + display_name = "Starting Region" + option_lumbridge = 0 + option_al_kharid = 1 + option_varrock_east = 2 + option_varrock_west = 3 + option_edgeville = 4 + option_falador = 5 + option_draynor = 6 + option_wilderness = 7 + option_any_bank = 8 + option_chunksanity = 9 + default = 0 + + +class BrutalGrinds(Toggle): + """ + Whether to allow skill tasks without having reasonable access to the usual skill training path. + For example, if enabled, you could be forced to train smithing without an anvil purely by smelting bars, + or training fishing to high levels entirely on shrimp. + """ + display_name = "Allow Brutal Grinds" + + +class ProgressiveTasks(Toggle): + """ + Whether skill tasks should always be generated in order of easiest to hardest. + If enabled, you would not be assigned "Mine Gold" without also being assigned + "Mine Silver", "Mine Coal", and "Mine Iron". Enabling this will result in a generally shorter seed, but with + a lower variety of tasks. + """ + display_name = "Progressive Tasks" + + +class MaxCombatLevel(Range): + """ + The highest combat level of monster to possibly be assigned as a task. + If set to 0, no combat tasks will be generated. + """ + range_start = 0 + range_end = 1520 + default = 50 + + +class MaxCombatTasks(Range): + """ + The maximum number of Combat Tasks to possibly be assigned. + If set to 0, no combat tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_COMBAT_TASKS + default = MAX_COMBAT_TASKS + + +class CombatTaskWeight(Range): + """ + How much to favor generating combat tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxPrayerLevel(Range): + """ + The highest Prayer requirement of any task generated. + If set to 0, no Prayer tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxPrayerTasks(Range): + """ + The maximum number of Prayer Tasks to possibly be assigned. + If set to 0, no Prayer tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_PRAYER_TASKS + default = MAX_PRAYER_TASKS + + +class PrayerTaskWeight(Range): + """ + How much to favor generating Prayer tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMagicLevel(Range): + """ + The highest Magic requirement of any task generated. + If set to 0, no Magic tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMagicTasks(Range): + """ + The maximum number of Magic Tasks to possibly be assigned. + If set to 0, no Magic tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_MAGIC_TASKS + default = MAX_MAGIC_TASKS + + +class MagicTaskWeight(Range): + """ + How much to favor generating Magic tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxRunecraftLevel(Range): + """ + The highest Runecraft requirement of any task generated. + If set to 0, no Runecraft tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxRunecraftTasks(Range): + """ + The maximum number of Runecraft Tasks to possibly be assigned. + If set to 0, no Runecraft tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_RUNECRAFT_TASKS + default = MAX_RUNECRAFT_TASKS + + +class RunecraftTaskWeight(Range): + """ + How much to favor generating Runecraft tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCraftingLevel(Range): + """ + The highest Crafting requirement of any task generated. + If set to 0, no Crafting tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCraftingTasks(Range): + """ + The maximum number of Crafting Tasks to possibly be assigned. + If set to 0, no Crafting tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_CRAFTING_TASKS + default = MAX_CRAFTING_TASKS + + +class CraftingTaskWeight(Range): + """ + How much to favor generating Crafting tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMiningLevel(Range): + """ + The highest Mining requirement of any task generated. + If set to 0, no Mining tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxMiningTasks(Range): + """ + The maximum number of Mining Tasks to possibly be assigned. + If set to 0, no Mining tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_MINING_TASKS + default = MAX_MINING_TASKS + + +class MiningTaskWeight(Range): + """ + How much to favor generating Mining tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxSmithingLevel(Range): + """ + The highest Smithing requirement of any task generated. + If set to 0, no Smithing tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxSmithingTasks(Range): + """ + The maximum number of Smithing Tasks to possibly be assigned. + If set to 0, no Smithing tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_SMITHING_TASKS + default = MAX_SMITHING_TASKS + + +class SmithingTaskWeight(Range): + """ + How much to favor generating Smithing tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFishingLevel(Range): + """ + The highest Fishing requirement of any task generated. + If set to 0, no Fishing tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFishingTasks(Range): + """ + The maximum number of Fishing Tasks to possibly be assigned. + If set to 0, no Fishing tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_FISHING_TASKS + default = MAX_FISHING_TASKS + + +class FishingTaskWeight(Range): + """ + How much to favor generating Fishing tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCookingLevel(Range): + """ + The highest Cooking requirement of any task generated. + If set to 0, no Cooking tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxCookingTasks(Range): + """ + The maximum number of Cooking Tasks to possibly be assigned. + If set to 0, no Cooking tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_COOKING_TASKS + default = MAX_COOKING_TASKS + + +class CookingTaskWeight(Range): + """ + How much to favor generating Cooking tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFiremakingLevel(Range): + """ + The highest Firemaking requirement of any task generated. + If set to 0, no Firemaking tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxFiremakingTasks(Range): + """ + The maximum number of Firemaking Tasks to possibly be assigned. + If set to 0, no Firemaking tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_FIREMAKING_TASKS + default = MAX_FIREMAKING_TASKS + + +class FiremakingTaskWeight(Range): + """ + How much to favor generating Firemaking tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxWoodcuttingLevel(Range): + """ + The highest Woodcutting requirement of any task generated. + If set to 0, no Woodcutting tasks will be generated. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MaxWoodcuttingTasks(Range): + """ + The maximum number of Woodcutting Tasks to possibly be assigned. + If set to 0, no Woodcutting tasks will be generated. + This only determines the maximum possible, fewer than the maximum could be assigned. + """ + range_start = 0 + range_end = MAX_WOODCUTTING_TASKS + default = MAX_WOODCUTTING_TASKS + + +class WoodcuttingTaskWeight(Range): + """ + How much to favor generating Woodcutting tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +class MinimumGeneralTasks(Range): + """ + How many guaranteed general progression tasks to be assigned (total level, total XP, etc.). + General progression tasks will be used to fill out any holes caused by having fewer possible tasks than needed, so + there is no maximum. + """ + range_start = 0 + range_end = NON_QUEST_LOCATION_COUNT + default = 10 + + +class GeneralTaskWeight(Range): + """ + How much to favor generating General tasks over other types of task. + Weights of all Task Types will be compared against each other, a task with 50 weight + is twice as likely to appear as one with 25. + """ + range_start = 0 + range_end = 99 + default = 50 + + +@dataclass +class OSRSOptions(PerGameCommonOptions): + starting_area: StartingArea + brutal_grinds: BrutalGrinds + progressive_tasks: ProgressiveTasks + max_combat_level: MaxCombatLevel + max_combat_tasks: MaxCombatTasks + combat_task_weight: CombatTaskWeight + max_prayer_level: MaxPrayerLevel + max_prayer_tasks: MaxPrayerTasks + prayer_task_weight: PrayerTaskWeight + max_magic_level: MaxMagicLevel + max_magic_tasks: MaxMagicTasks + magic_task_weight: MagicTaskWeight + max_runecraft_level: MaxRunecraftLevel + max_runecraft_tasks: MaxRunecraftTasks + runecraft_task_weight: RunecraftTaskWeight + max_crafting_level: MaxCraftingLevel + max_crafting_tasks: MaxCraftingTasks + crafting_task_weight: CraftingTaskWeight + max_mining_level: MaxMiningLevel + max_mining_tasks: MaxMiningTasks + mining_task_weight: MiningTaskWeight + max_smithing_level: MaxSmithingLevel + max_smithing_tasks: MaxSmithingTasks + smithing_task_weight: SmithingTaskWeight + max_fishing_level: MaxFishingLevel + max_fishing_tasks: MaxFishingTasks + fishing_task_weight: FishingTaskWeight + max_cooking_level: MaxCookingLevel + max_cooking_tasks: MaxCookingTasks + cooking_task_weight: CookingTaskWeight + max_firemaking_level: MaxFiremakingLevel + max_firemaking_tasks: MaxFiremakingTasks + firemaking_task_weight: FiremakingTaskWeight + max_woodcutting_level: MaxWoodcuttingLevel + max_woodcutting_tasks: MaxWoodcuttingTasks + woodcutting_task_weight: WoodcuttingTaskWeight + minimum_general_tasks: MinimumGeneralTasks + general_task_weight: GeneralTaskWeight diff --git a/worlds/osrs/Regions.py b/worlds/osrs/Regions.py new file mode 100644 index 00000000000..436cdf3c7c7 --- /dev/null +++ b/worlds/osrs/Regions.py @@ -0,0 +1,12 @@ +import typing + + +class RegionRow(typing.NamedTuple): + name: str + itemReq: str + connections: typing.List[str] + resources: typing.List[str] + + +class ResourceRow(typing.NamedTuple): + name: str diff --git a/worlds/osrs/__init__.py b/worlds/osrs/__init__.py new file mode 100644 index 00000000000..f726b4b81bf --- /dev/null +++ b/worlds/osrs/__init__.py @@ -0,0 +1,657 @@ +import typing + +from BaseClasses import Item, Tutorial, ItemClassification, Region, MultiWorld +from worlds.AutoWorld import WebWorld, World +from worlds.generic.Rules import add_rule, CollectionRule +from .Items import OSRSItem, starting_area_dict, chunksanity_starting_chunks, QP_Items, ItemRow, \ + chunksanity_special_region_names +from .Locations import OSRSLocation, LocationRow + +from .Options import OSRSOptions, StartingArea +from .Names import LocationNames, ItemNames, RegionNames + +from .LogicCSV.LogicCSVToPython import data_csv_tag +from .LogicCSV.items_generated import item_rows +from .LogicCSV.locations_generated import location_rows +from .LogicCSV.regions_generated import region_rows +from .LogicCSV.resources_generated import resource_rows +from .Regions import RegionRow, ResourceRow + + +class OSRSWeb(WebWorld): + theme = "stone" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Old School Runescape Randomizer connected to an Archipelago Multiworld", + "English", + "docs/setup_en.md", + "setup/en", + ["digiholic"] + ) + tutorials = [setup_en] + + +class OSRSWorld(World): + game = "Old School Runescape" + options_dataclass = OSRSOptions + options: OSRSOptions + topology_present = True + web = OSRSWeb() + base_id = 0x070000 + data_version = 1 + + item_name_to_id = {item_rows[i].name: 0x070000 + i for i in range(len(item_rows))} + location_name_to_id = {location_rows[i].name: 0x070000 + i for i in range(len(location_rows))} + + region_name_to_data: typing.Dict[str, Region] + location_name_to_data: typing.Dict[str, OSRSLocation] + + location_rows_by_name: typing.Dict[str, LocationRow] + region_rows_by_name: typing.Dict[str, RegionRow] + resource_rows_by_name: typing.Dict[str, ResourceRow] + item_rows_by_name: typing.Dict[str, ItemRow] + + starting_area_item: str + + locations_by_category: typing.Dict[str, typing.List[LocationRow]] + + def __init__(self, world: MultiWorld, player: int): + super().__init__(world, player) + self.region_name_to_data = {} + self.location_name_to_data = {} + + self.location_rows_by_name = {} + self.region_rows_by_name = {} + self.resource_rows_by_name = {} + self.item_rows_by_name = {} + + self.starting_area_item = "" + + self.locations_by_category = {} + + def generate_early(self) -> None: + location_categories = [location_row.category for location_row in location_rows] + self.locations_by_category = {category: + [location_row for location_row in location_rows if + location_row.category == category] + for category in location_categories} + + self.location_rows_by_name = {loc_row.name: loc_row for loc_row in location_rows} + self.region_rows_by_name = {reg_row.name: reg_row for reg_row in region_rows} + self.resource_rows_by_name = {rec_row.name: rec_row for rec_row in resource_rows} + self.item_rows_by_name = {it_row.name: it_row for it_row in item_rows} + + rnd = self.random + starting_area = self.options.starting_area + + if starting_area.value == StartingArea.option_any_bank: + self.starting_area_item = rnd.choice(starting_area_dict) + elif starting_area.value < StartingArea.option_chunksanity: + self.starting_area_item = starting_area_dict[starting_area.value] + else: + self.starting_area_item = rnd.choice(chunksanity_starting_chunks) + + # Set Starting Chunk + self.multiworld.push_precollected(self.create_item(self.starting_area_item)) + + """ + This function pulls from LogicCSVToPython so that it sends the correct tag of the repository to the client. + _Make sure to update that value whenever the CSVs change!_ + """ + + def fill_slot_data(self): + data = self.options.as_dict("brutal_grinds") + data["data_csv_tag"] = data_csv_tag + return data + + def create_regions(self) -> None: + """ + called to place player's regions into the MultiWorld's regions list. If it's hard to separate, this can be done + during generate_early or basic as well. + """ + + # First, create the "Menu" region to start + menu_region = self.create_region("Menu") + + for region_row in region_rows: + self.create_region(region_row.name) + + for resource_row in resource_rows: + self.create_region(resource_row.name) + + # Removes the word "Area: " from the item name to get the region it applies to. + # I figured tacking "Area: " at the beginning would make it _easier_ to tell apart. Turns out it made it worse + if self.starting_area_item in chunksanity_special_region_names: + starting_area_region = chunksanity_special_region_names[self.starting_area_item] + else: + starting_area_region = self.starting_area_item[6:] # len("Area: ") + starting_entrance = menu_region.create_exit(f"Start->{starting_area_region}") + starting_entrance.access_rule = lambda state: state.has(self.starting_area_item, self.player) + starting_entrance.connect(self.region_name_to_data[starting_area_region]) + + # Create entrances between regions + for region_row in region_rows: + region = self.region_name_to_data[region_row.name] + + for outbound_region_name in region_row.connections: + parsed_outbound = outbound_region_name.replace('*', '') + entrance = region.create_exit(f"{region_row.name}->{parsed_outbound}") + entrance.connect(self.region_name_to_data[parsed_outbound]) + + item_name = self.region_rows_by_name[parsed_outbound].itemReq + if "*" not in outbound_region_name and "*" not in item_name: + entrance.access_rule = lambda state, item_name=item_name: state.has(item_name, self.player) + continue + + self.generate_special_rules_for(entrance, region_row, outbound_region_name) + + for resource_region in region_row.resources: + if not resource_region: + continue + + entrance = region.create_exit(f"{region_row.name}->{resource_region.replace('*', '')}") + if "*" not in resource_region: + entrance.connect(self.region_name_to_data[resource_region]) + else: + self.generate_special_rules_for(entrance, region_row, resource_region) + entrance.connect(self.region_name_to_data[resource_region.replace('*', '')]) + + self.roll_locations() + + def generate_special_rules_for(self, entrance, region_row, outbound_region_name): + # print(f"Special rules required to access region {outbound_region_name} from {region_row.name}") + if outbound_region_name == RegionNames.Cooks_Guild: + item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '') + cooking_level_rule = self.get_skill_rule("cooking", 32) + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + cooking_level_rule(state) + return + if outbound_region_name == RegionNames.Crafting_Guild: + item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '') + crafting_level_rule = self.get_skill_rule("crafting", 40) + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + crafting_level_rule(state) + return + if outbound_region_name == RegionNames.Corsair_Cove: + item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '') + # Need to be able to start Corsair Curse in addition to having the item + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + state.can_reach(RegionNames.Falador_Farm, "Region", self.player) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Falador_Farm, self.player), entrance) + + return + if outbound_region_name == "Camdozaal*": + item_name = self.region_rows_by_name[outbound_region_name.replace('*', '')].itemReq + entrance.access_rule = lambda state: state.has(item_name, self.player) and \ + state.has(ItemNames.QP_Below_Ice_Mountain, self.player) + return + if region_row.name == "Dwarven Mountain Pass" and outbound_region_name == "Anvil*": + entrance.access_rule = lambda state: state.has(ItemNames.QP_Dorics_Quest, self.player) + return + # Special logic for canoes + canoe_regions = [RegionNames.Lumbridge, RegionNames.South_Of_Varrock, RegionNames.Barbarian_Village, + RegionNames.Edgeville, RegionNames.Wilderness] + if region_row.name in canoe_regions: + # Skill rules for greater distances + woodcutting_rule_d1 = self.get_skill_rule("woodcutting", 12) + woodcutting_rule_d2 = self.get_skill_rule("woodcutting", 27) + woodcutting_rule_d3 = self.get_skill_rule("woodcutting", 42) + woodcutting_rule_all = self.get_skill_rule("woodcutting", 57) + + if region_row.name == RegionNames.Lumbridge: + # Canoe Tree access for the Location + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.South_Of_Varrock, self.player) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_all(state) and self.options.max_woodcutting_level >= 57) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + # South of Varrock does not need to be checked, because it's already adjacent + if outbound_region_name == RegionNames.Barbarian_Village: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + if outbound_region_name == RegionNames.Edgeville: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if outbound_region_name == RegionNames.Wilderness: + entrance.access_rule = lambda state: woodcutting_rule_all(state) \ + and self.options.max_woodcutting_level >= 57 + + if region_row.name == RegionNames.South_Of_Varrock: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + # Lumbridge does not need to be checked, because it's already adjacent + if outbound_region_name == RegionNames.Barbarian_Village: + entrance.access_rule = lambda state: woodcutting_rule_d1(state) \ + and self.options.max_woodcutting_level >= 12 + if outbound_region_name == RegionNames.Edgeville: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 27 + if outbound_region_name == RegionNames.Wilderness: + entrance.access_rule = lambda state: woodcutting_rule_all(state) \ + and self.options.max_woodcutting_level >= 42 + if region_row.name == RegionNames.Barbarian_Village: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.South_Of_Varrock) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + if outbound_region_name == RegionNames.Lumbridge: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + if outbound_region_name == RegionNames.South_Of_Varrock: + entrance.access_rule = lambda state: woodcutting_rule_d1(state) \ + and self.options.max_woodcutting_level >= 12 + # Edgeville does not need to be checked, because it's already adjacent + if outbound_region_name == RegionNames.Wilderness: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if region_row.name == RegionNames.Edgeville: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \ + (state.can_reach_region(RegionNames.South_Of_Varrock) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \ + (state.can_reach_region(RegionNames.Wilderness) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance) + # Access to other chunks based on woodcutting settings + if outbound_region_name == RegionNames.Lumbridge: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if outbound_region_name == RegionNames.South_Of_Varrock: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + # Barbarian Village does not need to be checked, because it's already adjacent + # Wilderness does not need to be checked, because it's already adjacent + if region_row.name == RegionNames.Wilderness: + if outbound_region_name == RegionNames.Canoe_Tree: + entrance.access_rule = \ + lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player) + and woodcutting_rule_all(state) and self.options.max_woodcutting_level >= 57) or \ + (state.can_reach_region(RegionNames.South_Of_Varrock) + and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \ + (state.can_reach_region(RegionNames.Barbarian_Village) + and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \ + (state.can_reach_region(RegionNames.Edgeville) + and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance) + self.multiworld.register_indirect_condition( + self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance) + # Access to other chunks based on woodcutting settings + if outbound_region_name == RegionNames.Lumbridge: + entrance.access_rule = lambda state: woodcutting_rule_all(state) \ + and self.options.max_woodcutting_level >= 57 + if outbound_region_name == RegionNames.South_Of_Varrock: + entrance.access_rule = lambda state: woodcutting_rule_d3(state) \ + and self.options.max_woodcutting_level >= 42 + if outbound_region_name == RegionNames.Barbarian_Village: + entrance.access_rule = lambda state: woodcutting_rule_d2(state) \ + and self.options.max_woodcutting_level >= 27 + # Edgeville does not need to be checked, because it's already adjacent + + def roll_locations(self): + locations_required = 0 + generation_is_fake = hasattr(self.multiworld, "generation_is_fake") # UT specific override + for item_row in item_rows: + locations_required += item_row.amount + + locations_added = 1 # At this point we've already added the starting area, so we start at 1 instead of 0 + + # Quests are always added + for i, location_row in enumerate(location_rows): + if location_row.category in {"quest", "points", "goal"}: + self.create_and_add_location(i) + if location_row.category == "quest": + locations_added += 1 + + # Build up the weighted Task Pool + rnd = self.random + + # Start with the minimum general tasks + general_tasks = [task for task in self.locations_by_category["general"]] + if not self.options.progressive_tasks: + rnd.shuffle(general_tasks) + else: + general_tasks.reverse() + for i in range(self.options.minimum_general_tasks): + task = general_tasks.pop() + self.add_location(task) + locations_added += 1 + + general_weight = self.options.general_task_weight if len(general_tasks) > 0 else 0 + + tasks_per_task_type: typing.Dict[str, typing.List[LocationRow]] = {} + weights_per_task_type: typing.Dict[str, int] = {} + + task_types = ["prayer", "magic", "runecraft", "mining", "crafting", + "smithing", "fishing", "cooking", "firemaking", "woodcutting", "combat"] + for task_type in task_types: + max_level_for_task_type = getattr(self.options, f"max_{task_type}_level") + max_amount_for_task_type = getattr(self.options, f"max_{task_type}_tasks") + tasks_for_this_type = [task for task in self.locations_by_category[task_type] + if task.skills[0].level <= max_level_for_task_type] + if not self.options.progressive_tasks: + rnd.shuffle(tasks_for_this_type) + else: + tasks_for_this_type.reverse() + + tasks_for_this_type = tasks_for_this_type[:max_amount_for_task_type] + weight_for_this_type = getattr(self.options, + f"{task_type}_task_weight") + if weight_for_this_type > 0 and tasks_for_this_type: + tasks_per_task_type[task_type] = tasks_for_this_type + weights_per_task_type[task_type] = weight_for_this_type + + # Build a list of collections and weights in a matching order for rnd.choices later + all_tasks = [] + all_weights = [] + for task_type in task_types: + if task_type in tasks_per_task_type: + all_tasks.append(tasks_per_task_type[task_type]) + all_weights.append(weights_per_task_type[task_type]) + + # Even after the initial forced generals, they can still be rolled randomly + if general_weight > 0: + all_tasks.append(general_tasks) + all_weights.append(general_weight) + + while locations_added < locations_required or (generation_is_fake and len(all_tasks) > 0): + if all_tasks: + chosen_task = rnd.choices(all_tasks, all_weights)[0] + if chosen_task: + task = chosen_task.pop() + self.add_location(task) + locations_added += 1 + + # This isn't an else because chosen_task can become empty in the process of resolving the above block + # We still want to clear this list out while we're doing that + if not chosen_task: + index = all_tasks.index(chosen_task) + del all_tasks[index] + del all_weights[index] + + else: + if len(general_tasks) == 0: + raise Exception(f"There are not enough available tasks to fill the remaining pool for OSRS " + + f"Please adjust {self.player_name}'s settings to be less restrictive of tasks.") + task = general_tasks.pop() + self.add_location(task) + locations_added += 1 + + def add_location(self, location): + index = [i for i in range(len(location_rows)) if location_rows[i].name == location.name][0] + self.create_and_add_location(index) + + def create_items(self) -> None: + for item_row in item_rows: + if item_row.name != self.starting_area_item: + for c in range(item_row.amount): + item = self.create_item(item_row.name) + self.multiworld.itempool.append(item) + + def get_filler_item_name(self) -> str: + return self.random.choice( + [ItemNames.Progressive_Armor, ItemNames.Progressive_Weapons, ItemNames.Progressive_Magic, + ItemNames.Progressive_Tools, ItemNames.Progressive_Range_Armor, ItemNames.Progressive_Range_Weapon]) + + def create_and_add_location(self, row_index) -> None: + location_row = location_rows[row_index] + # print(f"Adding task {location_row.name}") + + # Create Location + location_id = self.base_id + row_index + if location_row.category == "points" or location_row.category == "goal": + location_id = None + location = OSRSLocation(self.player, location_row.name, location_id) + self.location_name_to_data[location_row.name] = location + + # Add the location to its first region, or if it doesn't belong to one, to Menu + region = self.region_name_to_data["Menu"] + if location_row.regions: + region = self.region_name_to_data[location_row.regions[0]] + location.parent_region = region + region.locations.append(location) + + def set_rules(self) -> None: + """ + called to set access and item rules on locations and entrances. + """ + quest_attr_names = ["Cooks_Assistant", "Demon_Slayer", "Restless_Ghost", "Romeo_Juliet", + "Sheep_Shearer", "Shield_of_Arrav", "Ernest_the_Chicken", "Vampyre_Slayer", + "Imp_Catcher", "Prince_Ali_Rescue", "Dorics_Quest", "Black_Knights_Fortress", + "Witchs_Potion", "Knights_Sword", "Goblin_Diplomacy", "Pirates_Treasure", + "Rune_Mysteries", "Misthalin_Mystery", "Corsair_Curse", "X_Marks_the_Spot", + "Below_Ice_Mountain"] + for qp_attr_name in quest_attr_names: + loc_name = getattr(LocationNames, f"QP_{qp_attr_name}") + item_name = getattr(ItemNames, f"QP_{qp_attr_name}") + self.multiworld.get_location(loc_name, self.player) \ + .place_locked_item(self.create_event(item_name)) + + for quest_attr_name in quest_attr_names: + qp_loc_name = getattr(LocationNames, f"QP_{quest_attr_name}") + q_loc_name = getattr(LocationNames, f"Q_{quest_attr_name}") + add_rule(self.multiworld.get_location(qp_loc_name, self.player), lambda state, q_loc_name=q_loc_name: ( + self.multiworld.get_location(q_loc_name, self.player).can_reach(state) + )) + + # place "Victory" at "Dragon Slayer" and set collection as win condition + self.multiworld.get_location(LocationNames.Q_Dragon_Slayer, self.player) \ + .place_locked_item(self.create_event("Victory")) + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Victory", self.player)) + + for location_name, location in self.location_name_to_data.items(): + location_row = self.location_rows_by_name[location_name] + # Set up requirements for region + for region_required_name in location_row.regions: + region_required = self.region_name_to_data[region_required_name] + add_rule(location, + lambda state, region_required=region_required: state.can_reach(region_required, "Region", + self.player)) + for skill_req in location_row.skills: + add_rule(location, self.get_skill_rule(skill_req.skill, skill_req.level)) + for item_req in location_row.items: + add_rule(location, lambda state, item_req=item_req: state.has(item_req, self.player)) + if location_row.qp: + add_rule(location, lambda state, location_row=location_row: self.quest_points(state) > location_row.qp) + + def create_region(self, name: str) -> "Region": + region = Region(name, self.player, self.multiworld) + self.region_name_to_data[name] = region + self.multiworld.regions.append(region) + return region + + def create_item(self, item_name: str) -> "Item": + item = [item for item in item_rows if item.name == item_name][0] + index = item_rows.index(item) + return OSRSItem(item.name, item.progression, self.base_id + index, self.player) + + def create_event(self, event: str): + # while we are at it, we can also add a helper to create events + return OSRSItem(event, ItemClassification.progression, None, self.player) + + def quest_points(self, state): + qp = 0 + for qp_event in QP_Items: + if state.has(qp_event, self.player): + qp += int(qp_event[0]) + return qp + + """ + Ensures a target level can be reached with available resources + """ + + def get_skill_rule(self, skill, level) -> CollectionRule: + if skill.lower() == "fishing": + if self.options.brutal_grinds or level < 5: + return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) + if level < 20: + return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) and \ + state.can_reach(RegionNames.Port_Sarim, "Region", self.player) + else: + return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) and \ + state.can_reach(RegionNames.Port_Sarim, "Region", self.player) and \ + state.can_reach(RegionNames.Fly_Fish, "Region", self.player) + if skill.lower() == "mining": + if self.options.brutal_grinds or level < 15: + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) or \ + state.can_reach(RegionNames.Clay_Rock, "Region", self.player) + else: + # Iron is the best way to train all the way to 99, so having access to iron is all you need to check for + return lambda state: (state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) or + state.can_reach(RegionNames.Clay_Rock, "Region", self.player)) and \ + state.can_reach(RegionNames.Iron_Rock, "Region", self.player) + if skill.lower() == "woodcutting": + if self.options.brutal_grinds or level < 15: + # I've checked. There is not a single chunk in the f2p that does not have at least one normal tree. + # Even the desert. + return lambda state: True + if level < 30: + return lambda state: state.can_reach(RegionNames.Oak_Tree, "Region", self.player) + else: + return lambda state: state.can_reach(RegionNames.Oak_Tree, "Region", self.player) and \ + state.can_reach(RegionNames.Willow_Tree, "Region", self.player) + if skill.lower() == "smithing": + if self.options.brutal_grinds: + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) + if level < 15: + # Lumbridge has a special bronze-only anvil. This is the only anvil of its type so it's not included + # in the "Anvil" resource region. We still need to check for it though. + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and \ + (state.can_reach(RegionNames.Anvil, "Region", self.player) or + state.can_reach(RegionNames.Lumbridge, "Region", self.player)) + if level < 30: + # For levels between 15 and 30, the lumbridge anvil won't cut it. Only a real one will do + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Iron_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and \ + state.can_reach(RegionNames.Anvil, "Region", self.player) + else: + return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \ + state.can_reach(RegionNames.Iron_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Coal_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and \ + state.can_reach(RegionNames.Anvil, "Region", self.player) + if skill.lower() == "crafting": + # Crafting is really complex. Need a lot of sub-rules to make this even remotely readable + def can_spin(state): + return state.can_reach(RegionNames.Sheep, "Region", self.player) and \ + state.can_reach(RegionNames.Spinning_Wheel, "Region", self.player) + + def can_pot(state): + return state.can_reach(RegionNames.Clay_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Barbarian_Village, "Region", self.player) + + def can_tan(state): + return state.can_reach(RegionNames.Milk, "Region", self.player) and \ + state.can_reach(RegionNames.Al_Kharid, "Region", self.player) + + def mould_access(state): + return state.can_reach(RegionNames.Al_Kharid, "Region", self.player) or \ + state.can_reach(RegionNames.Rimmington, "Region", self.player) + + def can_silver(state): + + return state.can_reach(RegionNames.Silver_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and mould_access(state) + + def can_gold(state): + return state.can_reach(RegionNames.Gold_Rock, "Region", self.player) and \ + state.can_reach(RegionNames.Furnace, "Region", self.player) and mould_access(state) + + if self.options.brutal_grinds or level < 5: + return lambda state: can_spin(state) or can_pot(state) or can_tan(state) + + can_smelt_gold = self.get_skill_rule("smithing", 40) + can_smelt_silver = self.get_skill_rule("smithing", 20) + if level < 16: + return lambda state: can_pot(state) or can_tan(state) or (can_gold(state) and can_smelt_gold(state)) + else: + return lambda state: can_tan(state) or (can_silver(state) and can_smelt_silver(state)) or \ + (can_gold(state) and can_smelt_gold(state)) + if skill.lower() == "Cooking": + if self.options.brutal_grinds or level < 15: + return lambda state: state.can_reach(RegionNames.Milk, "Region", self.player) or \ + state.can_reach(RegionNames.Egg, "Region", self.player) or \ + state.can_reach(RegionNames.Shrimp, "Region", self.player) or \ + (state.can_reach(RegionNames.Wheat, "Region", self.player) and + state.can_reach(RegionNames.Windmill, "Region", self.player)) + else: + can_catch_fly_fish = self.get_skill_rule("fishing", 20) + return lambda state: state.can_reach(RegionNames.Fly_Fish, "Region", self.player) and \ + can_catch_fly_fish(state) and \ + (state.can_reach(RegionNames.Milk, "Region", self.player) or + state.can_reach(RegionNames.Egg, "Region", self.player) or + state.can_reach(RegionNames.Shrimp, "Region", self.player) or + (state.can_reach(RegionNames.Wheat, "Region", self.player) and + state.can_reach(RegionNames.Windmill, "Region", self.player))) + if skill.lower() == "runecraft": + return lambda state: state.has(ItemNames.QP_Rune_Mysteries, self.player) + if skill.lower() == "magic": + return lambda state: state.can_reach(RegionNames.Mind_Runes, "Region", self.player) + + return lambda state: True diff --git a/worlds/osrs/docs/en_Old School Runescape.md b/worlds/osrs/docs/en_Old School Runescape.md new file mode 100644 index 00000000000..d367082b227 --- /dev/null +++ b/worlds/osrs/docs/en_Old School Runescape.md @@ -0,0 +1,114 @@ +# Old School Runescape + +## What is the Goal of this Randomizer? +The goal is to complete the quest "Dragon Slayer I" with limited access to gear and map chunks while following normal +Ironman/Group Ironman restrictions on a fresh free-to-play account. + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a +config file. OSRS contains many options for a highly customizable experience. The options available to you are: + +* **Starting Area** - The starting region of your run. This is the first region you will have available, and you can always +freely return to it (see the section below for when it is allowed to cross locked regions to access it) + * You may select a starting city from the list of Lumbridge, Al Kharid, Varrock (East or West), Edgeville, Falador, +Draynor Village, or The Wilderness (Ferox Enclave) + * The option "Any Bank" will choose one of the above regions at random + * The option "Chunksanity" can start you in _any_ chunk, regardless of whether it has access to a bank. +* **Brutal Grinds** - If enabled, the logic will assume you are willing to go to great lengths to train skills. + * As an example, when enabled, it might be in logic to obtain tin and copper from mob drops and smelt bronze bars to +reach Smithing Level 40 to smelt gold for a task. + * If left disabled, the logic will always ensure you have a reasonable method for training a skill to reach a specific +task, such as having access to intermediate-level training options +* **Progressive Tasks** - If enabled, tasks for a skill are generated in order from earliest to latest. + * For example, your first Smithing task would always be "Smelt an Iron Bar", then "Smelt a Silver Bar", and so on. +You would never have the task "Smelt a Gold Bar" without having every previous Smithing task as well. +This can lead to a more consistent length of run, and is generally shorter than disabling it, but with less variety. +* **Skill Category Weighting Options** + * These are available in each task category (all trainable skills plus "Combat" and "General") + * **Max [Category] Level** - The highest level you intend to have to reach in order to complete all tasks for this +category. For the Combat category, this is the max level of monster you are willing to fight. +General tasks do not have a level and thus do not have this option. + * **Max [Category] Tasks** - The highest number of tasks in this category you are willing to be assigned. +Note that you can end up with _less_ than this amount, but never more. The "General" category is used to fill remaining +spots so a maximum is not specified, instead it has a _minimum_ count. + * **[Category] Task Weighting** - The relative weighting of this category to all of the others. Increase this to make +tasks in this category more likely. + +## What does randomization do to this game? +The OSRS Archipelago Randomizer takes the form of a "Chunkman" account, a form of challenge account +where you are limited to specific regions of the map (known as "chunks") until you complete tasks to unlock +more. The plugin will interface with the [Region Locker Plugin](https://github.com/slaytostay/region-locker) to +visually display these chunk borders and highlight them as locked or unlocked. The optional included GPU plugin for the +Region Locker can tint the locked areas gray, but is incompatible with other GPU plugins such as 117's HD OSRS. +If you choose not to include it, the world map will show locked and unlocked regions instead. + +In order to access a region, you will need to access it entirely through unlocked regions. At no point are you +ever allowed to cross through locked regions, with the following exceptions: +* If your starting region is not Lumbridge, when you complete Tutorial Island, you will need to traverse locked regions +to reach your intended starting location. +* If your starting region is not Lumbridge, you are allowed to "Home Teleport" to your starting region by using the +Lumbridge Home Teleport Spell and then walking to your start location. This is to prevent you from getting "stuck" after +using one-way transportation such as the Port Sarim Jail Teleport from Shantay Pass and being locked out of progression. +* All of your starting Tutorial Island items are assumed to be available at all times. If you have lost an important +item such as a Tinderbox, and cannot re-obtain it in your unlocked region, you are allowed to enter locked regions to +replace it in the least obtrusive way possible. +* If you need to adjust Group Ironman settings, such as adding or removing a member, you may freely access The Node +to do so. + +When passing through locked regions for such exceptions, do not interact with any NPCs, items, or enemies and attempt +to spend as little time in them as possible. + +The plugin will prevent equipping items that you have not unlocked the ability to wield. For example, attempting +to equip an Iron Platebody before the first Progressive Armor unlock will display a chat message and will not +equip the item. + +The plugin will show a list of your current tasks in the sidebar. The plugin will be able to detect the completion +of most tasks, but in the case that a task cannot be detected (for example, killing an enemy with no +drop table such as Deadly Red Spiders), the task can be marked as complete manually by clicking +on the button. This button can also be used to mark completed tasks you have done while playing OSRS mobile or +on a different client without having the plugin available. Simply click the button the next time you are logged in to +Runelite and connected to send the check. + +Due to the nature of randomizing a live MMO with no ability to freely edit the character or adjust game logic or +balancing, this randomizer relies heavily on **the honor system**. The plugin cannot prevent you from walking through +locked regions or equipping locked items with the plugin disabled before connecting. It is important +to acknowledge before starting that the entire purpose of the randomizer is a self-imposed challenge, and there +is little point in cheating by circumventing the plugin's restrictions or marking a task complete without actually +completing it. If you wish to play OSRS with no restrictions, that is always available without the plugin. + +In order to access the AP Text Client commands (such as `!hint` or to chat with other players in the seed), enter your +command in chat prefaced by the string `!ap`. Example commands: + +`!ap buying gf 100k` -> Sends the message "buying gf 100k" to the server +`!ap !hint Area: Lumbridge` -> Attempts to hint for the "Area: Lumbridge" item. Results will appear in your chat box. + +Other server messages, such as chat, will appear in your chat box, prefaced by the Archipelago icon. + +## What items and locations get shuffled? +Items: +- Every map region (at least one chunk but sometimes more) +- Weapon tiers from iron to Rune (bronze is available from the start) +- Armor tiers from iron to Rune (bronze is available from the start) +- Two Spell Tiers (bolt and blast spells) +- Three tiers of Ranged Armor (leather, studded leather + vambraces, green dragonhide) +- Three tiers of Ranged Weapons (oak, willow, maple bows and their respective highest tier of arrows) + +Locations: +* Every Quest is a location that will always be included in every seed +* A random assortment of tasks, separated into categories based on the skill required. +These task categories can have different weights, minimums, and maximums based on your options. + * For a full list of Locations, items, and regions, see the +[Logic Document](https://docs.google.com/spreadsheets/d/1R8Cm8L6YkRWeiN7uYrdru8Vc1DlJ0aFAinH_fwhV8aU/edit?usp=sharing) + +## Which items can be in another player's world? +Any item or region unlock can be found in any player's world. + +## What does another world's item look like in Old School Runescape? +Upon completing a task, the item and recipient will be listed in the player's chatbox. + +## When the player receives an item, what happens? +In addition to the message appearing in the chatbox, a UI window will appear listing the item and who sent it. +These boxes also appear when connecting to a seed already in progress to list the items you have acquired while offline. +The sidebar will list all received items below the task list, starting with regions, then showing the highest tier of +equipment in each category. \ No newline at end of file diff --git a/worlds/osrs/docs/setup_en.md b/worlds/osrs/docs/setup_en.md new file mode 100644 index 00000000000..47c1c8f16fd --- /dev/null +++ b/worlds/osrs/docs/setup_en.md @@ -0,0 +1,58 @@ +# Setup Guide for Old School Runescape + +## Required Software + +- [RuneLite](https://runelite.net/) +- If the account being used has been migrated to a Jagex Account, the [Jagex Launcher](https://www.jagex.com/en-GB/launcher) +will also be necessary to run RuneLite + +## Configuring your YAML file + +### What is a YAML file and why do I need one? + +Your YAML file contains a set of configuration options which provide the generator with information about how it should +generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy +an experience customized for their taste, and different players in the same multiworld can all have different options. + +### Where do I get a YAML file? + +You can customize your settings by visiting the +[Old School Runescape Player Options Page](/games/Old%20School%20Runescape/player-options). + +## Joining a MultiWorld Game + +### Install the RuneLite Plugins +Open RuneLite and click on the wrench icon on the right side. From there, click on the plug icon to access the +Plugin Hub. You will need to install the [Archipelago Plugin](https://github.com/digiholic/osrs-archipelago) +and [Region Locker Plugin](https://github.com/slaytostay/region-locker). The Region Locker plugin +will include three plugins; only the `Region Locker` plugin itself is required. The `Region Locker GPU` plugin can be +used to display locked chunks in gray, but is incompatible with other GPU plugins such as 117's HD OSRS and can be +disabled. + +### Create a new OSRS Account +The OSRS Randomizer assumes you are playing on a newly created f2p Ironman account. As such, you will need to [create a +new Runescape account](https://secure.runescape.com/m=account-creation/create_account?theme=oldschool). + +If you already have a [Jagex Account](https://www.jagex.com/en-GB/accounts) you can add up to 20 characters on +one account through the Jagex Launcher. Note that there is currently no way to _remove_ characters +from a Jagex Account, as such, you might want to create a separate account to hold your Archipelago +characters if you intend to use your main Jagex account for more characters in the future. + +**Protip**: In order to avoid having to remember random email addresses for many accounts, take advantage of an email +alias, a feature supported by most email providers. Any text after a `+` in your email address will redirect to your +normal address, but the email will be recognized by the Jagex login as a new email address. For example, if your email +were `Archipelago@gmail.com`, entering `Archipelago+OSRSRandomizer@gmail.com` would cause the confirmation email to +be sent to your primary address, but the alias can be used to create a new account. One recommendation would be to +include the date of generation in the account, such as `Archipelago+APYYMMDD@gmail.com` for easy memorability. + +After creating an account, you may run through Tutorial Island without connecting; the randomizer has no +effect on the Tutorial. + +### Connect to the Multiserver +In the Archipelago Plugin, enter your server information. The `Auto Reconnect on Login For` field should remain blank; +it will be populated by the character name you first connect with, and it will reconnect to the AP server whenever that +character logs in. Open the Archipelago panel on the right-hand side to connect to the multiworld while logged in to +a game world to associate this character to the randomizer. + +For further information about how to connect to the server in the RuneLite plugin, +please see the [Archipelago Plugin](https://github.com/digiholic/osrs-archipelago) instructions. \ No newline at end of file diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index aa4f6ccf751..abdee26f572 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -52,8 +52,17 @@ class PokemonEmeraldWebWorld(WebWorld): "setup/es", ["nachocua"] ) + + setup_sv = Tutorial( + "Multivärld Installations Guide", + "En guide för att kunna spela Pokémon Emerald med Archipelago.", + "Svenska", + "setup_sv.md", + "setup/sv", + ["Tsukino"] + ) - tutorials = [setup_en, setup_es] + tutorials = [setup_en, setup_es, setup_sv] class PokemonEmeraldSettings(settings.Group): diff --git a/worlds/pokemon_emerald/docs/setup_sv.md b/worlds/pokemon_emerald/docs/setup_sv.md new file mode 100644 index 00000000000..88b1d384096 --- /dev/null +++ b/worlds/pokemon_emerald/docs/setup_sv.md @@ -0,0 +1,78 @@ +# Pokémon Emerald Installationsguide + +## Programvara som behövs + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Ett engelskt Pokémon Emerald ROM, Archipelago kan inte hjälpa dig med detta. +- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 eller senare + +### Konfigurera BizHawk + +När du har installerat BizHawk, öppna `EmuHawk.exe` och ändra följande inställningar: + +- Om du använder BizHawk 2.7 eller 2.8, gå till `Config > Customize`. På "Advanced Tab", byt Lua core från +`NLua+KopiLua` till `Lua+LuaInterface`, starta om EmuHawk efteråt. (Använder du BizHawk 2.9, kan du skippa detta steg.) +- Gå till `Config > Customize`. Markera "Run in background" inställningen för att förhindra bortkoppling från +klienten om du alt-tabbar bort från EmuHawk. +- Öppna en `.gba` fil i EmuHawk och gå till `Config > Controllers…` för att konfigurera dina inputs. +Om du inte hittar `Controllers…`, starta ett valfritt `.gba` ROM först. +- Överväg att rensa keybinds i `Config > Hotkeys…` som du inte tänkt använda. Välj en keybind och tryck på ESC +för att rensa bort den. + +## Extra programvara + +- [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest), +används tillsammans med +[PopTracker](https://github.com/black-sliver/PopTracker/releases) + +## Generera och patcha ett spel + +1. Skapa din konfigurationsfil (YAML). Du kan göra en via att använda +[Pokémon Emerald options hemsida](../../../games/Pokemon%20Emerald/player-options). +2. Följ de allmänna Archipelago instruktionerna för att +[Generera ett spel](../../Archipelago/setup/en#generating-a-game). +Detta kommer generera en fil för dig. Din patchfil kommer ha `.apemerald` som sitt filnamnstillägg. +3. Öppna `ArchipelagoLauncher.exe` +4. Välj "Open Patch" på vänstra sidan, och välj din patchfil. +5. Om detta är första gången du patchar, så kommer du behöva välja var ditt ursprungliga ROM är. +6. En patchad `.gba` fil kommer skapas på samma plats som patchfilen. +7. Första gången du öppnar en patch med BizHawk-klienten, kommer du också behöva bekräfta var `EmuHawk.exe` filen är +installerad i din BizHawk-mapp. + +Om du bara tänkt spela själv och du inte bryr dig om automatisk spårning eller ledtrådar, så kan du stanna här, stänga +av klienten, och starta ditt patchade ROM med valfri emulator. Dock, för multvärldsfunktionen eller andra +Archipelago-funktioner, fortsätt nedanför med BizHawk. + +## Anslut till en server + +Om du vanligtsvis öppnar en patchad fil så görs steg 1-5 automatiskt åt dig. Även om det är så, kom ihåg dessa steg +ifall du till exempel behöver stänga ner och starta om något medans du spelar. + +1. Pokemon Emerald använder Archipelagos BizHawk-klient. Om klienten inte startat efter att du patchat ditt spel, +så kan du bara öppna den igen från launchern. +2. Dubbelkolla att EmuHawk faktiskt startat med den patchade ROM-filen. +3. I EmuHawk, gå till `Tools > Lua Console`. Luakonsolen måste vara igång medans du spelar. +4. I Luakonsolen, Tryck på `Script > Open Script…`. +5. Leta reda på din Archipelago-mapp och i den öppna `data/lua/connector_bizhawk_generic.lua`. +6. Emulatorn och klienten kommer så småningom ansluta till varandra. I BizHawk-klienten kommer du kunna see om allt är +anslutet och att Pokemon Emerald är igenkänt. +7. För att ansluta klienten till en server, skriv in din lobbyadress och port i textfältet t.ex. +`archipelago.gg:38281` +längst upp i din klient och tryck sen på "Connect". + +Du borde nu kunna ta emot och skicka föremål. Du behöver göra dom här stegen varje gång du vill ansluta igen. Det är +helt okej att göra saker offline utan att behöva oroa sig; allt kommer att synkronisera när du ansluter till servern +igen. + +## Automatisk Spårning + +Pokémon Emerald har en fullt fungerande spårare med stöd för automatisk spårning. + +1. Ladda ner [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest) +och +[PopTracker](https://github.com/black-sliver/PopTracker/releases). +2. Placera tracker pack zip-filen i packs/ där du har PopTracker installerat. +3. Öppna PopTracker, och välj Pokemon Emerald. +4. För att automatiskt spåra, tryck på "AP" symbolen längst upp. +5. Skriv in Archipelago-serverns uppgifter (Samma som du använde för att ansluta med klienten), "Slot"-namn samt +lösenord. diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 5f527033289..277d73b2258 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -443,7 +443,7 @@ def pre_fill(self) -> None: self.multiworld.elite_four_pokedex_condition[self.player].total = \ int((len(reachable_mons) / 100) * self.multiworld.elite_four_pokedex_condition[self.player].value) - if self.multiworld.accessibility[self.player] == "locations": + if self.multiworld.accessibility[self.player] == "full": balls = [self.create_item(ball) for ball in ["Poke Ball", "Great Ball", "Ultra Ball"]] traps = [self.create_item(trap) for trap in item_groups["Traps"]] locations = [location for location in self.multiworld.get_locations(self.player) if "Pokedex - " in diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index 251beb59cc1..6aee25df263 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -427,7 +427,7 @@ def __init__(self, flag): LocationData("Seafoam Islands B3F", "Hidden Item Rock", "Max Elixir", rom_addresses['Hidden_Item_Seafoam_Islands_B3F'], Hidden(50), inclusion=hidden_items), LocationData("Vermilion City", "Hidden Item In Water Near Fan Club", "Max Ether", rom_addresses['Hidden_Item_Vermilion_City'], Hidden(51), inclusion=hidden_items), LocationData("Cerulean City-Badge House Backyard", "Hidden Item Gym Badge Guy's Backyard", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_City'], Hidden(52), inclusion=hidden_items), - LocationData("Route 4-E", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items), + LocationData("Route 4-C", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items), LocationData("Oak's Lab", "Oak's Parcel Reward", "Pokedex", rom_addresses["Event_Pokedex"], EventFlag(0x38)), diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index bd6515913ac..9f217e82e64 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -1,4 +1,4 @@ -from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink +from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink, ItemsAccessibility class GameVersion(Choice): @@ -287,7 +287,7 @@ class AllPokemonSeen(Toggle): class DexSanity(NamedRange): """Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to - have checks added. If Accessibility is set to locations, this will be the percentage of all logically reachable + have checks added. If Accessibility is set to full, this will be the percentage of all logically reachable Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage of all 151 Pokemon. If Pokedex is required, the items for Pokemon acquired before acquiring the Pokedex can be found by talking to @@ -418,10 +418,10 @@ class ExpModifier(NamedRange): """Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16.""" display_name = "Exp Modifier" default = 16 - range_start = default / 4 + range_start = default // 4 range_end = 255 special_range_names = { - "half": default / 2, + "half": default // 2, "normal": default, "double": default * 2, "triple": default * 3, @@ -861,6 +861,7 @@ class RandomizePokemonPalettes(Choice): pokemon_rb_options = { + "accessibility": ItemsAccessibility, "game_version": GameVersion, "trainer_name": TrainerName, "rival_name": RivalName, @@ -959,4 +960,4 @@ class RandomizePokemonPalettes(Choice): "ice_trap_weight": IceTrapWeight, "randomize_pokemon_palettes": RandomizePokemonPalettes, "death_link": DeathLink -} \ No newline at end of file +} diff --git a/worlds/pokemon_rb/rules.py b/worlds/pokemon_rb/rules.py index 21dceb75e8d..1d68f314896 100644 --- a/worlds/pokemon_rb/rules.py +++ b/worlds/pokemon_rb/rules.py @@ -22,7 +22,7 @@ def prize_rule(i): item_rules["Celadon Prize Corner - Item Prize 2"] = prize_rule item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule - if multiworld.accessibility[player] != "locations": + if multiworld.accessibility[player] != "full": multiworld.get_location("Cerulean Bicycle Shop", player).always_allow = (lambda state, item: item.name == "Bike Voucher" and item.player == player) diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py index 2e6b018f42f..f0ab9f28313 100644 --- a/worlds/ror2/rules.py +++ b/worlds/ror2/rules.py @@ -31,23 +31,17 @@ def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: # Checks to see if chest/shrine are accessible def has_location_access_rule(multiworld: MultiWorld, environment: str, player: int, item_number: int, item_type: str)\ -> None: - if item_number == 1: - multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \ - lambda state: state.has(environment, player) + location_name = f"{environment}: {item_type} {item_number}" + if item_type == "Scavenger": # scavengers need to be locked till after a full loop since that is when they are capable of spawning. # (While technically the requirement is just beating 5 stages, this will ensure that the player will have # a long enough run to have enough director credits for scavengers and # help prevent being stuck in the same stages until that point). - if item_type == "Scavenger": - multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \ - lambda state: state.has(environment, player) and state.has("Stage 5", player) + multiworld.get_location(location_name, player).access_rule = \ + lambda state: state.has(environment, player) and state.has("Stage 5", player) else: - multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \ - lambda state: check_location(state, environment, player, item_number, item_type) - - -def check_location(state, environment: str, player: int, item_number: int, item_name: str) -> bool: - return state.can_reach(f"{environment}: {item_name} {item_number - 1}", "Location", player) + multiworld.get_location(location_name, player).access_rule = \ + lambda state: state.has(environment, player) def set_rules(ror2_world: "RiskOfRainWorld") -> None: diff --git a/worlds/sc2/requirements.txt b/worlds/sc2/requirements.txt index 9b84863c459..5bc808b639d 100644 --- a/worlds/sc2/requirements.txt +++ b/worlds/sc2/requirements.txt @@ -1,2 +1 @@ nest-asyncio >= 1.5.5 -six >= 1.16.0 \ No newline at end of file diff --git a/worlds/shivers/Items.py b/worlds/shivers/Items.py index 3b403be5cb7..10d234d450b 100644 --- a/worlds/shivers/Items.py +++ b/worlds/shivers/Items.py @@ -33,28 +33,38 @@ class ItemData(typing.NamedTuple): "Lightning Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 17, "pot"), "Sand Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 18, "pot"), "Metal Pot Top": ItemData(SHIVERS_ITEM_ID_OFFSET + 19, "pot"), + "Water Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 20, "pot_type2"), + "Wax Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 21, "pot_type2"), + "Ash Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 22, "pot_type2"), + "Oil Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 23, "pot_type2"), + "Cloth Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 24, "pot_type2"), + "Wood Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 25, "pot_type2"), + "Crystal Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 26, "pot_type2"), + "Lightning Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 27, "pot_type2"), + "Sand Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 28, "pot_type2"), + "Metal Pot Complete": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "pot_type2"), #Keys - "Key for Office Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 20, "key"), - "Key for Bedroom Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 21, "key"), - "Key for Three Floor Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 22, "key"), - "Key for Workshop": ItemData(SHIVERS_ITEM_ID_OFFSET + 23, "key"), - "Key for Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 24, "key"), - "Key for Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 25, "key"), - "Key for Greenhouse Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 26, "key"), - "Key for Ocean Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 27, "key"), - "Key for Projector Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 28, "key"), - "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "key"), - "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), - "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), - "Key for Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), - "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), - "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), - "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), - "Key for Bedroom": ItemData(SHIVERS_ITEM_ID_OFFSET + 36, "key"), - "Key for Underground Lake Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 37, "key"), - "Key for Janitor Closet": ItemData(SHIVERS_ITEM_ID_OFFSET + 38, "key"), - "Key for Front Door": ItemData(SHIVERS_ITEM_ID_OFFSET + 39, "key-optional"), + "Key for Office Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), + "Key for Bedroom Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), + "Key for Three Floor Elevator": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), + "Key for Workshop": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), + "Key for Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), + "Key for Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), + "Key for Greenhouse Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 36, "key"), + "Key for Ocean Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 37, "key"), + "Key for Projector Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 38, "key"), + "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 39, "key"), + "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 40, "key"), + "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 41, "key"), + "Key for Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 42, "key"), + "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 43, "key"), + "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 44, "key"), + "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 45, "key"), + "Key for Bedroom": ItemData(SHIVERS_ITEM_ID_OFFSET + 46, "key"), + "Key for Underground Lake Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 47, "key"), + "Key for Janitor Closet": ItemData(SHIVERS_ITEM_ID_OFFSET + 48, "key"), + "Key for Front Door": ItemData(SHIVERS_ITEM_ID_OFFSET + 49, "key-optional"), #Abilities "Crawling": ItemData(SHIVERS_ITEM_ID_OFFSET + 50, "ability"), @@ -83,6 +93,16 @@ class ItemData(typing.NamedTuple): "Lightning Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 87, "potduplicate"), "Sand Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 88, "potduplicate"), "Metal Pot Top DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 89, "potduplicate"), + "Water Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 140, "potduplicate_type2"), + "Wax Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 141, "potduplicate_type2"), + "Ash Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 142, "potduplicate_type2"), + "Oil Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 143, "potduplicate_type2"), + "Cloth Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 144, "potduplicate_type2"), + "Wood Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 145, "potduplicate_type2"), + "Crystal Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 146, "potduplicate_type2"), + "Lightning Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 147, "potduplicate_type2"), + "Sand Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 148, "potduplicate_type2"), + "Metal Pot Complete DUPE": ItemData(SHIVERS_ITEM_ID_OFFSET + 149, "potduplicate_type2"), #Filler "Empty": ItemData(SHIVERS_ITEM_ID_OFFSET + 90, "filler"), diff --git a/worlds/shivers/Options.py b/worlds/shivers/Options.py index b70882f9a54..2f33eb18e5d 100644 --- a/worlds/shivers/Options.py +++ b/worlds/shivers/Options.py @@ -1,21 +1,37 @@ -from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions +from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions, Range from dataclasses import dataclass +class IxupiCapturesNeeded(Range): + """ + Number of Ixupi Captures needed for goal condition. + """ + display_name = "Number of Ixupi Captures Needed" + range_start = 1 + range_end = 10 + default = 10 + class LobbyAccess(Choice): - """Chooses how keys needed to reach the lobby are placed. + """ + Chooses how keys needed to reach the lobby are placed. - Normal: Keys are placed anywhere - Early: Keys are placed early - - Local: Keys are placed locally""" + - Local: Keys are placed locally + """ display_name = "Lobby Access" option_normal = 0 option_early = 1 option_local = 2 + default = 1 class PuzzleHintsRequired(DefaultOnToggle): - """If turned on puzzle hints will be available before the corresponding puzzle is required. For example: The Shaman - Drums puzzle will be placed after access to the security cameras which give you the solution. Turning this off - allows for greater randomization.""" + """ + If turned on puzzle hints/solutions will be available before the corresponding puzzle is required. + + For example: The Red Door puzzle will be logically required only after access to the Beth's Address Book which gives you the solution. + + Turning this off allows for greater randomization. + """ display_name = "Puzzle Hints Required" class InformationPlaques(Toggle): @@ -26,7 +42,9 @@ class InformationPlaques(Toggle): display_name = "Include Information Plaques" class FrontDoorUsable(Toggle): - """Adds a key to unlock the front door of the museum.""" + """ + Adds a key to unlock the front door of the museum. + """ display_name = "Front Door Usable" class ElevatorsStaySolved(DefaultOnToggle): @@ -37,7 +55,9 @@ class ElevatorsStaySolved(DefaultOnToggle): display_name = "Elevators Stay Solved" class EarlyBeth(DefaultOnToggle): - """Beth's body is open at the start of the game. This allows any pot piece to be placed in the slide and early checks on the second half of the final riddle.""" + """ + Beth's body is open at the start of the game. This allows any pot piece to be placed in the slide and early checks on the second half of the final riddle. + """ display_name = "Early Beth" class EarlyLightning(Toggle): @@ -47,9 +67,34 @@ class EarlyLightning(Toggle): """ display_name = "Early Lightning" +class LocationPotPieces(Choice): + """ + Chooses where pot pieces will be located within the multiworld. + - Own World: Pot pieces will be located within your own world + - Different World: Pot pieces will be located in another world + - Any World: Pot pieces will be located in any world + """ + display_name = "Location of Pot Pieces" + option_own_world = 0 + option_different_world = 1 + option_any_world = 2 + +class FullPots(Choice): + """ + Chooses if pots will be in pieces or already completed + - Pieces: Only pot pieces will be added to the item pool + - Complete: Only completed pots will be added to the item pool + - Mixed: Each pot will be randomly chosen to be pieces or already completed. + """ + display_name = "Full Pots" + option_pieces = 0 + option_complete = 1 + option_mixed = 2 + @dataclass class ShiversOptions(PerGameCommonOptions): + ixupi_captures_needed: IxupiCapturesNeeded lobby_access: LobbyAccess puzzle_hints_required: PuzzleHintsRequired include_information_plaques: InformationPlaques @@ -57,3 +102,5 @@ class ShiversOptions(PerGameCommonOptions): elevators_stay_solved: ElevatorsStaySolved early_beth: EarlyBeth early_lightning: EarlyLightning + location_pot_pieces: LocationPotPieces + full_pots: FullPots diff --git a/worlds/shivers/Rules.py b/worlds/shivers/Rules.py index 3dc4f51c47a..5288fa2c9c3 100644 --- a/worlds/shivers/Rules.py +++ b/worlds/shivers/Rules.py @@ -8,58 +8,58 @@ def water_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Lobby", "Region", player) or (state.can_reach("Janitor Closet", "Region", player) and cloth_capturable(state, player))) \ - and state.has_all({"Water Pot Bottom", "Water Pot Top", "Water Pot Bottom DUPE", "Water Pot Top DUPE"}, player) + return state.has_all({"Water Pot Bottom", "Water Pot Top", "Water Pot Bottom DUPE", "Water Pot Top DUPE"}, player) or \ + state.has_all({"Water Pot Complete", "Water Pot Complete DUPE"}, player) def wax_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Library", "Region", player) or state.can_reach("Anansi", "Region", player)) \ - and state.has_all({"Wax Pot Bottom", "Wax Pot Top", "Wax Pot Bottom DUPE", "Wax Pot Top DUPE"}, player) + return state.has_all({"Wax Pot Bottom", "Wax Pot Top", "Wax Pot Bottom DUPE", "Wax Pot Top DUPE"}, player) or \ + state.has_all({"Wax Pot Complete", "Wax Pot Complete DUPE"}, player) def ash_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Office", "Region", player) or state.can_reach("Burial", "Region", player)) \ - and state.has_all({"Ash Pot Bottom", "Ash Pot Top", "Ash Pot Bottom DUPE", "Ash Pot Top DUPE"}, player) + return state.has_all({"Ash Pot Bottom", "Ash Pot Top", "Ash Pot Bottom DUPE", "Ash Pot Top DUPE"}, player) or \ + state.has_all({"Ash Pot Complete", "Ash Pot Complete DUPE"}, player) def oil_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Prehistoric", "Region", player) or state.can_reach("Tar River", "Region", player)) \ - and state.has_all({"Oil Pot Bottom", "Oil Pot Top", "Oil Pot Bottom DUPE", "Oil Pot Top DUPE"}, player) + return state.has_all({"Oil Pot Bottom", "Oil Pot Top", "Oil Pot Bottom DUPE", "Oil Pot Top DUPE"}, player) or \ + state.has_all({"Oil Pot Complete", "Oil Pot Complete DUPE"}, player) def cloth_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Egypt", "Region", player) or state.can_reach("Burial", "Region", player) or state.can_reach("Janitor Closet", "Region", player)) \ - and state.has_all({"Cloth Pot Bottom", "Cloth Pot Top", "Cloth Pot Bottom DUPE", "Cloth Pot Top DUPE"}, player) + return state.has_all({"Cloth Pot Bottom", "Cloth Pot Top", "Cloth Pot Bottom DUPE", "Cloth Pot Top DUPE"}, player) or \ + state.has_all({"Cloth Pot Complete", "Cloth Pot Complete DUPE"}, player) def wood_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Workshop", "Region", player) or state.can_reach("Blue Maze", "Region", player) or state.can_reach("Gods Room", "Region", player) or state.can_reach("Anansi", "Region", player)) \ - and state.has_all({"Wood Pot Bottom", "Wood Pot Top", "Wood Pot Bottom DUPE", "Wood Pot Top DUPE"}, player) + return state.has_all({"Wood Pot Bottom", "Wood Pot Top", "Wood Pot Bottom DUPE", "Wood Pot Top DUPE"}, player) or \ + state.has_all({"Wood Pot Complete", "Wood Pot Complete DUPE"}, player) def crystal_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Lobby", "Region", player) or state.can_reach("Ocean", "Region", player)) \ - and state.has_all({"Crystal Pot Bottom", "Crystal Pot Top", "Crystal Pot Bottom DUPE", "Crystal Pot Top DUPE"}, player) + return state.has_all({"Crystal Pot Bottom", "Crystal Pot Top", "Crystal Pot Bottom DUPE", "Crystal Pot Top DUPE"}, player) or \ + state.has_all({"Crystal Pot Complete", "Crystal Pot Complete DUPE"}, player) def sand_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Greenhouse", "Region", player) or state.can_reach("Ocean", "Region", player)) \ - and state.has_all({"Sand Pot Bottom", "Sand Pot Top", "Sand Pot Bottom DUPE", "Sand Pot Top DUPE"}, player) + return state.has_all({"Sand Pot Bottom", "Sand Pot Top", "Sand Pot Bottom DUPE", "Sand Pot Top DUPE"}, player) or \ + state.has_all({"Sand Pot Complete", "Sand Pot Complete DUPE"}, player) def metal_capturable(state: CollectionState, player: int) -> bool: - return (state.can_reach("Projector Room", "Region", player) or state.can_reach("Prehistoric", "Region", player) or state.can_reach("Bedroom", "Region", player)) \ - and state.has_all({"Metal Pot Bottom", "Metal Pot Top", "Metal Pot Bottom DUPE", "Metal Pot Top DUPE"}, player) + return state.has_all({"Metal Pot Bottom", "Metal Pot Top", "Metal Pot Bottom DUPE", "Metal Pot Top DUPE"}, player) or \ + state.has_all({"Metal Pot Complete", "Metal Pot Complete DUPE"}, player) def lightning_capturable(state: CollectionState, player: int) -> bool: - return (first_nine_ixupi_capturable or state.multiworld.early_lightning[player].value) \ - and state.can_reach("Generator", "Region", player) \ - and state.has_all({"Lightning Pot Bottom", "Lightning Pot Top", "Lightning Pot Bottom DUPE", "Lightning Pot Top DUPE"}, player) + return (first_nine_ixupi_capturable(state, player) or state.multiworld.worlds[player].options.early_lightning.value) \ + and (state.has_all({"Lightning Pot Bottom", "Lightning Pot Top", "Lightning Pot Bottom DUPE", "Lightning Pot Top DUPE"}, player) or \ + state.has_all({"Lightning Pot Complete", "Lightning Pot Complete DUPE"}, player)) def beths_body_available(state: CollectionState, player: int) -> bool: - return (first_nine_ixupi_capturable(state, player) or state.multiworld.early_beth[player].value) \ + return (first_nine_ixupi_capturable(state, player) or state.multiworld.worlds[player].options.early_beth.value) \ and state.can_reach("Generator", "Region", player) @@ -123,7 +123,8 @@ def get_rules_lookup(player: int): "To Burial From Egypt": lambda state: state.can_reach("Egypt", "Region", player), "To Gods Room From Anansi": lambda state: state.can_reach("Gods Room", "Region", player), "To Slide Room": lambda state: all_skull_dials_available(state, player), - "To Lobby From Slide Room": lambda state: (beths_body_available(state, player)) + "To Lobby From Slide Room": lambda state: beths_body_available(state, player), + "To Water Capture From Janitor Closet": lambda state: cloth_capturable(state, player) }, "locations_required": { "Puzzle Solved Anansi Musicbox": lambda state: state.can_reach("Clock Tower", "Region", player), @@ -207,8 +208,10 @@ def set_rules(world: "ShiversWorld") -> None: # forbid cloth in janitor closet and oil in tar river forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Bottom DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Top DUPE", player) + forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Complete DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Bottom DUPE", player) forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Top DUPE", player) + forbid_item(multiworld.get_location("Accessible: Storage: Tar River", player), "Oil Pot Complete DUPE", player) # Filler Item Forbids forbid_item(multiworld.get_location("Puzzle Solved Lyre", player), "Easier Lyre", player) @@ -234,4 +237,8 @@ def set_rules(world: "ShiversWorld") -> None: forbid_item(multiworld.get_location("Ixupi Captured Metal", player), "Metal Always Available in Prehistoric", player) # Set completion condition - multiworld.completion_condition[player] = lambda state: (first_nine_ixupi_capturable(state, player) and lightning_capturable(state, player)) + multiworld.completion_condition[player] = lambda state: (( + water_capturable(state, player) + wax_capturable(state, player) + ash_capturable(state, player) \ + + oil_capturable(state, player) + cloth_capturable(state, player) + wood_capturable(state, player) \ + + crystal_capturable(state, player) + sand_capturable(state, player) + metal_capturable(state, player) \ + + lightning_capturable(state, player)) >= world.options.ixupi_captures_needed.value) diff --git a/worlds/shivers/__init__.py b/worlds/shivers/__init__.py index e43e91fb5ae..a2d7bc14644 100644 --- a/worlds/shivers/__init__.py +++ b/worlds/shivers/__init__.py @@ -1,3 +1,4 @@ +from typing import List from .Items import item_table, ShiversItem from .Rules import set_rules from BaseClasses import Item, Tutorial, Region, Location @@ -22,7 +23,7 @@ class ShiversWorld(World): Shivers is a horror themed point and click adventure. Explore the mysteries of Windlenot's Museum of the Strange and Unusual. """ - game: str = "Shivers" + game = "Shivers" topology_present = False web = ShiversWeb() options_dataclass = ShiversOptions @@ -30,7 +31,13 @@ class ShiversWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = Constants.location_name_to_id - + shivers_item_id_offset = 27000 + pot_completed_list: List[int] + + + def generate_early(self): + self.pot_completed_list = [] + def create_item(self, name: str) -> Item: data = item_table[name] return ShiversItem(name, data.classification, data.code, self.player) @@ -78,9 +85,28 @@ def create_items(self) -> None: #Add items to item pool itempool = [] for name, data in item_table.items(): - if data.type in {"pot", "key", "ability", "filler2"}: + if data.type in {"key", "ability", "filler2"}: itempool.append(self.create_item(name)) + # Pot pieces/Completed/Mixed: + for i in range(10): + if self.options.full_pots == "pieces": + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + i])) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 10 + i])) + elif self.options.full_pots == "complete": + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 20 + i])) + else: + # Roll for if pieces or a complete pot will be used. + # Pot Pieces + if self.random.randint(0, 1) == 0: + self.pot_completed_list.append(0) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + i])) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 10 + i])) + # Completed Pot + else: + self.pot_completed_list.append(1) + itempool.append(self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 20 + i])) + #Add Filler itempool += [self.create_item("Easier Lyre") for i in range(9)] @@ -88,7 +114,6 @@ def create_items(self) -> None: filler_needed = len(self.multiworld.get_unfilled_locations(self.player)) - 24 - len(itempool) itempool += [self.random.choices([self.create_item("Heal"), self.create_item("Easier Lyre")], weights=[95, 5])[0] for i in range(filler_needed)] - #Place library escape items. Choose a location to place the escape item library_region = self.multiworld.get_region("Library", self.player) librarylocation = self.random.choice([loc for loc in library_region.locations if not loc.name.startswith("Accessible:")]) @@ -123,14 +148,14 @@ def create_items(self) -> None: self.multiworld.itempool += itempool #Lobby acess: - if self.options.lobby_access == 1: + if self.options.lobby_access == "early": if lobby_access_keys == 1: self.multiworld.early_items[self.player]["Key for Underground Lake Room"] = 1 self.multiworld.early_items[self.player]["Key for Office Elevator"] = 1 self.multiworld.early_items[self.player]["Key for Office"] = 1 elif lobby_access_keys == 2: self.multiworld.early_items[self.player]["Key for Front Door"] = 1 - if self.options.lobby_access == 2: + if self.options.lobby_access == "local": if lobby_access_keys == 1: self.multiworld.local_early_items[self.player]["Key for Underground Lake Room"] = 1 self.multiworld.local_early_items[self.player]["Key for Office Elevator"] = 1 @@ -138,6 +163,12 @@ def create_items(self) -> None: elif lobby_access_keys == 2: self.multiworld.local_early_items[self.player]["Key for Front Door"] = 1 + #Pot piece shuffle location: + if self.options.location_pot_pieces == "own_world": + self.options.local_items.value |= {name for name, data in item_table.items() if data.type == "pot" or data.type == "pot_type2"} + if self.options.location_pot_pieces == "different_world": + self.options.non_local_items.value |= {name for name, data in item_table.items() if data.type == "pot" or data.type == "pot_type2"} + def pre_fill(self) -> None: # Prefills event storage locations with duplicate pots storagelocs = [] @@ -149,7 +180,23 @@ def pre_fill(self) -> None: if loc_name.startswith("Accessible: "): storagelocs.append(self.multiworld.get_location(loc_name, self.player)) - storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate'] + #Pot pieces/Completed/Mixed: + if self.options.full_pots == "pieces": + storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate'] + elif self.options.full_pots == "complete": + storageitems += [self.create_item(name) for name, data in item_table.items() if data.type == 'potduplicate_type2'] + storageitems += [self.create_item("Empty") for i in range(10)] + else: + for i in range(10): + #Pieces + if self.pot_completed_list[i] == 0: + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 70 + i])] + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 80 + i])] + #Complete + else: + storageitems += [self.create_item(self.item_id_to_name[self.shivers_item_id_offset + 140 + i])] + storageitems += [self.create_item("Empty")] + storageitems += [self.create_item("Empty") for i in range(3)] state = self.multiworld.get_all_state(True) @@ -166,11 +213,13 @@ def pre_fill(self) -> None: def fill_slot_data(self) -> dict: return { - "storageplacements": self.storage_placements, - "excludedlocations": {str(excluded_location).replace('ExcludeLocations(', '').replace(')', '') for excluded_location in self.multiworld.exclude_locations.values()}, - "elevatorsstaysolved": {self.options.elevators_stay_solved.value}, - "earlybeth": {self.options.early_beth.value}, - "earlylightning": {self.options.early_lightning.value}, + "StoragePlacements": self.storage_placements, + "ExcludedLocations": list(self.options.exclude_locations.value), + "IxupiCapturesNeeded": self.options.ixupi_captures_needed.value, + "ElevatorsStaySolved": self.options.elevators_stay_solved.value, + "EarlyBeth": self.options.early_beth.value, + "EarlyLightning": self.options.early_lightning.value, + "FrontDoorUsable": self.options.front_door_usable.value } diff --git a/worlds/shivers/data/locations.json b/worlds/shivers/data/locations.json index 1d62f85d2d1..64fe3647348 100644 --- a/worlds/shivers/data/locations.json +++ b/worlds/shivers/data/locations.json @@ -81,7 +81,7 @@ "Information Plaque: (Ocean) Poseidon", "Information Plaque: (Ocean) Colossus of Rhodes", "Information Plaque: (Ocean) Poseidon's Temple", - "Information Plaque: (Underground Maze) Subterranean World", + "Information Plaque: (Underground Maze Staircase) Subterranean World", "Information Plaque: (Underground Maze) Dero", "Information Plaque: (Egypt) Tomb of the Ixupi", "Information Plaque: (Egypt) The Sphinx", @@ -119,16 +119,6 @@ "Outside": [ "Puzzle Solved Gears", "Puzzle Solved Stone Henge", - "Ixupi Captured Water", - "Ixupi Captured Wax", - "Ixupi Captured Ash", - "Ixupi Captured Oil", - "Ixupi Captured Cloth", - "Ixupi Captured Wood", - "Ixupi Captured Crystal", - "Ixupi Captured Sand", - "Ixupi Captured Metal", - "Ixupi Captured Lightning", "Puzzle Solved Office Elevator", "Puzzle Solved Three Floor Elevator", "Puzzle Hint Found: Combo Lock in Mailbox", @@ -182,7 +172,8 @@ "Accessible: Storage: Transforming Mask" ], "Generator": [ - "Final Riddle: Beth's Body Page 17" + "Final Riddle: Beth's Body Page 17", + "Ixupi Captured Lightning" ], "Theater Back Hallways": [ "Puzzle Solved Clock Tower Door" @@ -210,6 +201,7 @@ "Information Plaque: (Ocean) Poseidon's Temple" ], "Maze Staircase": [ + "Information Plaque: (Underground Maze Staircase) Subterranean World", "Puzzle Solved Maze Door" ], "Egypt": [ @@ -305,7 +297,6 @@ ], "Tar River": [ "Accessible: Storage: Tar River", - "Information Plaque: (Underground Maze) Subterranean World", "Information Plaque: (Underground Maze) Dero" ], "Theater": [ @@ -320,6 +311,33 @@ "Skull Dial Bridge": [ "Accessible: Storage: Skull Bridge", "Puzzle Solved Skull Dial Door" + ], + "Water Capture": [ + "Ixupi Captured Water" + ], + "Wax Capture": [ + "Ixupi Captured Wax" + ], + "Ash Capture": [ + "Ixupi Captured Ash" + ], + "Oil Capture": [ + "Ixupi Captured Oil" + ], + "Cloth Capture": [ + "Ixupi Captured Cloth" + ], + "Wood Capture": [ + "Ixupi Captured Wood" + ], + "Crystal Capture": [ + "Ixupi Captured Crystal" + ], + "Sand Capture": [ + "Ixupi Captured Sand" + ], + "Metal Capture": [ + "Ixupi Captured Metal" ] } -} +} diff --git a/worlds/shivers/data/regions.json b/worlds/shivers/data/regions.json index 963d100fadd..aeb5aa73736 100644 --- a/worlds/shivers/data/regions.json +++ b/worlds/shivers/data/regions.json @@ -7,35 +7,35 @@ ["Underground Lake", ["To Underground Tunnels From Underground Lake", "To Underground Blue Tunnels From Underground Lake"]], ["Underground Blue Tunnels", ["To Underground Lake From Underground Blue Tunnels", "To Office Elevator From Underground Blue Tunnels"]], ["Office Elevator", ["To Underground Blue Tunnels From Office Elevator","To Office From Office Elevator"]], - ["Office", ["To Office Elevator From Office", "To Workshop", "To Lobby From Office", "To Bedroom Elevator From Office"]], - ["Workshop", ["To Office From Workshop"]], + ["Office", ["To Office Elevator From Office", "To Workshop", "To Lobby From Office", "To Bedroom Elevator From Office", "To Ash Capture From Office"]], + ["Workshop", ["To Office From Workshop", "To Wood Capture From Workshop"]], ["Bedroom Elevator", ["To Office From Bedroom Elevator", "To Bedroom"]], - ["Bedroom", ["To Bedroom Elevator From Bedroom"]], - ["Lobby", ["To Office From Lobby", "To Library From Lobby", "To Theater From Lobby", "To Prehistoric From Lobby", "To Egypt From Lobby", "To Tar River From Lobby", "To Outside From Lobby"]], - ["Library", ["To Lobby From Library", "To Maintenance Tunnels From Library"]], + ["Bedroom", ["To Bedroom Elevator From Bedroom", "To Metal Capture From Bedroom"]], + ["Lobby", ["To Office From Lobby", "To Library From Lobby", "To Theater From Lobby", "To Prehistoric From Lobby", "To Egypt From Lobby", "To Tar River From Lobby", "To Outside From Lobby", "To Water Capture From Lobby", "To Crystal Capture From Lobby"]], + ["Library", ["To Lobby From Library", "To Maintenance Tunnels From Library", "To Wax Capture From Library"]], ["Maintenance Tunnels", ["To Library From Maintenance Tunnels", "To Three Floor Elevator From Maintenance Tunnels", "To Generator"]], ["Generator", ["To Maintenance Tunnels From Generator"]], ["Theater", ["To Lobby From Theater", "To Theater Back Hallways From Theater"]], ["Theater Back Hallways", ["To Theater From Theater Back Hallways", "To Clock Tower Staircase From Theater Back Hallways", "To Maintenance Tunnels From Theater Back Hallways", "To Projector Room"]], ["Clock Tower Staircase", ["To Theater Back Hallways From Clock Tower Staircase", "To Clock Tower"]], ["Clock Tower", ["To Clock Tower Staircase From Clock Tower"]], - ["Projector Room", ["To Theater Back Hallways From Projector Room"]], - ["Prehistoric", ["To Lobby From Prehistoric", "To Greenhouse", "To Ocean From Prehistoric"]], - ["Greenhouse", ["To Prehistoric From Greenhouse"]], - ["Ocean", ["To Prehistoric From Ocean", "To Maze Staircase From Ocean"]], + ["Projector Room", ["To Theater Back Hallways From Projector Room", "To Metal Capture From Projector Room"]], + ["Prehistoric", ["To Lobby From Prehistoric", "To Greenhouse", "To Ocean From Prehistoric", "To Oil Capture From Prehistoric", "To Metal Capture From Prehistoric"]], + ["Greenhouse", ["To Prehistoric From Greenhouse", "To Sand Capture From Greenhouse"]], + ["Ocean", ["To Prehistoric From Ocean", "To Maze Staircase From Ocean", "To Crystal Capture From Ocean", "To Sand Capture From Ocean"]], ["Maze Staircase", ["To Ocean From Maze Staircase", "To Maze From Maze Staircase"]], ["Maze", ["To Maze Staircase From Maze", "To Tar River"]], - ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River"]], - ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt"]], - ["Burial", ["To Egypt From Burial", "To Shaman From Burial"]], - ["Shaman", ["To Burial From Shaman", "To Gods Room"]], - ["Gods Room", ["To Shaman From Gods Room", "To Anansi From Gods Room"]], - ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi"]], + ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River", "To Oil Capture From Tar River"]], + ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt", "To Cloth Capture From Egypt"]], + ["Burial", ["To Egypt From Burial", "To Shaman From Burial", "To Ash Capture From Burial", "To Cloth Capture From Burial"]], + ["Shaman", ["To Burial From Shaman", "To Gods Room", "To Wax Capture From Shaman"]], + ["Gods Room", ["To Shaman From Gods Room", "To Anansi From Gods Room", "To Wood Capture From Gods Room"]], + ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi", "To Wax Capture From Anansi", "To Wood Capture From Anansi"]], ["Werewolf", ["To Anansi From Werewolf", "To Night Staircase From Werewolf"]], ["Night Staircase", ["To Werewolf From Night Staircase", "To Janitor Closet", "To UFO"]], - ["Janitor Closet", ["To Night Staircase From Janitor Closet"]], + ["Janitor Closet", ["To Night Staircase From Janitor Closet", "To Water Capture From Janitor Closet", "To Cloth Capture From Janitor Closet"]], ["UFO", ["To Night Staircase From UFO", "To Inventions From UFO"]], - ["Blue Maze", ["To Egypt From Blue Maze", "To Three Floor Elevator From Blue Maze Bottom", "To Three Floor Elevator From Blue Maze Top", "To Fortune Teller", "To Inventions From Blue Maze"]], + ["Blue Maze", ["To Egypt From Blue Maze", "To Three Floor Elevator From Blue Maze Bottom", "To Three Floor Elevator From Blue Maze Top", "To Fortune Teller", "To Inventions From Blue Maze", "To Wood Capture From Blue Maze"]], ["Three Floor Elevator", ["To Maintenance Tunnels From Three Floor Elevator", "To Blue Maze From Three Floor Elevator"]], ["Fortune Teller", ["To Blue Maze From Fortune Teller"]], ["Inventions", ["To Blue Maze From Inventions", "To UFO From Inventions", "To Torture From Inventions"]], @@ -43,7 +43,16 @@ ["Puzzle Room Mastermind", ["To Torture", "To Puzzle Room Marbles From Puzzle Room Mastermind"]], ["Puzzle Room Marbles", ["To Puzzle Room Mastermind From Puzzle Room Marbles", "To Skull Dial Bridge From Puzzle Room Marbles"]], ["Skull Dial Bridge", ["To Puzzle Room Marbles From Skull Dial Bridge", "To Slide Room"]], - ["Slide Room", ["To Skull Dial Bridge From Slide Room", "To Lobby From Slide Room"]] + ["Slide Room", ["To Skull Dial Bridge From Slide Room", "To Lobby From Slide Room"]], + ["Water Capture", []], + ["Wax Capture", []], + ["Ash Capture", []], + ["Oil Capture", []], + ["Cloth Capture", []], + ["Wood Capture", []], + ["Crystal Capture", []], + ["Sand Capture", []], + ["Metal Capture", []] ], "mandatory_connections": [ ["To Registry", "Registry"], @@ -140,6 +149,29 @@ ["To Puzzle Room Marbles From Skull Dial Bridge", "Puzzle Room Marbles"], ["To Skull Dial Bridge From Puzzle Room Marbles", "Skull Dial Bridge"], ["To Skull Dial Bridge From Slide Room", "Skull Dial Bridge"], - ["To Slide Room", "Slide Room"] + ["To Slide Room", "Slide Room"], + ["To Wax Capture From Library", "Wax Capture"], + ["To Wax Capture From Shaman", "Wax Capture"], + ["To Wax Capture From Anansi", "Wax Capture"], + ["To Water Capture From Lobby", "Water Capture"], + ["To Water Capture From Janitor Closet", "Water Capture"], + ["To Ash Capture From Office", "Ash Capture"], + ["To Ash Capture From Burial", "Ash Capture"], + ["To Oil Capture From Prehistoric", "Oil Capture"], + ["To Oil Capture From Tar River", "Oil Capture"], + ["To Cloth Capture From Egypt", "Cloth Capture"], + ["To Cloth Capture From Burial", "Cloth Capture"], + ["To Cloth Capture From Janitor Closet", "Cloth Capture"], + ["To Wood Capture From Workshop", "Wood Capture"], + ["To Wood Capture From Gods Room", "Wood Capture"], + ["To Wood Capture From Anansi", "Wood Capture"], + ["To Wood Capture From Blue Maze", "Wood Capture"], + ["To Crystal Capture From Lobby", "Crystal Capture"], + ["To Crystal Capture From Ocean", "Crystal Capture"], + ["To Sand Capture From Greenhouse", "Sand Capture"], + ["To Sand Capture From Ocean", "Sand Capture"], + ["To Metal Capture From Bedroom", "Metal Capture"], + ["To Metal Capture From Projector Room", "Metal Capture"], + ["To Metal Capture From Prehistoric", "Metal Capture"] ] -} \ No newline at end of file +} diff --git a/worlds/shivers/docs/en_Shivers.md b/worlds/shivers/docs/en_Shivers.md index a92f8a6b791..2c56152a7a0 100644 --- a/worlds/shivers/docs/en_Shivers.md +++ b/worlds/shivers/docs/en_Shivers.md @@ -12,8 +12,8 @@ these are randomized. Crawling has been added and is required to use any crawl s ## What is considered a location check in Shivers? -1. All puzzle solves are location checks excluding elevator puzzles. -2. All Ixupi captures are location checks excluding Lightning. +1. All puzzle solves are location checks. +2. All Ixupi captures are location checks. 3. Puzzle hints/solutions are location checks. For example, looking at the Atlantis map. 4. Optionally information plaques are location checks. @@ -23,9 +23,9 @@ If the player receives a key then the corresponding door will be unlocked. If th ## What is the victory condition? -Victory is achieved when the player captures Lightning in the generator room. +Victory is achieved when the player has captured the required number Ixupi set in their options. ## Encountered a bug? -Please contact GodlFire on Discord for bugs related to Shivers world generation.\ +Please contact GodlFire on Discord for bugs related to Shivers world generation.
Please contact GodlFire or mouse on Discord for bugs related to the Shivers Randomizer. diff --git a/worlds/shivers/docs/setup_en.md b/worlds/shivers/docs/setup_en.md index 187382ef643..c53edcdf2b5 100644 --- a/worlds/shivers/docs/setup_en.md +++ b/worlds/shivers/docs/setup_en.md @@ -5,7 +5,7 @@ - [Shivers (GOG version)](https://www.gog.com/en/game/shivers) or original disc - [ScummVM](https://www.scummvm.org/downloads/) version 2.7.0 or later -- [Shivers Randomizer](https://www.speedrun.com/shivers/resources) +- [Shivers Randomizer](https://github.com/GodlFire/Shivers-Randomizer-CSharp/releases/latest) Latest release version ## Setup ScummVM for Shivers diff --git a/worlds/shorthike/Locations.py b/worlds/shorthike/Locations.py index 319ad8f20e1..657035a0301 100644 --- a/worlds/shorthike/Locations.py +++ b/worlds/shorthike/Locations.py @@ -328,7 +328,7 @@ class LocationInfo(TypedDict): {"name": "Boat Rental", "id": base_id + 55, "inGameId": "DadDeer[0]", - "needsShovel": False, "purchase": True, + "needsShovel": False, "purchase": 100, "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, {"name": "Boat Challenge Reward", "id": base_id + 56, diff --git a/worlds/smz3/Options.py b/worlds/smz3/Options.py index ada463fa362..8c5efc431f5 100644 --- a/worlds/smz3/Options.py +++ b/worlds/smz3/Options.py @@ -1,5 +1,5 @@ import typing -from Options import Choice, Option, Toggle, DefaultOnToggle, Range +from Options import Choice, Option, Toggle, DefaultOnToggle, Range, ItemsAccessibility class SMLogic(Choice): """This option selects what kind of logic to use for item placement inside @@ -128,6 +128,7 @@ class EnergyBeep(DefaultOnToggle): smz3_options: typing.Dict[str, type(Option)] = { + "accessibility": ItemsAccessibility, "sm_logic": SMLogic, "sword_location": SwordLocation, "morph_location": MorphLocation, diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index 6056a171d37..690e5172a25 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -215,7 +215,6 @@ def create_items(self): niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World) junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World) - allJunkItems = niceItems + junkItems self.junkItemsNames = [item.Type.name for item in junkItems] if (self.smz3World.Config.Keysanity): @@ -228,7 +227,8 @@ def create_items(self): self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item)) itemPool = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \ - [SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in allJunkItems] + [SMZ3Item(item.Type.name, ItemClassification.useful, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in niceItems] + \ + [SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in junkItems] self.smz3DungeonItems = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon] self.multiworld.itempool += itemPool @@ -244,7 +244,7 @@ def set_rules(self): set_rule(entrance, lambda state, region=region: region.CanEnter(state.smz3state[self.player])) for loc in region.Locations: l = self.locations[loc.Name] - if self.multiworld.accessibility[self.player] != 'locations': + if self.multiworld.accessibility[self.player] != 'full': l.always_allow = lambda state, item, loc=loc: \ item.game == "SMZ3" and \ loc.alwaysAllow(item.item, state.smz3state[self.player]) diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index 3baed165d82..161c749fd6b 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -188,6 +188,7 @@ class SoEWorld(World): connect_name: str _halls_ne_chest_names: typing.List[str] = [loc.name for loc in _locations if 'Halls NE' in loc.name] + _fillers = sorted(item_name_groups["Ingredients"]) def __init__(self, multiworld: "MultiWorld", player: int): self.connect_name_available_event = threading.Event() @@ -469,7 +470,7 @@ def modify_multidata(self, multidata: typing.Dict[str, typing.Any]) -> None: multidata["connect_names"][self.connect_name] = payload def get_filler_item_name(self) -> str: - return self.random.choice(list(self.item_name_groups["Ingredients"])) + return self.random.choice(self._fillers) class SoEItem(Item): diff --git a/worlds/spire/Options.py b/worlds/spire/Options.py index 76cbc4cf37a..9c94756600d 100644 --- a/worlds/spire/Options.py +++ b/worlds/spire/Options.py @@ -1,5 +1,7 @@ import typing -from Options import TextChoice, Option, Range, Toggle +from dataclasses import dataclass + +from Options import TextChoice, Range, Toggle, PerGameCommonOptions class Character(TextChoice): @@ -55,9 +57,18 @@ class Downfall(Toggle): default = 0 -spire_options: typing.Dict[str, type(Option)] = { - "character": Character, - "ascension": Ascension, - "final_act": FinalAct, - "downfall": Downfall, -} +class DeathLink(Range): + """Percentage of health to lose when a death link is received.""" + display_name = "Death Link %" + range_start = 0 + range_end = 100 + default = 0 + + +@dataclass +class SpireOptions(PerGameCommonOptions): + character: Character + ascension: Ascension + final_act: FinalAct + downfall: Downfall + death_link: DeathLink diff --git a/worlds/spire/__init__.py b/worlds/spire/__init__.py index 5b0e1e17f23..a0a6a794d8a 100644 --- a/worlds/spire/__init__.py +++ b/worlds/spire/__init__.py @@ -3,7 +3,7 @@ from BaseClasses import Entrance, Item, ItemClassification, Location, MultiWorld, Region, Tutorial from .Items import event_item_pairs, item_pool, item_table from .Locations import location_table -from .Options import spire_options +from .Options import SpireOptions from .Regions import create_regions from .Rules import set_rules from ..AutoWorld import WebWorld, World @@ -27,7 +27,8 @@ class SpireWorld(World): immense power, and Slay the Spire! """ - option_definitions = spire_options + options_dataclass = SpireOptions + options: SpireOptions game = "Slay the Spire" topology_present = False web = SpireWeb() @@ -63,15 +64,13 @@ def create_regions(self): def fill_slot_data(self) -> dict: slot_data = { - 'seed': "".join(self.multiworld.per_slot_randoms[self.player].choice(string.ascii_letters) for i in range(16)) + 'seed': "".join(self.random.choice(string.ascii_letters) for i in range(16)) } - for option_name in spire_options: - option = getattr(self.multiworld, option_name)[self.player] - slot_data[option_name] = option.value + slot_data.update(self.options.as_dict("character", "ascension", "final_act", "downfall", "death_link")) return slot_data def get_filler_item_name(self) -> str: - return self.multiworld.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) + return self.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 07235ad2983..f9df8c292e3 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,4 +1,5 @@ import logging +from random import Random from typing import Dict, Any, Iterable, Optional, Union, List, TextIO from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState @@ -27,15 +28,20 @@ from .strings.metal_names import Ore from .strings.region_names import Region as RegionName, LogicRegion +logger = logging.getLogger(__name__) + +STARDEW_VALLEY = "Stardew Valley" +UNIVERSAL_TRACKER_SEED_PROPERTY = "ut_seed" + client_version = 0 class StardewLocation(Location): - game: str = "Stardew Valley" + game: str = STARDEW_VALLEY class StardewItem(Item): - game: str = "Stardew Valley" + game: str = STARDEW_VALLEY class StardewWebWorld(WebWorld): @@ -60,7 +66,7 @@ class StardewValleyWorld(World): Stardew Valley is an open-ended country-life RPG. You can farm, fish, mine, fight, complete quests, befriend villagers, and uncover dark secrets. """ - game = "Stardew Valley" + game = STARDEW_VALLEY topology_present = False item_name_to_id = {name: data.code for name, data in item_table.items()} @@ -95,6 +101,17 @@ def __init__(self, multiworld: MultiWorld, player: int): self.total_progression_items = 0 # self.all_progression_items = dict() + # Taking the seed specified in slot data for UT, otherwise just generating the seed. + self.seed = getattr(multiworld, "re_gen_passthrough", {}).get(STARDEW_VALLEY, self.random.getrandbits(64)) + self.random = Random(self.seed) + + def interpret_slot_data(self, slot_data: Dict[str, Any]) -> Optional[int]: + # If the seed is not specified in the slot data, this mean the world was generated before Universal Tracker support. + seed = slot_data.get(UNIVERSAL_TRACKER_SEED_PROPERTY) + if seed is None: + logger.warning(f"World was generated before Universal Tracker support. Tracker might not be accurate.") + return seed + def generate_early(self): self.force_change_options_if_incompatible() self.content = create_content(self.options) @@ -108,12 +125,12 @@ def force_change_options_if_incompatible(self): self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false goal_name = self.options.goal.current_key player_name = self.multiworld.player_name[self.player] - logging.warning( + logger.warning( f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})") if exclude_ginger_island and self.options.walnutsanity != Walnutsanity.preset_none: self.options.walnutsanity.value = Walnutsanity.preset_none player_name = self.multiworld.player_name[self.player] - logging.warning( + logger.warning( f"Walnutsanity requires Ginger Island. Ginger Island was excluded from {self.player} ({player_name})'s world, so walnutsanity was force disabled") def create_regions(self): @@ -255,7 +272,7 @@ def setup_victory(self): Event.victory) elif self.options.goal == Goal.option_greatest_walnut_hunter: self.create_event_location(location_table[GoalName.greatest_walnut_hunter], - self.logic.has_walnut(130), + self.logic.walnut.has_walnut(130), Event.victory) elif self.options.goal == Goal.option_protector_of_the_valley: self.create_event_location(location_table[GoalName.protector_of_the_valley], @@ -413,6 +430,7 @@ def fill_slot_data(self) -> Dict[str, Any]: included_option_names: List[str] = [option_name for option_name in self.options_dataclass.type_hints if option_name not in excluded_option_names] slot_data = self.options.as_dict(*included_option_names) slot_data.update({ + UNIVERSAL_TRACKER_SEED_PROPERTY: self.seed, "seed": self.random.randrange(1000000000), # Seed should be max 9 digits "randomized_entrances": self.randomized_entrances, "modified_bundles": bundles, diff --git a/worlds/stardew_valley/content/mods/alecto.py b/worlds/stardew_valley/content/mods/alecto.py new file mode 100644 index 00000000000..c05c936de3c --- /dev/null +++ b/worlds/stardew_valley/content/mods/alecto.py @@ -0,0 +1,33 @@ +from ..game_content import ContentPack, StardewContent +from ..mod_registry import register_mod_content_pack +from ...data import villagers_data +from ...data.harvest import ForagingSource +from ...data.requirement import QuestRequirement +from ...mods.mod_data import ModNames +from ...strings.quest_names import ModQuest +from ...strings.region_names import Region +from ...strings.seed_names import DistantLandsSeed + + +class AlectoContentPack(ContentPack): + + def harvest_source_hook(self, content: StardewContent): + if ModNames.distant_lands in content.registered_packs: + content.game_items.pop(DistantLandsSeed.void_mint) + content.game_items.pop(DistantLandsSeed.vile_ancient_fruit) + content.source_item(DistantLandsSeed.void_mint, + ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.WitchOrder),)),), + content.source_item(DistantLandsSeed.vile_ancient_fruit, + ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.WitchOrder),)), ), + + +register_mod_content_pack(ContentPack( + ModNames.alecto, + weak_dependencies=( + ModNames.distant_lands, # For Witch's order + ), + villagers=( + villagers_data.alecto, + ) + +)) diff --git a/worlds/stardew_valley/content/mods/archeology.py b/worlds/stardew_valley/content/mods/archeology.py index 97d38085d3b..5eb8af4cfc3 100644 --- a/worlds/stardew_valley/content/mods/archeology.py +++ b/worlds/stardew_valley/content/mods/archeology.py @@ -1,20 +1,34 @@ -from ..game_content import ContentPack +from ..game_content import ContentPack, StardewContent from ..mod_registry import register_mod_content_pack -from ...data.game_item import ItemTag, Tag -from ...data.shop import ShopSource +from ...data.artisan import MachineSource from ...data.skill import Skill from ...mods.mod_data import ModNames -from ...strings.book_names import ModBook -from ...strings.region_names import LogicRegion +from ...strings.craftable_names import ModMachine +from ...strings.fish_names import ModTrash +from ...strings.metal_names import all_artifacts, all_fossils from ...strings.skill_names import ModSkill -register_mod_content_pack(ContentPack( + +class ArchaeologyContentPack(ContentPack): + def artisan_good_hook(self, content: StardewContent): + # Done as honestly there are too many display items to put into the initial registration traditionally. + display_items = all_artifacts + all_fossils + for item in display_items: + self.source_display_items(item, content) + content.source_item(ModTrash.rusty_scrap, *(MachineSource(item=artifact, machine=ModMachine.grinder) for artifact in all_artifacts)) + + def source_display_items(self, item: str, content: StardewContent): + wood_display = f"Wooden Display: {item}" + hardwood_display = f"Hardwood Display: {item}" + if item == "Trilobite": + wood_display = f"Wooden Display: Trilobite Fossil" + hardwood_display = f"Hardwood Display: Trilobite Fossil" + content.source_item(wood_display, MachineSource(item=str(item), machine=ModMachine.preservation_chamber)) + content.source_item(hardwood_display, MachineSource(item=str(item), machine=ModMachine.hardwood_preservation_chamber)) + + +register_mod_content_pack(ArchaeologyContentPack( ModNames.archaeology, - shop_sources={ - ModBook.digging_like_worms: ( - Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=500, shop_region=LogicRegion.bookseller_1),), - }, skills=(Skill(name=ModSkill.archaeology, has_mastery=False),), )) diff --git a/worlds/stardew_valley/content/mods/distant_lands.py b/worlds/stardew_valley/content/mods/distant_lands.py index 19380d4ff56..c5614d13025 100644 --- a/worlds/stardew_valley/content/mods/distant_lands.py +++ b/worlds/stardew_valley/content/mods/distant_lands.py @@ -1,9 +1,26 @@ -from ..game_content import ContentPack +from ..game_content import ContentPack, StardewContent from ..mod_registry import register_mod_content_pack from ...data import villagers_data, fish_data +from ...data.game_item import ItemTag, Tag +from ...data.harvest import ForagingSource, HarvestCropSource +from ...data.requirement import QuestRequirement from ...mods.mod_data import ModNames +from ...strings.crop_names import DistantLandsCrop +from ...strings.forageable_names import DistantLandsForageable +from ...strings.quest_names import ModQuest +from ...strings.region_names import Region +from ...strings.season_names import Season +from ...strings.seed_names import DistantLandsSeed -register_mod_content_pack(ContentPack( + +class DistantLandsContentPack(ContentPack): + + def harvest_source_hook(self, content: StardewContent): + content.untag_item(DistantLandsSeed.void_mint, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(DistantLandsSeed.vile_ancient_fruit, tag=ItemTag.CROPSANITY_SEED) + + +register_mod_content_pack(DistantLandsContentPack( ModNames.distant_lands, fishes=( fish_data.void_minnow, @@ -13,5 +30,13 @@ ), villagers=( villagers_data.zic, - ) + ), + harvest_sources={ + DistantLandsForageable.swamp_herb: (ForagingSource(regions=(Region.witch_swamp,)),), + DistantLandsForageable.brown_amanita: (ForagingSource(regions=(Region.witch_swamp,)),), + DistantLandsSeed.void_mint: (ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.CorruptedCropsTask),)),), + DistantLandsCrop.void_mint: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=DistantLandsSeed.void_mint, seasons=(Season.spring, Season.summer, Season.fall)),), + DistantLandsSeed.vile_ancient_fruit: (ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.CorruptedCropsTask),)),), + DistantLandsCrop.vile_ancient_fruit: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=DistantLandsSeed.vile_ancient_fruit, seasons=(Season.spring, Season.summer, Season.fall)),) + } )) diff --git a/worlds/stardew_valley/content/mods/npc_mods.py b/worlds/stardew_valley/content/mods/npc_mods.py index 3172a55dbf3..52d97d5c52b 100644 --- a/worlds/stardew_valley/content/mods/npc_mods.py +++ b/worlds/stardew_valley/content/mods/npc_mods.py @@ -73,13 +73,6 @@ ) )) -register_mod_content_pack(ContentPack( - ModNames.alecto, - villagers=( - villagers_data.alecto, - ) -)) - register_mod_content_pack(ContentPack( ModNames.lacey, villagers=( diff --git a/worlds/stardew_valley/content/mods/sve.py b/worlds/stardew_valley/content/mods/sve.py index f74b80948c9..a68d4ae9c09 100644 --- a/worlds/stardew_valley/content/mods/sve.py +++ b/worlds/stardew_valley/content/mods/sve.py @@ -3,15 +3,27 @@ from ..override import override from ..vanilla.ginger_island import ginger_island_content_pack as ginger_island_content_pack from ...data import villagers_data, fish_data -from ...data.harvest import ForagingSource -from ...data.requirement import YearRequirement +from ...data.game_item import ItemTag, Tag +from ...data.harvest import ForagingSource, HarvestCropSource +from ...data.requirement import YearRequirement, CombatRequirement, RelationshipRequirement, ToolRequirement, SkillRequirement, FishingRequirement +from ...data.shop import ShopSource from ...mods.mod_data import ModNames -from ...strings.crop_names import Fruit -from ...strings.fish_names import WaterItem +from ...strings.craftable_names import ModEdible +from ...strings.crop_names import Fruit, SVEVegetable, SVEFruit +from ...strings.fish_names import WaterItem, SVEFish, SVEWaterItem from ...strings.flower_names import Flower -from ...strings.forageable_names import Mushroom, Forageable -from ...strings.region_names import Region, SVERegion +from ...strings.food_names import SVEMeal, SVEBeverage +from ...strings.forageable_names import Mushroom, Forageable, SVEForage +from ...strings.gift_names import SVEGift +from ...strings.metal_names import Ore +from ...strings.monster_drop_names import ModLoot, Loot +from ...strings.performance_names import Performance +from ...strings.region_names import Region, SVERegion, LogicRegion from ...strings.season_names import Season +from ...strings.seed_names import SVESeed +from ...strings.skill_names import Skill +from ...strings.tool_names import Tool, ToolMaterial +from ...strings.villager_names import ModNPC class SVEContentPack(ContentPack): @@ -38,6 +50,24 @@ def villager_hook(self, content: StardewContent): # Remove Lance if Ginger Island is not in content since he is first encountered in Volcano Forge content.villagers.pop(villagers_data.lance.name) + def harvest_source_hook(self, content: StardewContent): + content.untag_item(SVESeed.shrub, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.fungus, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.slime, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.stalk, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.void, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.ancient_fern, tag=ItemTag.CROPSANITY_SEED) + if ginger_island_content_pack.name not in content.registered_packs: + # Remove Highlands seeds as these are behind Lance existing. + content.game_items.pop(SVESeed.void) + content.game_items.pop(SVEVegetable.void_root) + content.game_items.pop(SVESeed.stalk) + content.game_items.pop(SVEFruit.monster_fruit) + content.game_items.pop(SVESeed.fungus) + content.game_items.pop(SVEVegetable.monster_mushroom) + content.game_items.pop(SVESeed.slime) + content.game_items.pop(SVEFruit.slime_berry) + register_mod_content_pack(SVEContentPack( ModNames.sve, @@ -45,12 +75,24 @@ def villager_hook(self, content: StardewContent): ginger_island_content_pack.name, ModNames.jasper, # To override Marlon and Gunther ), + shop_sources={ + SVEGift.aged_blue_moon_wine: (ShopSource(money_price=28000, shop_region=SVERegion.blue_moon_vineyard),), + SVEGift.blue_moon_wine: (ShopSource(money_price=3000, shop_region=SVERegion.blue_moon_vineyard),), + ModEdible.lightning_elixir: (ShopSource(money_price=12000, shop_region=SVERegion.galmoran_outpost),), + ModEdible.barbarian_elixir: (ShopSource(money_price=22000, shop_region=SVERegion.galmoran_outpost),), + ModEdible.gravity_elixir: (ShopSource(money_price=4000, shop_region=SVERegion.galmoran_outpost),), + SVEMeal.grampleton_orange_chicken: (ShopSource(money_price=650, shop_region=Region.saloon, other_requirements=(RelationshipRequirement(ModNPC.sophia, 6),)),), + ModEdible.hero_elixir: (ShopSource(money_price=8000, shop_region=SVERegion.isaac_shop),), + ModEdible.aegis_elixir: (ShopSource(money_price=28000, shop_region=SVERegion.galmoran_outpost),), + SVEBeverage.sports_drink: (ShopSource(money_price=750, shop_region=Region.hospital),), + SVEMeal.stamina_capsule: (ShopSource(money_price=4000, shop_region=Region.hospital),), + }, harvest_sources={ Mushroom.red: ( ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.summer, Season.fall)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), ) ), Mushroom.purple: ( - ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), ) + ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave, SVERegion.junimo_woods), ) ), Mushroom.morel: ( ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), ) @@ -64,17 +106,59 @@ def villager_hook(self, content: StardewContent): Flower.sunflower: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.summer,)),), Flower.fairy_rose: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.fall,)),), Fruit.ancient_fruit: ( - ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring, Season.summer, Season.fall), other_requirements=(YearRequirement(3),)), + ForagingSource(regions=(SVERegion.sprite_spring,), seasons=Season.not_winter, other_requirements=(YearRequirement(3),)), ForagingSource(regions=(SVERegion.sprite_spring_cave,)), ), Fruit.sweet_gem_berry: ( - ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring, Season.summer, Season.fall), other_requirements=(YearRequirement(3),)), + ForagingSource(regions=(SVERegion.sprite_spring,), seasons=Season.not_winter, other_requirements=(YearRequirement(3),)), ), + # New items + + ModLoot.green_mushroom: (ForagingSource(regions=(SVERegion.highlands_pond,), seasons=Season.not_winter),), + ModLoot.ornate_treasure_chest: (ForagingSource(regions=(SVERegion.highlands_outside,), + other_requirements=(CombatRequirement(Performance.galaxy), ToolRequirement(Tool.axe, ToolMaterial.iron))),), + ModLoot.swirl_stone: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.galaxy),)),), + ModLoot.void_soul: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEForage.winter_star_rose: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.winter,)),), + SVEForage.bearberry: (ForagingSource(regions=(Region.secret_woods,), seasons=(Season.winter,)),), + SVEForage.poison_mushroom: (ForagingSource(regions=(Region.secret_woods,), seasons=(Season.summer, Season.fall)),), + SVEForage.red_baneberry: (ForagingSource(regions=(Region.secret_woods,), seasons=(Season.summer, Season.summer)),), + SVEForage.ferngill_primrose: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.spring,)),), + SVEForage.goldenrod: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.summer, Season.fall)),), + SVEForage.conch: (ForagingSource(regions=(Region.beach, SVERegion.fable_reef,)),), + SVEForage.dewdrop_berry: (ForagingSource(regions=(SVERegion.enchanted_grove,)),), + SVEForage.sand_dollar: (ForagingSource(regions=(Region.beach, SVERegion.fable_reef,), seasons=(Season.spring, Season.summer)),), + SVEForage.golden_ocean_flower: (ForagingSource(regions=(SVERegion.fable_reef,)),), + SVEForage.four_leaf_clover: (ForagingSource(regions=(Region.secret_woods, SVERegion.forest_west,), seasons=(Season.summer, Season.fall)),), + SVEForage.mushroom_colony: (ForagingSource(regions=(Region.secret_woods, SVERegion.junimo_woods, SVERegion.forest_west,), seasons=(Season.fall,)),), + SVEForage.rusty_blade: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.great),)),), + SVEForage.rafflesia: (ForagingSource(regions=(Region.secret_woods,), seasons=Season.not_winter),), + SVEForage.thistle: (ForagingSource(regions=(SVERegion.summit,)),), + ModLoot.void_pebble: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.great),)),), + ModLoot.void_shard: (ForagingSource(regions=(SVERegion.crimson_badlands,), + other_requirements=(CombatRequirement(Performance.galaxy), SkillRequirement(Skill.combat, 10), YearRequirement(3),)),), + SVEWaterItem.dulse_seaweed: (ForagingSource(regions=(Region.beach,), other_requirements=(FishingRequirement(Region.beach),)),), + # Fable Reef WaterItem.coral: (ForagingSource(regions=(SVERegion.fable_reef,)),), Forageable.rainbow_shell: (ForagingSource(regions=(SVERegion.fable_reef,)),), WaterItem.sea_urchin: (ForagingSource(regions=(SVERegion.fable_reef,)),), + + # Crops + SVESeed.shrub: (ForagingSource(regions=(Region.secret_woods,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEFruit.salal_berry: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=SVESeed.shrub, seasons=(Season.spring,)),), + SVESeed.slime: (ForagingSource(regions=(SVERegion.highlands_outside,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEFruit.slime_berry: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=SVESeed.slime, seasons=(Season.spring,)),), + SVESeed.ancient_fern: (ForagingSource(regions=(Region.secret_woods,)),), + SVEVegetable.ancient_fiber: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=SVESeed.ancient_fern, seasons=(Season.summer,)),), + SVESeed.stalk: (ForagingSource(regions=(SVERegion.highlands_outside,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEFruit.monster_fruit: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=SVESeed.stalk, seasons=(Season.summer,)),), + SVESeed.fungus: (ForagingSource(regions=(SVERegion.highlands_pond,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEVegetable.monster_mushroom: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=SVESeed.fungus, seasons=(Season.fall,)),), + SVESeed.void: (ForagingSource(regions=(SVERegion.highlands_cavern,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEVegetable.void_root: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=SVESeed.void, seasons=(Season.winter,)),), + }, fishes=( fish_data.baby_lunaloo, # Removed when no ginger island diff --git a/worlds/stardew_valley/content/vanilla/ginger_island.py b/worlds/stardew_valley/content/vanilla/ginger_island.py index d824deff390..2fbcb032799 100644 --- a/worlds/stardew_valley/content/vanilla/ginger_island.py +++ b/worlds/stardew_valley/content/vanilla/ginger_island.py @@ -3,6 +3,7 @@ from ...data import villagers_data, fish_data from ...data.game_item import ItemTag, Tag from ...data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource +from ...data.requirement import WalnutRequirement from ...data.shop import ShopSource from ...strings.book_names import Book from ...strings.crop_names import Fruit, Vegetable @@ -10,7 +11,7 @@ from ...strings.forageable_names import Forageable, Mushroom from ...strings.fruit_tree_names import Sapling from ...strings.metal_names import Fossil, Mineral -from ...strings.region_names import Region +from ...strings.region_names import Region, LogicRegion from ...strings.season_names import Season from ...strings.seed_names import Seed @@ -62,6 +63,9 @@ def harvest_source_hook(self, content: StardewContent): Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), ShopSource(items_price=((10, Mineral.diamond),), shop_region=Region.volcano_dwarf_shop), ), + Book.queen_of_sauce_cookbook: ( + Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), + ShopSource(money_price=50000, shop_region=LogicRegion.bookseller_2, other_requirements=(WalnutRequirement(100),)),), # Worst book ever }, fishes=( diff --git a/worlds/stardew_valley/content/vanilla/pelican_town.py b/worlds/stardew_valley/content/vanilla/pelican_town.py index 2c687eacbdd..220b46eae2a 100644 --- a/worlds/stardew_valley/content/vanilla/pelican_town.py +++ b/worlds/stardew_valley/content/vanilla/pelican_town.py @@ -229,7 +229,7 @@ ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.mapping_cave_systems: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=Region.adventurer_guild_bedroom), + GenericSource(regions=(Region.adventurer_guild_bedroom,)), ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.monster_compendium: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), @@ -243,12 +243,12 @@ ShopSource(money_price=3000, shop_region=LogicRegion.bookseller_2),), Book.the_alleyway_buffet: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=Region.town, + GenericSource(regions=(Region.town,), other_requirements=(ToolRequirement(Tool.axe, ToolMaterial.iron), ToolRequirement(Tool.pickaxe, ToolMaterial.iron))), ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.the_art_o_crabbing: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=Region.beach, + GenericSource(regions=(Region.beach,), other_requirements=(ToolRequirement(Tool.fishing_rod, ToolMaterial.iridium), SkillRequirement(Skill.fishing, 6), SeasonRequirement(Season.winter))), @@ -290,9 +290,6 @@ Book.woodcutters_weekly: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), - Book.queen_of_sauce_cookbook: ( - Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=50000, shop_region=LogicRegion.bookseller_2),), # Worst book ever }, fishes=( fish_data.albacore, diff --git a/worlds/stardew_valley/data/fish_data.py b/worlds/stardew_valley/data/fish_data.py index c6f0c30d41f..26b1a0d58a8 100644 --- a/worlds/stardew_valley/data/fish_data.py +++ b/worlds/stardew_valley/data/fish_data.py @@ -46,7 +46,8 @@ def __repr__(self): crimson_badlands = (SVERegion.crimson_badlands,) shearwater = (SVERegion.shearwater,) -highlands = (SVERegion.highlands_outside,) +highlands_pond = (SVERegion.highlands_pond,) +highlands_cave = (SVERegion.highlands_cavern,) sprite_spring = (SVERegion.sprite_spring,) fable_reef = (SVERegion.fable_reef,) vineyard = (SVERegion.blue_moon_vineyard,) @@ -133,9 +134,9 @@ def create_fish(name: str, locations: Tuple[str, ...], seasons: Union[str, Tuple bull_trout = create_fish(SVEFish.bull_trout, forest_river, season.not_spring, 45, mod_name=ModNames.sve) butterfish = create_fish(SVEFish.butterfish, shearwater, season.not_winter, 75, mod_name=ModNames.sve) clownfish = create_fish(SVEFish.clownfish, ginger_island_ocean, season.all_seasons, 45, mod_name=ModNames.sve) -daggerfish = create_fish(SVEFish.daggerfish, highlands, season.all_seasons, 50, mod_name=ModNames.sve) +daggerfish = create_fish(SVEFish.daggerfish, highlands_pond, season.all_seasons, 50, mod_name=ModNames.sve) frog = create_fish(SVEFish.frog, mountain_lake, (season.spring, season.summer), 70, mod_name=ModNames.sve) -gemfish = create_fish(SVEFish.gemfish, highlands, season.all_seasons, 100, mod_name=ModNames.sve) +gemfish = create_fish(SVEFish.gemfish, highlands_cave, season.all_seasons, 100, mod_name=ModNames.sve) goldenfish = create_fish(SVEFish.goldenfish, sprite_spring, season.all_seasons, 60, mod_name=ModNames.sve) grass_carp = create_fish(SVEFish.grass_carp, secret_woods, (season.spring, season.summer), 85, mod_name=ModNames.sve) king_salmon = create_fish(SVEFish.king_salmon, forest_river, (season.spring, season.summer), 80, mod_name=ModNames.sve) diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index 2604ad2c46b..e026090f865 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -307,7 +307,7 @@ id,name,classification,groups,mod_name 322,Phoenix Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", 323,Immunity Band,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", 324,Glowstone Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -325,Fairy Dust Recipe,progression,, +325,Fairy Dust Recipe,progression,"GINGER_ISLAND", 326,Heavy Tapper Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", 327,Hyper Speed-Gro Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", 328,Deluxe Fertilizer Recipe,progression,QI_CRAFTING_RECIPE, diff --git a/worlds/stardew_valley/data/locations.csv b/worlds/stardew_valley/data/locations.csv index bb2ed2e2ce1..608b6a5f576 100644 --- a/worlds/stardew_valley/data/locations.csv +++ b/worlds/stardew_valley/data/locations.csv @@ -2088,7 +2088,7 @@ id,region,name,tags,mod_name 3472,Farm,Craft Life Elixir,CRAFTSANITY, 3473,Farm,Craft Oil of Garlic,CRAFTSANITY, 3474,Farm,Craft Monster Musk,CRAFTSANITY, -3475,Farm,Craft Fairy Dust,CRAFTSANITY, +3475,Farm,Craft Fairy Dust,"CRAFTSANITY,GINGER_ISLAND", 3476,Farm,Craft Warp Totem: Beach,CRAFTSANITY, 3477,Farm,Craft Warp Totem: Mountains,CRAFTSANITY, 3478,Farm,Craft Warp Totem: Farm,CRAFTSANITY, @@ -2212,7 +2212,7 @@ id,region,name,tags,mod_name 3808,Shipping,Shipsanity: Mystery Box,"SHIPSANITY", 3809,Shipping,Shipsanity: Golden Tag,"SHIPSANITY", 3810,Shipping,Shipsanity: Deluxe Bait,"SHIPSANITY", -3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT", +3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT", 3812,Shipping,Shipsanity: Mossy Seed,"SHIPSANITY", 3813,Shipping,Shipsanity: Sonar Bobber,"SHIPSANITY", 3814,Shipping,Shipsanity: Tent Kit,"SHIPSANITY", @@ -2221,7 +2221,7 @@ id,region,name,tags,mod_name 3817,Shipping,Shipsanity: Raisins,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT", 3818,Shipping,Shipsanity: Dried Fruit,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT", 3819,Shipping,Shipsanity: Dried Mushrooms,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT", -3820,Shipping,Shipsanity: Stardrop Tea,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT", +3820,Shipping,Shipsanity: Stardrop Tea,"SHIPSANITY", 3821,Shipping,Shipsanity: Prize Ticket,"SHIPSANITY", 3822,Shipping,Shipsanity: Treasure Totem,"SHIPSANITY,REQUIRES_MASTERIES", 3823,Shipping,Shipsanity: Challenge Bait,"SHIPSANITY,REQUIRES_MASTERIES", @@ -2252,7 +2252,7 @@ id,region,name,tags,mod_name 3848,Shipping,Shipsanity: Way Of The Wind pt. 1,"SHIPSANITY", 3849,Shipping,Shipsanity: Mapping Cave Systems,"SHIPSANITY", 3850,Shipping,Shipsanity: Price Catalogue,"SHIPSANITY", -3851,Shipping,Shipsanity: Queen Of Sauce Cookbook,"SHIPSANITY", +3851,Shipping,Shipsanity: Queen Of Sauce Cookbook,"SHIPSANITY,GINGER_ISLAND", 3852,Shipping,Shipsanity: The Diamond Hunter,"SHIPSANITY,GINGER_ISLAND", 3853,Shipping,Shipsanity: Book of Mysteries,"SHIPSANITY", 3854,Shipping,Shipsanity: Animal Catalogue,"SHIPSANITY", @@ -2292,7 +2292,7 @@ id,region,name,tags,mod_name 4032,Farm,Read Book Of Stars,"BOOKSANITY,BOOKSANITY_SKILL", 4033,Farm,Read Combat Quarterly,"BOOKSANITY,BOOKSANITY_SKILL", 4034,Farm,Read Mining Monthly,"BOOKSANITY,BOOKSANITY_SKILL", -4035,Farm,Read Queen Of Sauce Cookbook,"BOOKSANITY,BOOKSANITY_SKILL", +4035,Farm,Read Queen Of Sauce Cookbook,"BOOKSANITY,BOOKSANITY_SKILL,GINGER_ISLAND", 4036,Farm,Read Stardew Valley Almanac,"BOOKSANITY,BOOKSANITY_SKILL", 4037,Farm,Read Woodcutter's Weekly,"BOOKSANITY,BOOKSANITY_SKILL", 4051,Museum,Read Tips on Farming,"BOOKSANITY,BOOKSANITY_LOST", @@ -2900,7 +2900,6 @@ id,region,name,tags,mod_name 7055,Abandoned Mines - 3,Abandoned Treasure - Floor 3,MANDATORY,Boarding House and Bus Stop Extension 7056,Abandoned Mines - 4,Abandoned Treasure - Floor 4,MANDATORY,Boarding House and Bus Stop Extension 7057,Abandoned Mines - 5,Abandoned Treasure - Floor 5,MANDATORY,Boarding House and Bus Stop Extension -7351,Farm,Read Digging Like Worms,"BOOKSANITY,BOOKSANITY_SKILL",Archaeology 7401,Farm,Cook Magic Elixir,COOKSANITY,Magic 7402,Farm,Craft Travel Core,CRAFTSANITY,Magic 7403,Farm,Craft Haste Elixir,CRAFTSANITY,Stardew Valley Expanded @@ -3280,10 +3279,10 @@ id,region,name,tags,mod_name 8237,Shipping,Shipsanity: Pterodactyl R Wing Bone,SHIPSANITY,Boarding House and Bus Stop Extension 8238,Shipping,Shipsanity: Scrap Rust,SHIPSANITY,Archaeology 8239,Shipping,Shipsanity: Rusty Path,SHIPSANITY,Archaeology -8240,Shipping,Shipsanity: Digging Like Worms,SHIPSANITY,Archaeology 8241,Shipping,Shipsanity: Digger's Delight,SHIPSANITY,Archaeology 8242,Shipping,Shipsanity: Rocky Root Coffee,SHIPSANITY,Archaeology 8243,Shipping,Shipsanity: Ancient Jello,SHIPSANITY,Archaeology 8244,Shipping,Shipsanity: Bone Fence,SHIPSANITY,Archaeology 8245,Shipping,Shipsanity: Grilled Cheese,SHIPSANITY,Binning Skill 8246,Shipping,Shipsanity: Fish Casserole,SHIPSANITY,Binning Skill +8247,Shipping,Shipsanity: Snatcher Worm,SHIPSANITY,Stardew Valley Expanded diff --git a/worlds/stardew_valley/data/recipe_data.py b/worlds/stardew_valley/data/recipe_data.py index b4824687627..3123bb92430 100644 --- a/worlds/stardew_valley/data/recipe_data.py +++ b/worlds/stardew_valley/data/recipe_data.py @@ -5,7 +5,7 @@ from ..strings.artisan_good_names import ArtisanGood from ..strings.craftable_names import ModEdible, Edible from ..strings.crop_names import Fruit, Vegetable, SVEFruit, DistantLandsCrop -from ..strings.fish_names import Fish, SVEFish, WaterItem, DistantLandsFish +from ..strings.fish_names import Fish, SVEFish, WaterItem, DistantLandsFish, SVEWaterItem from ..strings.flower_names import Flower from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable, Mushroom from ..strings.ingredient_names import Ingredient @@ -195,7 +195,7 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, ModNames.sve) mushroom_berry_rice = friendship_and_shop_recipe(SVEMeal.mushroom_berry_rice, ModNPC.marlon, 6, Region.adventurer_guild, 1500, {SVEForage.poison_mushroom: 3, SVEForage.red_baneberry: 10, Ingredient.rice: 1, Ingredient.sugar: 2}, ModNames.sve) -seaweed_salad = shop_recipe(SVEMeal.seaweed_salad, Region.fish_shop, 1250, {SVEFish.dulse_seaweed: 2, WaterItem.seaweed: 2, Ingredient.oil: 1}, ModNames.sve) +seaweed_salad = shop_recipe(SVEMeal.seaweed_salad, Region.fish_shop, 1250, {SVEWaterItem.dulse_seaweed: 2, WaterItem.seaweed: 2, Ingredient.oil: 1}, ModNames.sve) void_delight = friendship_and_shop_recipe(SVEMeal.void_delight, NPC.krobus, 10, Region.sewer, 5000, {SVEFish.void_eel: 1, Loot.void_essence: 50, Loot.solar_essence: 20}, ModNames.sve) void_salmon_sushi = friendship_and_shop_recipe(SVEMeal.void_salmon_sushi, NPC.krobus, 10, Region.sewer, 5000, diff --git a/worlds/stardew_valley/data/requirement.py b/worlds/stardew_valley/data/requirement.py index 7e9466630fc..b2416d8d0b7 100644 --- a/worlds/stardew_valley/data/requirement.py +++ b/worlds/stardew_valley/data/requirement.py @@ -29,3 +29,29 @@ class SeasonRequirement(Requirement): @dataclass(frozen=True) class YearRequirement(Requirement): year: int + + +@dataclass(frozen=True) +class CombatRequirement(Requirement): + level: str + + +@dataclass(frozen=True) +class QuestRequirement(Requirement): + quest: str + + +@dataclass(frozen=True) +class RelationshipRequirement(Requirement): + npc: str + hearts: int + + +@dataclass(frozen=True) +class FishingRequirement(Requirement): + region: str + + +@dataclass(frozen=True) +class WalnutRequirement(Requirement): + amount: int diff --git a/worlds/stardew_valley/data/shop.py b/worlds/stardew_valley/data/shop.py index ca54d35e14f..f14dbac8213 100644 --- a/worlds/stardew_valley/data/shop.py +++ b/worlds/stardew_valley/data/shop.py @@ -16,8 +16,8 @@ class ShopSource(ItemSource): other_requirements: Tuple[Requirement, ...] = () def __post_init__(self): - assert self.money_price or self.items_price, "At least money price or items price need to be defined." - assert self.items_price is None or all(type(p) == tuple for p in self.items_price), "Items price should be a tuple." + assert self.money_price is not None or self.items_price is not None, "At least money price or items price need to be defined." + assert self.items_price is None or all(isinstance(p, tuple) for p in self.items_price), "Items price should be a tuple." @dataclass(frozen=True, **kw_only) diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index cb610201694..31c7da5e3ad 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -409,8 +409,9 @@ def create_special_quest_rewards(item_factory: StardewItemFactory, options: Star else: items.append(item_factory(Wallet.bears_knowledge, ItemClassification.useful)) # Not necessary outside of SVE items.append(item_factory(Wallet.iridium_snake_milk)) - items.append(item_factory("Fairy Dust Recipe")) items.append(item_factory("Dark Talisman")) + if options.exclude_ginger_island == ExcludeGingerIsland.option_false: + items.append(item_factory("Fairy Dust Recipe")) def create_help_wanted_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): diff --git a/worlds/stardew_valley/logic/bundle_logic.py b/worlds/stardew_valley/logic/bundle_logic.py index 4ca5fd81fc7..98fda1c73c7 100644 --- a/worlds/stardew_valley/logic/bundle_logic.py +++ b/worlds/stardew_valley/logic/bundle_logic.py @@ -27,8 +27,8 @@ def __init__(self, *args, **kwargs): self.bundle = BundleLogic(*args, **kwargs) -class BundleLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, MoneyLogicMixin, QualityLogicMixin, FishingLogicMixin, SkillLogicMixin, -QuestLogicMixin]]): +class BundleLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, MoneyLogicMixin, QualityLogicMixin, FishingLogicMixin, +SkillLogicMixin, QuestLogicMixin]]): # Should be cached def can_complete_bundle(self, bundle: Bundle) -> StardewRule: item_rules = [] @@ -45,7 +45,7 @@ def can_complete_bundle(self, bundle: Bundle) -> StardewRule: qualities.append(bundle_item.quality) quality_rules = self.get_quality_rules(qualities) item_rules = self.logic.has_n(*item_rules, count=bundle.number_required) - time_rule = True_() if time_to_grind <= 0 else self.logic.time.has_lived_months(time_to_grind) + time_rule = self.logic.time.has_lived_months(time_to_grind) return can_speak_junimo & item_rules & quality_rules & time_rule def get_quality_rules(self, qualities: List[str]) -> StardewRule: diff --git a/worlds/stardew_valley/logic/logic.py b/worlds/stardew_valley/logic/logic.py index 74cdaf2374e..fb0d938fbb1 100644 --- a/worlds/stardew_valley/logic/logic.py +++ b/worlds/stardew_valley/logic/logic.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from functools import cached_property from typing import Collection, Callable from .ability_logic import AbilityLogicMixin @@ -43,6 +42,7 @@ from .tool_logic import ToolLogicMixin from .traveling_merchant_logic import TravelingMerchantLogicMixin from .wallet_logic import WalletLogicMixin +from .walnut_logic import WalnutLogicMixin from ..content.game_content import StardewContent from ..data.craftable_data import all_crafting_recipes from ..data.museum_data import all_museum_items @@ -50,16 +50,14 @@ from ..mods.logic.magic_logic import MagicLogicMixin from ..mods.logic.mod_logic import ModLogicMixin from ..mods.mod_data import ModNames -from ..options import SpecialOrderLocations, ExcludeGingerIsland, FestivalLocations, StardewValleyOptions, Walnutsanity +from ..options import ExcludeGingerIsland, FestivalLocations, StardewValleyOptions from ..stardew_rule import False_, True_, StardewRule from ..strings.animal_names import Animal from ..strings.animal_product_names import AnimalProduct -from ..strings.ap_names.ap_option_names import OptionName from ..strings.ap_names.community_upgrade_names import CommunityUpgrade -from ..strings.ap_names.event_names import Event from ..strings.artisan_good_names import ArtisanGood from ..strings.building_names import Building -from ..strings.craftable_names import Consumable, Furniture, Ring, Fishing, Lighting, WildSeeds +from ..strings.craftable_names import Consumable, Ring, Fishing, Lighting, WildSeeds from ..strings.crop_names import Fruit, Vegetable from ..strings.currency_names import Currency from ..strings.decoration_names import Decoration @@ -96,7 +94,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin, SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin, SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin, - RequirementLogicMixin, BookLogicMixin, GrindLogicMixin): + RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, WalnutLogicMixin): player: int options: StardewValleyOptions content: StardewContent @@ -461,32 +459,6 @@ def setup_events(self, register_event: Callable[[str, str, StardewRule], None]) def can_smelt(self, item: str) -> StardewRule: return self.has(Machine.furnace) & self.has(item) - @cached_property - def can_start_field_office(self) -> StardewRule: - field_office = self.region.can_reach(Region.field_office) - professor_snail = self.received("Open Professor Snail Cave") - return field_office & professor_snail - - def can_complete_large_animal_collection(self) -> StardewRule: - fossils = self.has_all(Fossil.fossilized_leg, Fossil.fossilized_ribs, Fossil.fossilized_skull, Fossil.fossilized_spine, Fossil.fossilized_tail) - return self.can_start_field_office & fossils - - def can_complete_snake_collection(self) -> StardewRule: - fossils = self.has_all(Fossil.snake_skull, Fossil.snake_vertebrae) - return self.can_start_field_office & fossils - - def can_complete_frog_collection(self) -> StardewRule: - fossils = self.has_all(Fossil.mummified_frog) - return self.can_start_field_office & fossils - - def can_complete_bat_collection(self) -> StardewRule: - fossils = self.has_all(Fossil.mummified_bat) - return self.can_start_field_office & fossils - - def can_complete_field_office(self) -> StardewRule: - return self.can_complete_large_animal_collection() & self.can_complete_snake_collection() & \ - self.can_complete_frog_collection() & self.can_complete_bat_collection() - def can_finish_grandpa_evaluation(self) -> StardewRule: # https://stardewvalleywiki.com/Grandpa rules_worth_a_point = [ @@ -566,86 +538,6 @@ def has_island_trader(self) -> StardewRule: return False_() return self.region.can_reach(Region.island_trader) - def has_walnut(self, number: int) -> StardewRule: - if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: - return False_() - if number <= 0: - return True_() - - if self.options.walnutsanity == Walnutsanity.preset_none: - return self.can_get_walnuts(number) - if self.options.walnutsanity == Walnutsanity.preset_all: - return self.has_received_walnuts(number) - puzzle_walnuts = 61 - bush_walnuts = 25 - dig_walnuts = 18 - repeatable_walnuts = 33 - total_walnuts = puzzle_walnuts + bush_walnuts + dig_walnuts + repeatable_walnuts - walnuts_to_receive = 0 - walnuts_to_collect = number - if OptionName.walnutsanity_puzzles in self.options.walnutsanity: - puzzle_walnut_rate = puzzle_walnuts / total_walnuts - puzzle_walnuts_required = round(puzzle_walnut_rate * number) - walnuts_to_receive += puzzle_walnuts_required - walnuts_to_collect -= puzzle_walnuts_required - if OptionName.walnutsanity_bushes in self.options.walnutsanity: - bush_walnuts_rate = bush_walnuts / total_walnuts - bush_walnuts_required = round(bush_walnuts_rate * number) - walnuts_to_receive += bush_walnuts_required - walnuts_to_collect -= bush_walnuts_required - if OptionName.walnutsanity_dig_spots in self.options.walnutsanity: - dig_walnuts_rate = dig_walnuts / total_walnuts - dig_walnuts_required = round(dig_walnuts_rate * number) - walnuts_to_receive += dig_walnuts_required - walnuts_to_collect -= dig_walnuts_required - if OptionName.walnutsanity_repeatables in self.options.walnutsanity: - repeatable_walnuts_rate = repeatable_walnuts / total_walnuts - repeatable_walnuts_required = round(repeatable_walnuts_rate * number) - walnuts_to_receive += repeatable_walnuts_required - walnuts_to_collect -= repeatable_walnuts_required - return self.has_received_walnuts(walnuts_to_receive) & self.can_get_walnuts(walnuts_to_collect) - - def has_received_walnuts(self, number: int) -> StardewRule: - return self.received(Event.received_walnuts, number) - - def can_get_walnuts(self, number: int) -> StardewRule: - # https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations - reach_south = self.region.can_reach(Region.island_south) - reach_north = self.region.can_reach(Region.island_north) - reach_west = self.region.can_reach(Region.island_west) - reach_hut = self.region.can_reach(Region.leo_hut) - reach_southeast = self.region.can_reach(Region.island_south_east) - reach_field_office = self.region.can_reach(Region.field_office) - reach_pirate_cove = self.region.can_reach(Region.pirate_cove) - reach_outside_areas = self.logic.and_(reach_south, reach_north, reach_west, reach_hut) - reach_volcano_regions = [self.region.can_reach(Region.volcano), - self.region.can_reach(Region.volcano_secret_beach), - self.region.can_reach(Region.volcano_floor_5), - self.region.can_reach(Region.volcano_floor_10)] - reach_volcano = self.logic.or_(*reach_volcano_regions) - reach_all_volcano = self.logic.and_(*reach_volcano_regions) - reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office] - reach_caves = self.logic.and_(self.region.can_reach(Region.qi_walnut_room), self.region.can_reach(Region.dig_site), - self.region.can_reach(Region.gourmand_frog_cave), - self.region.can_reach(Region.colored_crystals_cave), - self.region.can_reach(Region.shipwreck), self.combat.has_slingshot) - reach_entire_island = self.logic.and_(reach_outside_areas, reach_all_volcano, - reach_caves, reach_southeast, reach_field_office, reach_pirate_cove) - if number <= 5: - return self.logic.or_(reach_south, reach_north, reach_west, reach_volcano) - if number <= 10: - return self.count(2, *reach_walnut_regions) - if number <= 15: - return self.count(3, *reach_walnut_regions) - if number <= 20: - return self.logic.and_(*reach_walnut_regions) - if number <= 50: - return reach_entire_island - gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz) - return reach_entire_island & self.has(Fruit.banana) & self.has_all(*gems) & self.ability.can_mine_perfectly() & \ - self.ability.can_fish_perfectly() & self.has(Furniture.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) & \ - self.can_complete_field_office() - def has_all_stardrops(self) -> StardewRule: other_rules = [] number_of_stardrops_to_receive = 0 diff --git a/worlds/stardew_valley/logic/museum_logic.py b/worlds/stardew_valley/logic/museum_logic.py index 4ba5364f552..36ba62b31fc 100644 --- a/worlds/stardew_valley/logic/museum_logic.py +++ b/worlds/stardew_valley/logic/museum_logic.py @@ -41,7 +41,7 @@ def can_find_museum_item(self, item: MuseumItem) -> StardewRule: else: geodes_rule = False_() # monster_rule = self.can_farm_monster(item.monsters) - time_needed_to_grind = (20 - item.difficulty) / 2 + time_needed_to_grind = int((20 - item.difficulty) // 2) time_rule = self.logic.time.has_lived_months(time_needed_to_grind) pan_rule = False_() if item.item_name == Mineral.earth_crystal or item.item_name == Mineral.fire_quartz or item.item_name == Mineral.frozen_tear: diff --git a/worlds/stardew_valley/logic/requirement_logic.py b/worlds/stardew_valley/logic/requirement_logic.py index 87d9ee02152..6a5adf4890c 100644 --- a/worlds/stardew_valley/logic/requirement_logic.py +++ b/worlds/stardew_valley/logic/requirement_logic.py @@ -3,14 +3,20 @@ from .base_logic import BaseLogicMixin, BaseLogic from .book_logic import BookLogicMixin +from .combat_logic import CombatLogicMixin +from .fishing_logic import FishingLogicMixin from .has_logic import HasLogicMixin +from .quest_logic import QuestLogicMixin from .received_logic import ReceivedLogicMixin +from .relationship_logic import RelationshipLogicMixin from .season_logic import SeasonLogicMixin from .skill_logic import SkillLogicMixin from .time_logic import TimeLogicMixin from .tool_logic import ToolLogicMixin +from .walnut_logic import WalnutLogicMixin from ..data.game_item import Requirement -from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement +from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, CombatRequirement, QuestRequirement, \ + RelationshipRequirement, FishingRequirement, WalnutRequirement class RequirementLogicMixin(BaseLogicMixin): @@ -20,7 +26,7 @@ def __init__(self, *args, **kwargs): class RequirementLogic(BaseLogic[Union[RequirementLogicMixin, HasLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, BookLogicMixin, -SeasonLogicMixin, TimeLogicMixin]]): +SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, RelationshipLogicMixin, FishingLogicMixin, WalnutLogicMixin]]): def meet_all_requirements(self, requirements: Iterable[Requirement]): if not requirements: @@ -50,3 +56,25 @@ def _(self, requirement: SeasonRequirement): @meet_requirement.register def _(self, requirement: YearRequirement): return self.logic.time.has_year(requirement.year) + + @meet_requirement.register + def _(self, requirement: WalnutRequirement): + return self.logic.walnut.has_walnut(requirement.amount) + + @meet_requirement.register + def _(self, requirement: CombatRequirement): + return self.logic.combat.can_fight_at_level(requirement.level) + + @meet_requirement.register + def _(self, requirement: QuestRequirement): + return self.logic.quest.can_complete_quest(requirement.quest) + + @meet_requirement.register + def _(self, requirement: RelationshipRequirement): + return self.logic.relationship.has_hearts(requirement.npc, requirement.hearts) + + @meet_requirement.register + def _(self, requirement: FishingRequirement): + return self.logic.fishing.can_fish_at(requirement.region) + + diff --git a/worlds/stardew_valley/logic/time_logic.py b/worlds/stardew_valley/logic/time_logic.py index 94e0e277c86..2ba76579ff4 100644 --- a/worlds/stardew_valley/logic/time_logic.py +++ b/worlds/stardew_valley/logic/time_logic.py @@ -26,8 +26,10 @@ class TimeLogic(BaseLogic[Union[TimeLogicMixin, HasLogicMixin]]): @cache_self1 def has_lived_months(self, number: int) -> StardewRule: + assert isinstance(number, int), "Can't have lived a fraction of a month. Use // instead of / when dividing." if number <= 0: return self.logic.true_ + number = min(number, MAX_MONTHS) return HasProgressionPercent(self.player, number * MONTH_COEFFICIENT) diff --git a/worlds/stardew_valley/logic/walnut_logic.py b/worlds/stardew_valley/logic/walnut_logic.py new file mode 100644 index 00000000000..14fe1c33909 --- /dev/null +++ b/worlds/stardew_valley/logic/walnut_logic.py @@ -0,0 +1,135 @@ +from functools import cached_property +from typing import Union + +from .ability_logic import AbilityLogicMixin +from .base_logic import BaseLogic, BaseLogicMixin +from .combat_logic import CombatLogicMixin +from .has_logic import HasLogicMixin +from .received_logic import ReceivedLogicMixin +from .region_logic import RegionLogicMixin +from ..strings.ap_names.event_names import Event +from ..options import ExcludeGingerIsland, Walnutsanity +from ..stardew_rule import StardewRule, False_, True_ +from ..strings.ap_names.ap_option_names import OptionName +from ..strings.craftable_names import Furniture +from ..strings.crop_names import Fruit +from ..strings.metal_names import Mineral, Fossil +from ..strings.region_names import Region +from ..strings.seed_names import Seed + + +class WalnutLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.walnut = WalnutLogic(*args, **kwargs) + + +class WalnutLogic(BaseLogic[Union[WalnutLogicMixin, ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, CombatLogicMixin, + AbilityLogicMixin]]): + + def has_walnut(self, number: int) -> StardewRule: + if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: + return False_() + if number <= 0: + return True_() + + if self.options.walnutsanity == Walnutsanity.preset_none: + return self.can_get_walnuts(number) + if self.options.walnutsanity == Walnutsanity.preset_all: + return self.has_received_walnuts(number) + puzzle_walnuts = 61 + bush_walnuts = 25 + dig_walnuts = 18 + repeatable_walnuts = 33 + total_walnuts = puzzle_walnuts + bush_walnuts + dig_walnuts + repeatable_walnuts + walnuts_to_receive = 0 + walnuts_to_collect = number + if OptionName.walnutsanity_puzzles in self.options.walnutsanity: + puzzle_walnut_rate = puzzle_walnuts / total_walnuts + puzzle_walnuts_required = round(puzzle_walnut_rate * number) + walnuts_to_receive += puzzle_walnuts_required + walnuts_to_collect -= puzzle_walnuts_required + if OptionName.walnutsanity_bushes in self.options.walnutsanity: + bush_walnuts_rate = bush_walnuts / total_walnuts + bush_walnuts_required = round(bush_walnuts_rate * number) + walnuts_to_receive += bush_walnuts_required + walnuts_to_collect -= bush_walnuts_required + if OptionName.walnutsanity_dig_spots in self.options.walnutsanity: + dig_walnuts_rate = dig_walnuts / total_walnuts + dig_walnuts_required = round(dig_walnuts_rate * number) + walnuts_to_receive += dig_walnuts_required + walnuts_to_collect -= dig_walnuts_required + if OptionName.walnutsanity_repeatables in self.options.walnutsanity: + repeatable_walnuts_rate = repeatable_walnuts / total_walnuts + repeatable_walnuts_required = round(repeatable_walnuts_rate * number) + walnuts_to_receive += repeatable_walnuts_required + walnuts_to_collect -= repeatable_walnuts_required + return self.has_received_walnuts(walnuts_to_receive) & self.can_get_walnuts(walnuts_to_collect) + + def has_received_walnuts(self, number: int) -> StardewRule: + return self.logic.received(Event.received_walnuts, number) + + def can_get_walnuts(self, number: int) -> StardewRule: + # https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations + reach_south = self.logic.region.can_reach(Region.island_south) + reach_north = self.logic.region.can_reach(Region.island_north) + reach_west = self.logic.region.can_reach(Region.island_west) + reach_hut = self.logic.region.can_reach(Region.leo_hut) + reach_southeast = self.logic.region.can_reach(Region.island_south_east) + reach_field_office = self.logic.region.can_reach(Region.field_office) + reach_pirate_cove = self.logic.region.can_reach(Region.pirate_cove) + reach_outside_areas = self.logic.and_(reach_south, reach_north, reach_west, reach_hut) + reach_volcano_regions = [self.logic.region.can_reach(Region.volcano), + self.logic.region.can_reach(Region.volcano_secret_beach), + self.logic.region.can_reach(Region.volcano_floor_5), + self.logic.region.can_reach(Region.volcano_floor_10)] + reach_volcano = self.logic.or_(*reach_volcano_regions) + reach_all_volcano = self.logic.and_(*reach_volcano_regions) + reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office] + reach_caves = self.logic.and_(self.logic.region.can_reach(Region.qi_walnut_room), self.logic.region.can_reach(Region.dig_site), + self.logic.region.can_reach(Region.gourmand_frog_cave), + self.logic.region.can_reach(Region.colored_crystals_cave), + self.logic.region.can_reach(Region.shipwreck), self.logic.combat.has_slingshot) + reach_entire_island = self.logic.and_(reach_outside_areas, reach_all_volcano, + reach_caves, reach_southeast, reach_field_office, reach_pirate_cove) + if number <= 5: + return self.logic.or_(reach_south, reach_north, reach_west, reach_volcano) + if number <= 10: + return self.logic.count(2, *reach_walnut_regions) + if number <= 15: + return self.logic.count(3, *reach_walnut_regions) + if number <= 20: + return self.logic.and_(*reach_walnut_regions) + if number <= 50: + return reach_entire_island + gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz) + return reach_entire_island & self.logic.has(Fruit.banana) & self.logic.has_all(*gems) & \ + self.logic.ability.can_mine_perfectly() & self.logic.ability.can_fish_perfectly() & \ + self.logic.has(Furniture.flute_block) & self.logic.has(Seed.melon) & self.logic.has(Seed.wheat) & \ + self.logic.has(Seed.garlic) & self.can_complete_field_office() + + @cached_property + def can_start_field_office(self) -> StardewRule: + field_office = self.logic.region.can_reach(Region.field_office) + professor_snail = self.logic.received("Open Professor Snail Cave") + return field_office & professor_snail + + def can_complete_large_animal_collection(self) -> StardewRule: + fossils = self.logic.has_all(Fossil.fossilized_leg, Fossil.fossilized_ribs, Fossil.fossilized_skull, Fossil.fossilized_spine, Fossil.fossilized_tail) + return self.can_start_field_office & fossils + + def can_complete_snake_collection(self) -> StardewRule: + fossils = self.logic.has_all(Fossil.snake_skull, Fossil.snake_vertebrae) + return self.can_start_field_office & fossils + + def can_complete_frog_collection(self) -> StardewRule: + fossils = self.logic.has_all(Fossil.mummified_frog) + return self.can_start_field_office & fossils + + def can_complete_bat_collection(self) -> StardewRule: + fossils = self.logic.has_all(Fossil.mummified_bat) + return self.can_start_field_office & fossils + + def can_complete_field_office(self) -> StardewRule: + return self.can_complete_large_animal_collection() & self.can_complete_snake_collection() & \ + self.can_complete_frog_collection() & self.can_complete_bat_collection() diff --git a/worlds/stardew_valley/mods/logic/item_logic.py b/worlds/stardew_valley/mods/logic/item_logic.py index cfafc88e83f..ef5eab0134d 100644 --- a/worlds/stardew_valley/mods/logic/item_logic.py +++ b/worlds/stardew_valley/mods/logic/item_logic.py @@ -23,24 +23,15 @@ from ...options import Cropsanity from ...stardew_rule import StardewRule, True_ from ...strings.artisan_good_names import ModArtisanGood -from ...strings.craftable_names import ModCraftable, ModEdible, ModMachine -from ...strings.crop_names import SVEVegetable, SVEFruit, DistantLandsCrop -from ...strings.fish_names import ModTrash, SVEFish -from ...strings.food_names import SVEMeal, SVEBeverage -from ...strings.forageable_names import SVEForage, DistantLandsForageable -from ...strings.gift_names import SVEGift +from ...strings.craftable_names import ModCraftable, ModMachine +from ...strings.fish_names import ModTrash from ...strings.ingredient_names import Ingredient from ...strings.material_names import Material from ...strings.metal_names import all_fossils, all_artifacts, Ore, ModFossil -from ...strings.monster_drop_names import ModLoot, Loot +from ...strings.monster_drop_names import Loot from ...strings.performance_names import Performance -from ...strings.quest_names import ModQuest -from ...strings.region_names import Region, SVERegion, DeepWoodsRegion, BoardingHouseRegion -from ...strings.season_names import Season -from ...strings.seed_names import SVESeed, DistantLandsSeed -from ...strings.skill_names import Skill +from ...strings.region_names import SVERegion, DeepWoodsRegion, BoardingHouseRegion from ...strings.tool_names import Tool, ToolMaterial -from ...strings.villager_names import ModNPC display_types = [ModCraftable.wooden_display, ModCraftable.hardwood_display] display_items = all_artifacts + all_fossils @@ -58,12 +49,6 @@ class ModItemLogic(BaseLogic[Union[CombatLogicMixin, ReceivedLogicMixin, Cooking def get_modded_item_rules(self) -> Dict[str, StardewRule]: items = dict() - if ModNames.sve in self.options.mods: - items.update(self.get_sve_item_rules()) - if ModNames.archaeology in self.options.mods: - items.update(self.get_archaeology_item_rules()) - if ModNames.distant_lands in self.options.mods: - items.update(self.get_distant_lands_item_rules()) if ModNames.boarding_house in self.options.mods: items.update(self.get_boarding_house_item_rules()) return items @@ -75,61 +60,6 @@ def modify_vanilla_item_rules_with_mod_additions(self, item_rule: Dict[str, Star item_rule.update(self.get_modified_item_rules_for_deep_woods(item_rule)) return item_rule - def get_sve_item_rules(self): - return {SVEGift.aged_blue_moon_wine: self.logic.money.can_spend_at(SVERegion.sophias_house, 28000), - SVEGift.blue_moon_wine: self.logic.money.can_spend_at(SVERegion.sophias_house, 3000), - SVESeed.fungus: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon, - ModLoot.green_mushroom: self.logic.region.can_reach(SVERegion.highlands_outside) & - self.logic.tool.has_tool(Tool.axe, ToolMaterial.iron) & self.logic.season.has_any_not_winter(), - SVEFruit.monster_fruit: self.logic.season.has(Season.summer) & self.logic.has(SVESeed.stalk), - SVEVegetable.monster_mushroom: self.logic.season.has(Season.fall) & self.logic.has(SVESeed.fungus), - ModLoot.ornate_treasure_chest: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_galaxy_weapon & - self.logic.tool.has_tool(Tool.axe, ToolMaterial.iron), - SVEFruit.slime_berry: self.logic.season.has(Season.spring) & self.logic.has(SVESeed.slime), - SVESeed.slime: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon, - SVESeed.stalk: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon, - ModLoot.swirl_stone: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon, - SVEVegetable.void_root: self.logic.season.has(Season.winter) & self.logic.has(SVESeed.void), - SVESeed.void: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon, - ModLoot.void_soul: self.logic.region.can_reach( - SVERegion.crimson_badlands) & self.logic.combat.has_good_weapon & self.logic.cooking.can_cook(), - SVEForage.winter_star_rose: self.logic.region.can_reach(SVERegion.summit) & self.logic.season.has(Season.winter), - SVEForage.bearberry: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.winter), - SVEForage.poison_mushroom: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has_any([Season.summer, Season.fall]), - SVEForage.red_baneberry: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.summer), - SVEForage.ferngill_primrose: self.logic.region.can_reach(SVERegion.summit) & self.logic.season.has(Season.spring), - SVEForage.goldenrod: self.logic.region.can_reach(SVERegion.summit) & ( - self.logic.season.has(Season.summer) | self.logic.season.has(Season.fall)), - SVESeed.shrub: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic), - SVEFruit.salal_berry: self.logic.farming.can_plant_and_grow_item((Season.spring, Season.summer)) & self.logic.has(SVESeed.shrub), - ModEdible.aegis_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 28000), - ModEdible.lightning_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 12000), - ModEdible.barbarian_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 22000), - ModEdible.gravity_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 4000), - SVESeed.ancient_fern: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic), - SVEVegetable.ancient_fiber: self.logic.farming.can_plant_and_grow_item(Season.summer) & self.logic.has(SVESeed.ancient_fern), - SVEForage.conch: self.logic.region.can_reach_any((Region.beach, SVERegion.fable_reef)), - SVEForage.dewdrop_berry: self.logic.region.can_reach(SVERegion.enchanted_grove), - SVEForage.sand_dollar: self.logic.region.can_reach(SVERegion.fable_reef) | (self.logic.region.can_reach(Region.beach) & - self.logic.season.has_any([Season.summer, Season.fall])), - SVEForage.golden_ocean_flower: self.logic.region.can_reach(SVERegion.fable_reef), - SVEMeal.grampleton_orange_chicken: self.logic.money.can_spend_at(Region.saloon, 650) & self.logic.relationship.has_hearts(ModNPC.sophia, 6), - ModEdible.hero_elixir: self.logic.money.can_spend_at(SVERegion.isaac_shop, 8000), - SVEForage.four_leaf_clover: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.forest_west)) & - self.logic.season.has_any([Season.spring, Season.summer]), - SVEForage.mushroom_colony: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.junimo_woods, SVERegion.forest_west)) & - self.logic.season.has(Season.fall), - SVEForage.rusty_blade: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon, - SVEForage.rafflesia: self.logic.region.can_reach(Region.secret_woods), - SVEBeverage.sports_drink: self.logic.money.can_spend_at(Region.hospital, 750), - "Stamina Capsule": self.logic.money.can_spend_at(Region.hospital, 4000), - SVEForage.thistle: self.logic.region.can_reach(SVERegion.summit), - ModLoot.void_pebble: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon, - ModLoot.void_shard: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_galaxy_weapon & - self.logic.skill.has_level(Skill.combat, 10) & self.logic.region.can_reach(Region.saloon) & self.logic.time.has_year_three - } - # @formatter:on - def get_modified_item_rules_for_sve(self, items: Dict[str, StardewRule]): return { Loot.void_essence: items[Loot.void_essence] | self.logic.region.can_reach(SVERegion.highlands_cavern) | self.logic.region.can_reach( @@ -141,7 +71,7 @@ def get_modified_item_rules_for_sve(self, items: Dict[str, StardewRule]): self.logic.combat.can_fight_at_level(Performance.great)), Ore.iridium: items[Ore.iridium] | (self.logic.tool.can_use_tool_at(Tool.pickaxe, ToolMaterial.basic, SVERegion.crimson_badlands) & self.logic.combat.can_fight_at_level(Performance.maximum)), - SVEFish.dulse_seaweed: self.logic.fishing.can_fish_at(Region.beach) & self.logic.season.has_any([Season.spring, Season.summer, Season.winter]) + } def get_modified_item_rules_for_deep_woods(self, items: Dict[str, StardewRule]): @@ -160,36 +90,6 @@ def get_modified_item_rules_for_deep_woods(self, items: Dict[str, StardewRule]): return options_to_update - def get_archaeology_item_rules(self): - archaeology_item_rules = {} - preservation_chamber_rule = self.logic.has(ModMachine.preservation_chamber) - hardwood_preservation_chamber_rule = self.logic.has(ModMachine.hardwood_preservation_chamber) - for item in display_items: - for display_type in display_types: - if item == "Trilobite": - location_name = f"{display_type}: Trilobite Fossil" - else: - location_name = f"{display_type}: {item}" - display_item_rule = self.logic.crafting.can_craft(all_crafting_recipes_by_name[display_type]) & self.logic.has(item) - if "Wooden" in display_type: - archaeology_item_rules[location_name] = display_item_rule & preservation_chamber_rule - else: - archaeology_item_rules[location_name] = display_item_rule & hardwood_preservation_chamber_rule - archaeology_item_rules[ModTrash.rusty_scrap] = self.logic.has(ModMachine.grinder) & self.logic.has_any(*all_artifacts) - return archaeology_item_rules - - def get_distant_lands_item_rules(self): - return { - DistantLandsForageable.swamp_herb: self.logic.region.can_reach(Region.witch_swamp), - DistantLandsForageable.brown_amanita: self.logic.region.can_reach(Region.witch_swamp), - DistantLandsSeed.vile_ancient_fruit: self.logic.quest.can_complete_quest(ModQuest.WitchOrder) | self.logic.quest.can_complete_quest( - ModQuest.CorruptedCropsTask), - DistantLandsSeed.void_mint: self.logic.quest.can_complete_quest(ModQuest.WitchOrder) | self.logic.quest.can_complete_quest( - ModQuest.CorruptedCropsTask), - DistantLandsCrop.void_mint: self.logic.season.has_any_not_winter() & self.logic.has(DistantLandsSeed.void_mint), - DistantLandsCrop.vile_ancient_fruit: self.logic.season.has_any_not_winter() & self.logic.has(DistantLandsSeed.vile_ancient_fruit), - } - def get_boarding_house_item_rules(self): return { # Mob Drops from lost valley enemies @@ -251,8 +151,3 @@ def get_boarding_house_item_rules(self): BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( Performance.great), } - - def has_seed_unlocked(self, seed_name: str): - if self.options.cropsanity == Cropsanity.option_disabled: - return True_() - return self.logic.received(seed_name) diff --git a/worlds/stardew_valley/mods/mod_regions.py b/worlds/stardew_valley/mods/mod_regions.py index c075bd4d106..a402ba60686 100644 --- a/worlds/stardew_valley/mods/mod_regions.py +++ b/worlds/stardew_valley/mods/mod_regions.py @@ -183,7 +183,8 @@ RegionData(SVERegion.first_slash_guild, [SVEEntrance.first_slash_guild_to_hallway], is_ginger_island=True), RegionData(SVERegion.first_slash_hallway, [SVEEntrance.first_slash_hallway_to_room], is_ginger_island=True), RegionData(SVERegion.first_slash_spare_room, is_ginger_island=True), - RegionData(SVERegion.highlands_outside, [SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave], is_ginger_island=True), + RegionData(SVERegion.highlands_outside, [SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave, SVEEntrance.highlands_to_pond], is_ginger_island=True), + RegionData(SVERegion.highlands_pond, is_ginger_island=True), RegionData(SVERegion.highlands_cavern, [SVEEntrance.to_dwarf_prison], is_ginger_island=True), RegionData(SVERegion.dwarf_prison, is_ginger_island=True), RegionData(SVERegion.lances_house, [SVEEntrance.lance_to_ladder], is_ginger_island=True), @@ -276,6 +277,7 @@ ConnectionData(SVEEntrance.sprite_spring_to_cave, SVERegion.sprite_spring_cave, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.fish_shop_to_willy_bedroom, SVERegion.willy_bedroom, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.museum_to_gunther_bedroom, SVERegion.gunther_bedroom, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.highlands_to_pond, SVERegion.highlands_pond), ] alecto_regions = [ diff --git a/worlds/stardew_valley/presets.py b/worlds/stardew_valley/presets.py index e663241ac6a..cf6f87a1501 100644 --- a/worlds/stardew_valley/presets.py +++ b/worlds/stardew_valley/presets.py @@ -58,7 +58,7 @@ easy_settings = { "progression_balancing": ProgressionBalancing.default, - "accessibility": Accessibility.option_items, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_community_center, FarmType.internal_name: "random", StartingMoney.internal_name: "very rich", @@ -104,7 +104,7 @@ medium_settings = { "progression_balancing": 25, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_community_center, FarmType.internal_name: "random", StartingMoney.internal_name: "rich", @@ -150,7 +150,7 @@ hard_settings = { "progression_balancing": 0, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_grandpa_evaluation, FarmType.internal_name: "random", StartingMoney.internal_name: "extra", @@ -196,7 +196,7 @@ nightmare_settings = { "progression_balancing": 0, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_community_center, FarmType.internal_name: "random", StartingMoney.internal_name: "vanilla", @@ -242,7 +242,7 @@ short_settings = { "progression_balancing": ProgressionBalancing.default, - "accessibility": Accessibility.option_items, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.option_bottom_of_the_mines, FarmType.internal_name: "random", StartingMoney.internal_name: "filthy rich", @@ -334,7 +334,7 @@ allsanity_settings = { "progression_balancing": ProgressionBalancing.default, - "accessibility": Accessibility.option_locations, + "accessibility": Accessibility.option_full, Goal.internal_name: Goal.default, FarmType.internal_name: "random", StartingMoney.internal_name: StartingMoney.default, diff --git a/worlds/stardew_valley/regions.py b/worlds/stardew_valley/regions.py index 2aca2d3f4d3..b0fc7fa0ea5 100644 --- a/worlds/stardew_valley/regions.py +++ b/worlds/stardew_valley/regions.py @@ -137,7 +137,8 @@ def __call__(self, name: str, regions: Iterable[str]) -> Region: [Entrance.island_west_to_islandfarmhouse, Entrance.island_west_to_gourmand_cave, Entrance.island_west_to_crystals_cave, Entrance.island_west_to_shipwreck, Entrance.island_west_to_qi_walnut_room, Entrance.use_farm_obelisk, Entrance.parrot_express_jungle_to_docks, Entrance.parrot_express_jungle_to_dig_site, Entrance.parrot_express_jungle_to_volcano, LogicEntrance.grow_spring_crops_on_island, - LogicEntrance.grow_summer_crops_on_island, LogicEntrance.grow_fall_crops_on_island, LogicEntrance.grow_winter_crops_on_island, LogicEntrance.grow_indoor_crops_on_island], + LogicEntrance.grow_summer_crops_on_island, LogicEntrance.grow_fall_crops_on_island, LogicEntrance.grow_winter_crops_on_island, + LogicEntrance.grow_indoor_crops_on_island], is_ginger_island=True), RegionData(Region.island_east, [Entrance.island_east_to_leo_hut, Entrance.island_east_to_island_shrine], is_ginger_island=True), RegionData(Region.island_shrine, is_ginger_island=True), @@ -536,7 +537,7 @@ def create_final_regions(world_options) -> List[RegionData]: def create_final_connections_and_regions(world_options) -> Tuple[Dict[str, ConnectionData], Dict[str, RegionData]]: regions_data: Dict[str, RegionData] = {region.name: region for region in create_final_regions(world_options)} connections = {connection.name: connection for connection in vanilla_connections} - connections = modify_connections_for_mods(connections, world_options.mods) + connections = modify_connections_for_mods(connections, sorted(world_options.mods.value)) include_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_false return remove_ginger_island_regions_and_connections(regions_data, connections, include_island) @@ -563,10 +564,8 @@ def remove_ginger_island_regions_and_connections(regions_by_name: Dict[str, Regi return connections, regions_by_name -def modify_connections_for_mods(connections: Dict[str, ConnectionData], mods) -> Dict[str, ConnectionData]: - if mods is None: - return connections - for mod in mods.value: +def modify_connections_for_mods(connections: Dict[str, ConnectionData], mods: Iterable) -> Dict[str, ConnectionData]: + for mod in mods: if mod not in ModDataList: continue if mod in vanilla_connections_to_remove_by_mod: diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index c30d04c8a6f..89b1cf87c3c 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -375,7 +375,7 @@ def set_ginger_island_rules(logic: StardewLogic, multiworld, player, world_optio MultiWorldRules.add_rule(multiworld.get_location("Open Professor Snail Cave", player), logic.has(Bomb.cherry_bomb)) MultiWorldRules.add_rule(multiworld.get_location("Complete Island Field Office", player), - logic.can_complete_field_office()) + logic.walnut.can_complete_field_office()) set_walnut_rules(logic, multiworld, player, world_options) @@ -432,10 +432,10 @@ def set_island_entrances_rules(logic: StardewLogic, multiworld, player, world_op def set_island_parrot_rules(logic: StardewLogic, multiworld, player): # Logic rules require more walnuts than in reality, to allow the player to spend them "wrong" - has_walnut = logic.has_walnut(5) - has_5_walnut = logic.has_walnut(15) - has_10_walnut = logic.has_walnut(40) - has_20_walnut = logic.has_walnut(60) + has_walnut = logic.walnut.has_walnut(5) + has_5_walnut = logic.walnut.has_walnut(15) + has_10_walnut = logic.walnut.has_walnut(40) + has_20_walnut = logic.walnut.has_walnut(60) MultiWorldRules.add_rule(multiworld.get_location("Leo's Parrot", player), has_walnut) MultiWorldRules.add_rule(multiworld.get_location("Island West Turtle", player), @@ -471,7 +471,7 @@ def set_walnut_rules(logic: StardewLogic, multiworld, player, world_options: Sta set_walnut_repeatable_rules(logic, multiworld, player, world_options) -def set_walnut_puzzle_rules(logic, multiworld, player, world_options): +def set_walnut_puzzle_rules(logic: StardewLogic, multiworld, player, world_options): if OptionName.walnutsanity_puzzles not in world_options.walnutsanity: return @@ -482,15 +482,17 @@ def set_walnut_puzzle_rules(logic, multiworld, player, world_options): logic.has(Mineral.emerald) & logic.has(Mineral.ruby) & logic.has(Mineral.topaz) & logic.region.can_reach_all((Region.island_north, Region.island_west, Region.island_east, Region.island_south))) MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Melon", player), logic.has(Fruit.melon) & logic.region.can_reach(Region.island_west)) - MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) & logic.region.can_reach(Region.island_west)) - MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) & logic.region.can_reach(Region.island_west)) + MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) & + logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Melon")) + MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) & + logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Wheat")) MultiWorldRules.add_rule(multiworld.get_location("Whack A Mole", player), logic.tool.has_tool(Tool.watering_can, ToolMaterial.iridium)) - MultiWorldRules.add_rule(multiworld.get_location("Complete Large Animal Collection", player), logic.can_complete_large_animal_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Complete Snake Collection", player), logic.can_complete_snake_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Frog Collection", player), logic.can_complete_frog_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Bat Collection", player), logic.can_complete_bat_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Purple Flowers Island Survey", player), logic.can_start_field_office) - MultiWorldRules.add_rule(multiworld.get_location("Purple Starfish Island Survey", player), logic.can_start_field_office) + MultiWorldRules.add_rule(multiworld.get_location("Complete Large Animal Collection", player), logic.walnut.can_complete_large_animal_collection()) + MultiWorldRules.add_rule(multiworld.get_location("Complete Snake Collection", player), logic.walnut.can_complete_snake_collection()) + MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Frog Collection", player), logic.walnut.can_complete_frog_collection()) + MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Bat Collection", player), logic.walnut.can_complete_bat_collection()) + MultiWorldRules.add_rule(multiworld.get_location("Purple Flowers Island Survey", player), logic.walnut.can_start_field_office) + MultiWorldRules.add_rule(multiworld.get_location("Purple Starfish Island Survey", player), logic.walnut.can_start_field_office) MultiWorldRules.add_rule(multiworld.get_location("Protruding Tree Walnut", player), logic.combat.has_slingshot) MultiWorldRules.add_rule(multiworld.get_location("Starfish Tide Pool", player), logic.tool.has_fishing_rod(1)) MultiWorldRules.add_rule(multiworld.get_location("Mermaid Song", player), logic.has(Furniture.flute_block)) @@ -1029,6 +1031,7 @@ def set_sve_ginger_island_rules(logic: StardewLogic, multiworld: MultiWorld, pla set_entrance_rule(multiworld, player, SVEEntrance.wizard_to_fable_reef, logic.received(SVEQuestItem.fable_reef_portal)) set_entrance_rule(multiworld, player, SVEEntrance.highlands_to_cave, logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron) & logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) + set_entrance_rule(multiworld, player, SVEEntrance.highlands_to_pond, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) def set_boarding_house_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): diff --git a/worlds/stardew_valley/stardew_rule/base.py b/worlds/stardew_valley/stardew_rule/base.py index 576cd36851f..3e6eb327ea9 100644 --- a/worlds/stardew_valley/stardew_rule/base.py +++ b/worlds/stardew_valley/stardew_rule/base.py @@ -431,7 +431,7 @@ def rules_count(self): return len(self.rules) def __repr__(self): - return f"Received {self.count} {repr(self.rules)}" + return f"Received {self.count} [{', '.join(f'{value}x {repr(rule)}' for rule, value in self.counter.items())}]" @dataclass(frozen=True) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index 61a88ceb699..a9767c7b72d 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -34,7 +34,7 @@ def __str__(self, depth=0): if not self.sub_rules: return self.summary(depth) - return self.summary(depth) + "\n" + "\n".join(RuleExplanation.__str__(i, depth + 1) + return self.summary(depth) + "\n" + "\n".join(i.__str__(depth + 1) if i.result is not self.expected else i.summary(depth + 1) for i in sorted(self.explained_sub_rules, key=lambda x: x.result)) @@ -42,7 +42,7 @@ def __repr__(self, depth=0): if not self.sub_rules: return self.summary(depth) - return self.summary(depth) + "\n" + "\n".join(RuleExplanation.__repr__(i, depth + 1) + return self.summary(depth) + "\n" + "\n".join(i.__repr__(depth + 1) for i in sorted(self.explained_sub_rules, key=lambda x: x.result)) @cached_property @@ -61,6 +61,33 @@ def explained_sub_rules(self) -> List[RuleExplanation]: return [_explain(i, self.state, self.expected, self.explored_rules_key) for i in self.sub_rules] +@dataclass +class CountSubRuleExplanation(RuleExplanation): + count: int = 1 + + @staticmethod + def from_explanation(expl: RuleExplanation, count: int) -> CountSubRuleExplanation: + return CountSubRuleExplanation(expl.rule, expl.state, expl.expected, expl.sub_rules, expl.explored_rules_key, expl.current_rule_explored, count) + + def summary(self, depth=0) -> str: + summary = " " * depth + f"{self.count}x {str(self.rule)} -> {self.result}" + if self.current_rule_explored: + summary += " [Already explained]" + return summary + + +@dataclass +class CountExplanation(RuleExplanation): + rule: Count + + @cached_property + def explained_sub_rules(self) -> List[RuleExplanation]: + return [ + CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.explored_rules_key), count) + for rule, count in self.rule.counter.items() + ] + + def explain(rule: CollectionRule, state: CollectionState, expected: bool = True) -> RuleExplanation: if isinstance(rule, StardewRule): return _explain(rule, state, expected, explored_spots=set()) @@ -80,7 +107,7 @@ def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool, expl @_explain.register def _(rule: Count, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, rule.rules, explored_rules_key=explored_spots) + return CountExplanation(rule, state, expected, rule.rules, explored_rules_key=explored_spots) @_explain.register diff --git a/worlds/stardew_valley/stardew_rule/state.py b/worlds/stardew_valley/stardew_rule/state.py index cf0996a63bb..5f5e61b3d4e 100644 --- a/worlds/stardew_valley/stardew_rule/state.py +++ b/worlds/stardew_valley/stardew_rule/state.py @@ -122,4 +122,4 @@ def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRul return self, self(state) def __repr__(self): - return f"Received {self.percent}% progression items." + return f"Received {self.percent}% progression items" diff --git a/worlds/stardew_valley/strings/book_names.py b/worlds/stardew_valley/strings/book_names.py index 3c32cd81b32..6c271f42ae9 100644 --- a/worlds/stardew_valley/strings/book_names.py +++ b/worlds/stardew_valley/strings/book_names.py @@ -27,10 +27,6 @@ class Book: the_diamond_hunter = "The Diamond Hunter" -class ModBook: - digging_like_worms = "Digging Like Worms" - - ordered_lost_books = [] all_lost_books = set() diff --git a/worlds/stardew_valley/strings/entrance_names.py b/worlds/stardew_valley/strings/entrance_names.py index 9b651f42760..58a919f2a8a 100644 --- a/worlds/stardew_valley/strings/entrance_names.py +++ b/worlds/stardew_valley/strings/entrance_names.py @@ -358,6 +358,7 @@ class SVEEntrance: sprite_spring_to_cave = "Sprite Spring to Sprite Spring Cave" fish_shop_to_willy_bedroom = "Willy's Fish Shop to Willy's Bedroom" museum_to_gunther_bedroom = "Museum to Gunther's Bedroom" + highlands_to_pond = "Highlands to Highlands Pond" class AlectoEntrance: diff --git a/worlds/stardew_valley/strings/fish_names.py b/worlds/stardew_valley/strings/fish_names.py index d94f9e2fd40..d4ee81430eb 100644 --- a/worlds/stardew_valley/strings/fish_names.py +++ b/worlds/stardew_valley/strings/fish_names.py @@ -137,7 +137,6 @@ class SVEFish: void_eel = "Void Eel" water_grub = "Water Grub" sea_sponge = "Sea Sponge" - dulse_seaweed = "Dulse Seaweed" class DistantLandsFish: @@ -147,6 +146,10 @@ class DistantLandsFish: giant_horsehoe_crab = "Giant Horsehoe Crab" +class SVEWaterItem: + dulse_seaweed = "Dulse Seaweed" + + class ModTrash: rusty_scrap = "Scrap Rust" diff --git a/worlds/stardew_valley/strings/food_names.py b/worlds/stardew_valley/strings/food_names.py index 5555316f831..03784336d19 100644 --- a/worlds/stardew_valley/strings/food_names.py +++ b/worlds/stardew_valley/strings/food_names.py @@ -102,6 +102,7 @@ class SVEMeal: void_delight = "Void Delight" void_salmon_sushi = "Void Salmon Sushi" grampleton_orange_chicken = "Grampleton Orange Chicken" + stamina_capsule = "Stamina Capsule" class TrashyMeal: diff --git a/worlds/stardew_valley/strings/region_names.py b/worlds/stardew_valley/strings/region_names.py index 9cedb6b8ef3..58763b6fcb8 100644 --- a/worlds/stardew_valley/strings/region_names.py +++ b/worlds/stardew_valley/strings/region_names.py @@ -296,6 +296,7 @@ class SVERegion: sprite_spring_cave = "Sprite Spring Cave" willy_bedroom = "Willy's Bedroom" gunther_bedroom = "Gunther's Bedroom" + highlands_pond = "Highlands Pond" class AlectoRegion: diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py index bee02f3c3d6..7e82ea91e43 100644 --- a/worlds/stardew_valley/test/__init__.py +++ b/worlds/stardew_valley/test/__init__.py @@ -6,7 +6,7 @@ from contextlib import contextmanager from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any -from BaseClasses import MultiWorld, CollectionState, get_seed, Location, Item, ItemClassification +from BaseClasses import MultiWorld, CollectionState, PlandoOptions, get_seed, Location, Item, ItemClassification from Options import VerifyKeys from test.bases import WorldTestBase from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld @@ -365,7 +365,7 @@ def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOp if issubclass(option, VerifyKeys): # Values should already be verified, but just in case... - option.verify_keys(value.value) + value.verify(StardewValleyWorld, "Tester", PlandoOptions.bosses) setattr(args, name, {1: value}) multiworld.set_options(args) @@ -441,6 +441,16 @@ def setup_multiworld(test_options: Iterable[Dict[str, int]] = None, seed=None) - for i in range(1, len(test_options) + 1): multiworld.game[i] = StardewValleyWorld.game multiworld.player_name.update({i: f"Tester{i}"}) + args = create_args(test_options) + multiworld.set_options(args) + + for step in gen_steps: + call_all(multiworld, step) + + return multiworld + + +def create_args(test_options): args = Namespace() for name, option in StardewValleyWorld.options_dataclass.type_hints.items(): options = {} @@ -449,9 +459,4 @@ def setup_multiworld(test_options: Iterable[Dict[str, int]] = None, seed=None) - value = option(player_options[name]) if name in player_options else option.from_any(option.default) options.update({i: value}) setattr(args, name, options) - multiworld.set_options(args) - - for step in gen_steps: - call_all(multiworld, step) - - return multiworld + return args diff --git a/worlds/stardew_valley/test/mods/TestMods.py b/worlds/stardew_valley/test/mods/TestMods.py index 5e7e9d4143b..97184b1338b 100644 --- a/worlds/stardew_valley/test/mods/TestMods.py +++ b/worlds/stardew_valley/test/mods/TestMods.py @@ -14,7 +14,8 @@ class TestGenerateModsOptions(WorldAssertMixin, ModAssertMixin, SVTestCase): def test_given_single_mods_when_generate_then_basic_checks(self): for mod in options.Mods.valid_keys: - with self.solo_world_sub_test(f"Mod: {mod}", {options.Mods: mod}) as (multi_world, _): + world_options = {options.Mods: mod, options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false} + with self.solo_world_sub_test(f"Mod: {mod}", world_options) as (multi_world, _): self.assert_basic_checks(multi_world) self.assert_stray_mod_items(mod, multi_world) @@ -22,8 +23,9 @@ def test_given_mod_names_when_generate_paired_with_entrance_randomizer_then_basi for option in options.EntranceRandomization.options: for mod in options.Mods.valid_keys: world_options = { - options.EntranceRandomization.internal_name: options.EntranceRandomization.options[option], - options.Mods: mod + options.EntranceRandomization: options.EntranceRandomization.options[option], + options.Mods: mod, + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false } with self.solo_world_sub_test(f"entrance_randomization: {option}, Mod: {mod}", world_options) as (multi_world, _): self.assert_basic_checks(multi_world) diff --git a/worlds/stardew_valley/test/rules/TestBundles.py b/worlds/stardew_valley/test/rules/TestBundles.py index 25d4c70b2ab..ab376c90d4e 100644 --- a/worlds/stardew_valley/test/rules/TestBundles.py +++ b/worlds/stardew_valley/test/rules/TestBundles.py @@ -37,7 +37,7 @@ class TestRaccoonBundlesLogic(SVTestBase): options.BundlePrice: options.BundlePrice.option_normal, options.Craftsanity: options.Craftsanity.option_all, } - seed = 1234 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles + seed = 2 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles def test_raccoon_bundles_rely_on_previous_ones(self): # The first raccoon bundle is a fishing one diff --git a/worlds/stardew_valley/test/stability/StabilityOutputScript.py b/worlds/stardew_valley/test/stability/StabilityOutputScript.py index 4b31011d9f4..c8918d6cf2e 100644 --- a/worlds/stardew_valley/test/stability/StabilityOutputScript.py +++ b/worlds/stardew_valley/test/stability/StabilityOutputScript.py @@ -1,6 +1,7 @@ import argparse import json +from ...options import FarmType, EntranceRandomization from ...test import setup_solo_multiworld, allsanity_mods_6_x_x if __name__ == "__main__": @@ -10,21 +11,23 @@ args = parser.parse_args() seed = args.seed - multi_world = setup_solo_multiworld( - allsanity_mods_6_x_x(), - seed=seed - ) + options = allsanity_mods_6_x_x() + options[FarmType.internal_name] = FarmType.option_standard + options[EntranceRandomization.internal_name] = EntranceRandomization.option_buildings + multi_world = setup_solo_multiworld(options, seed=seed) + world = multi_world.worlds[1] output = { "bundles": { bundle_room.name: { bundle.name: str(bundle.items) for bundle in bundle_room.bundles } - for bundle_room in multi_world.worlds[1].modified_bundles + for bundle_room in world.modified_bundles }, "items": [item.name for item in multi_world.get_items()], - "location_rules": {location.name: repr(location.access_rule) for location in multi_world.get_locations(1)} + "location_rules": {location.name: repr(location.access_rule) for location in multi_world.get_locations(1)}, + "slot_data": world.fill_slot_data() } print(json.dumps(output)) diff --git a/worlds/stardew_valley/test/stability/TestStability.py b/worlds/stardew_valley/test/stability/TestStability.py index aaa8b331846..8bb904a56ea 100644 --- a/worlds/stardew_valley/test/stability/TestStability.py +++ b/worlds/stardew_valley/test/stability/TestStability.py @@ -24,8 +24,7 @@ def test_all_locations_and_items_are_the_same_between_two_generations(self): if self.skip_long_tests: raise unittest.SkipTest("Long tests disabled") - # seed = get_seed(33778671150797368040) # troubleshooting seed - seed = get_seed(74716545478307145559) + seed = get_seed() output_a = subprocess.check_output([sys.executable, '-m', 'worlds.stardew_valley.test.stability.StabilityOutputScript', '--seed', str(seed)]) output_b = subprocess.check_output([sys.executable, '-m', 'worlds.stardew_valley.test.stability.StabilityOutputScript', '--seed', str(seed)]) @@ -54,3 +53,6 @@ def test_all_locations_and_items_are_the_same_between_two_generations(self): # We check that the actual rule has the same order to make sure it is evaluated in the same order, # so performance tests are repeatable as much as possible. self.assertEqual(rule_a, rule_b, f"Location rule of {location_a} at index {i} is different between both executions. Seed={seed}") + + for key, value in result_a["slot_data"].items(): + self.assertEqual(value, result_b["slot_data"][key], f"Slot data {key} is different between both executions. Seed={seed}") diff --git a/worlds/stardew_valley/test/stability/TestUniversalTracker.py b/worlds/stardew_valley/test/stability/TestUniversalTracker.py new file mode 100644 index 00000000000..3e334098341 --- /dev/null +++ b/worlds/stardew_valley/test/stability/TestUniversalTracker.py @@ -0,0 +1,52 @@ +import unittest +from unittest.mock import Mock + +from .. import SVTestBase, create_args, allsanity_mods_6_x_x +from ... import STARDEW_VALLEY, FarmType, BundleRandomization, EntranceRandomization + + +class TestUniversalTrackerGenerationIsStable(SVTestBase): + options = allsanity_mods_6_x_x() + options.update({ + EntranceRandomization.internal_name: EntranceRandomization.option_buildings, + BundleRandomization.internal_name: BundleRandomization.option_shuffled, + FarmType.internal_name: FarmType.option_standard, # Need to choose one otherwise it's random + }) + + def test_all_locations_and_items_are_the_same_between_two_generations(self): + # This might open a kivy window temporarily, but it's the only way to test this... + if self.skip_long_tests: + raise unittest.SkipTest("Long tests disabled") + + try: + # This test only run if UT is present, so no risk of running in the CI. + from worlds.tracker.TrackerClient import TrackerGameContext # noqa + except ImportError: + raise unittest.SkipTest("UT not loaded, skipping test") + + slot_data = self.world.fill_slot_data() + ut_data = self.world.interpret_slot_data(slot_data) + + fake_context = Mock() + fake_context.re_gen_passthrough = {STARDEW_VALLEY: ut_data} + args = create_args({0: self.options}) + args.outputpath = None + args.outputname = None + args.multi = 1 + args.race = None + args.plando_options = self.multiworld.plando_options + args.plando_items = self.multiworld.plando_items + args.plando_texts = self.multiworld.plando_texts + args.plando_connections = self.multiworld.plando_connections + args.game = self.multiworld.game + args.name = self.multiworld.player_name + args.sprite = {} + args.sprite_pool = {} + args.skip_output = True + + generated_multi_world = TrackerGameContext.TMain(fake_context, args, self.multiworld.seed) + generated_slot_data = generated_multi_world.worlds[1].fill_slot_data() + + # Just checking slot data should prove that UT generates the same result as AP generation. + self.maxDiff = None + self.assertEqual(slot_data, generated_slot_data) diff --git a/worlds/subnautica/rules.py b/worlds/subnautica/rules.py index 3b6c5cd4dd6..ea9ec6a8058 100644 --- a/worlds/subnautica/rules.py +++ b/worlds/subnautica/rules.py @@ -150,7 +150,7 @@ def has_ultra_glide_fins(state: "CollectionState", player: int) -> bool: def get_max_swim_depth(state: "CollectionState", player: int) -> int: - swim_rule: SwimRule = state.multiworld.swim_rule[player] + swim_rule: SwimRule = state.multiworld.worlds[player].options.swim_rule depth: int = swim_rule.base_depth if swim_rule.consider_items: if has_seaglide(state, player): @@ -296,7 +296,7 @@ def set_rules(subnautica_world: "SubnauticaWorld"): set_location_rule(multiworld, player, loc) if subnautica_world.creatures_to_scan: - option = multiworld.creature_scan_logic[player] + option = multiworld.worlds[player].options.creature_scan_logic for creature_name in subnautica_world.creatures_to_scan: location = set_creature_rule(multiworld, player, creature_name) diff --git a/worlds/timespinner/Locations.py b/worlds/timespinner/Locations.py index 7b378b4637f..86839f0f216 100644 --- a/worlds/timespinner/Locations.py +++ b/worlds/timespinner/Locations.py @@ -1,6 +1,6 @@ from typing import List, Optional, Callable, NamedTuple -from BaseClasses import MultiWorld, CollectionState -from .Options import is_option_enabled +from BaseClasses import CollectionState +from .Options import TimespinnerOptions from .PreCalculatedWeights import PreCalculatedWeights from .LogicExtensions import TimespinnerLogic @@ -14,11 +14,10 @@ class LocationData(NamedTuple): rule: Optional[Callable[[CollectionState], bool]] = None -def get_location_datas(world: Optional[MultiWorld], player: Optional[int], - precalculated_weights: PreCalculatedWeights) -> List[LocationData]: - - flooded: PreCalculatedWeights = precalculated_weights - logic = TimespinnerLogic(world, player, precalculated_weights) +def get_location_datas(player: Optional[int], options: Optional[TimespinnerOptions], + precalculated_weights: Optional[PreCalculatedWeights]) -> List[LocationData]: + flooded: Optional[PreCalculatedWeights] = precalculated_weights + logic = TimespinnerLogic(player, options, precalculated_weights) # 1337000 - 1337155 Generic locations # 1337171 - 1337175 New Pickup checks @@ -203,7 +202,7 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], ] # 1337156 - 1337170 Downloads - if not world or is_option_enabled(world, player, "DownloadableItems"): + if not options or options.downloadable_items: location_table += ( LocationData('Library', 'Library: Terminal 2 (Lachiem)', 1337156, lambda state: state.has('Tablet', player)), LocationData('Library', 'Library: Terminal 1 (Windaria)', 1337157, lambda state: state.has('Tablet', player)), @@ -223,13 +222,13 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], ) # 1337176 - 1337176 Cantoran - if not world or is_option_enabled(world, player, "Cantoran"): + if not options or options.cantoran: location_table += ( LocationData('Left Side forest Caves', 'Lake Serene: Cantoran', 1337176), ) # 1337177 - 1337198 Lore Checks - if not world or is_option_enabled(world, player, "LoreChecks"): + if not options or options.lore_checks: location_table += ( LocationData('Lower lake desolation', 'Lake Desolation: Memory - Coyote Jump (Time Messenger)', 1337177), LocationData('Library', 'Library: Memory - Waterway (A Message)', 1337178), @@ -258,7 +257,7 @@ def get_location_datas(world: Optional[MultiWorld], player: Optional[int], # 1337199 - 1337236 Reserved for future use # 1337237 - 1337245 GyreArchives - if not world or is_option_enabled(world, player, "GyreArchives"): + if not options or options.gyre_archives: location_table += ( LocationData('Ravenlord\'s Lair', 'Ravenlord: Post fight (pedestal)', 1337237), LocationData('Ifrit\'s Lair', 'Ifrit: Post fight (pedestal)', 1337238), diff --git a/worlds/timespinner/LogicExtensions.py b/worlds/timespinner/LogicExtensions.py index d316a936b02..6c9cb3f684a 100644 --- a/worlds/timespinner/LogicExtensions.py +++ b/worlds/timespinner/LogicExtensions.py @@ -1,6 +1,6 @@ -from typing import Union -from BaseClasses import MultiWorld, CollectionState -from .Options import is_option_enabled +from typing import Union, Optional +from BaseClasses import CollectionState +from .Options import TimespinnerOptions from .PreCalculatedWeights import PreCalculatedWeights @@ -10,17 +10,18 @@ class TimespinnerLogic: flag_unchained_keys: bool flag_eye_spy: bool flag_specific_keycards: bool - pyramid_keys_unlock: Union[str, None] - present_keys_unlock: Union[str, None] - past_keys_unlock: Union[str, None] - time_keys_unlock: Union[str, None] + pyramid_keys_unlock: Optional[str] + present_keys_unlock: Optional[str] + past_keys_unlock: Optional[str] + time_keys_unlock: Optional[str] - def __init__(self, world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights): + def __init__(self, player: int, options: Optional[TimespinnerOptions], + precalculated_weights: Optional[PreCalculatedWeights]): self.player = player - self.flag_specific_keycards = is_option_enabled(world, player, "SpecificKeycards") - self.flag_eye_spy = is_option_enabled(world, player, "EyeSpy") - self.flag_unchained_keys = is_option_enabled(world, player, "UnchainedKeys") + self.flag_specific_keycards = bool(options and options.specific_keycards) + self.flag_eye_spy = bool(options and options.eye_spy) + self.flag_unchained_keys = bool(options and options.unchained_keys) if precalculated_weights: if self.flag_unchained_keys: diff --git a/worlds/timespinner/Options.py b/worlds/timespinner/Options.py index f7921fcb81e..20ad8132c45 100644 --- a/worlds/timespinner/Options.py +++ b/worlds/timespinner/Options.py @@ -1,59 +1,50 @@ -from typing import Dict, Union, List -from BaseClasses import MultiWorld -from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict, OptionList +from dataclasses import dataclass +from typing import Type, Any +from typing import Dict +from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, OptionDict, OptionList, Visibility, Option +from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions from schema import Schema, And, Optional, Or - class StartWithJewelryBox(Toggle): "Start with Jewelry Box unlocked" display_name = "Start with Jewelry Box" - class DownloadableItems(DefaultOnToggle): "With the tablet you will be able to download items at terminals" display_name = "Downloadable items" - class EyeSpy(Toggle): "Requires Oculus Ring in inventory to be able to break hidden walls." display_name = "Eye Spy" - class StartWithMeyef(Toggle): "Start with Meyef, ideal for when you want to play multiplayer." display_name = "Start with Meyef" - class QuickSeed(Toggle): "Start with Talaria Attachment, Nyoom!" display_name = "Quick seed" - class SpecificKeycards(Toggle): "Keycards can only open corresponding doors" display_name = "Specific Keycards" - class Inverted(Toggle): "Start in the past" display_name = "Inverted" - class GyreArchives(Toggle): "Gyre locations are in logic. New warps are gated by Merchant Crow and Kobo" display_name = "Gyre Archives" - class Cantoran(Toggle): "Cantoran's fight and check are available upon revisiting his room" display_name = "Cantoran" - class LoreChecks(Toggle): "Memories and journal entries contain items." display_name = "Lore Checks" - class BossRando(Choice): "Wheter all boss locations are shuffled, and if their damage/hp should be scaled." display_name = "Boss Randomization" @@ -62,7 +53,6 @@ class BossRando(Choice): option_unscaled = 2 alias_true = 1 - class EnemyRando(Choice): "Wheter enemies will be randomized, and if their damage/hp should be scaled." display_name = "Enemy Randomization" @@ -72,7 +62,6 @@ class EnemyRando(Choice): option_ryshia = 3 alias_true = 1 - class DamageRando(Choice): "Randomly nerfs and buffs some orbs and their associated spells as well as some associated rings." display_name = "Damage Rando" @@ -85,7 +74,6 @@ class DamageRando(Choice): option_manual = 6 alias_true = 2 - class DamageRandoOverrides(OptionDict): """Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that you don't specify will roll with 1/1/1 as odds""" @@ -191,7 +179,6 @@ class DamageRandoOverrides(OptionDict): "Radiant": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 }, } - class HpCap(Range): "Sets the number that Lunais's HP maxes out at." display_name = "HP Cap" @@ -199,7 +186,6 @@ class HpCap(Range): range_end = 999 default = 999 - class LevelCap(Range): """Sets the max level Lunais can achieve.""" display_name = "Level Cap" @@ -207,20 +193,17 @@ class LevelCap(Range): range_end = 99 default = 99 - class ExtraEarringsXP(Range): """Adds additional XP granted by Galaxy Earrings.""" display_name = "Extra Earrings XP" range_start = 0 range_end = 24 default = 0 - class BossHealing(DefaultOnToggle): "Enables/disables healing after boss fights. NOTE: Currently only applicable when Boss Rando is enabled." display_name = "Heal After Bosses" - class ShopFill(Choice): """Sets the items for sale in Merchant Crow's shops. Default: No sunglasses or trendy jacket, but sand vials for sale. @@ -233,12 +216,10 @@ class ShopFill(Choice): option_vanilla = 2 option_empty = 3 - class ShopWarpShards(DefaultOnToggle): "Shops always sell warp shards (when keys possessed), ignoring inventory setting." display_name = "Always Sell Warp Shards" - class ShopMultiplier(Range): "Multiplier for the cost of items in the shop. Set to 0 for free shops." display_name = "Shop Price Multiplier" @@ -246,7 +227,6 @@ class ShopMultiplier(Range): range_end = 10 default = 1 - class LootPool(Choice): """Sets the items that drop from enemies (does not apply to boss reward checks) Vanilla: Drops are the same as the base game @@ -257,7 +237,6 @@ class LootPool(Choice): option_randomized = 1 option_empty = 2 - class DropRateCategory(Choice): """Sets the drop rate when 'Loot Pool' is set to 'Random' Tiered: Based on item rarity/value @@ -271,7 +250,6 @@ class DropRateCategory(Choice): option_randomized = 2 option_fixed = 3 - class FixedDropRate(Range): "Base drop rate percentage when 'Drop Rate Category' is set to 'Fixed'" display_name = "Fixed Drop Rate" @@ -279,7 +257,6 @@ class FixedDropRate(Range): range_end = 100 default = 5 - class LootTierDistro(Choice): """Sets how often items of each rarity tier are placed when 'Loot Pool' is set to 'Random' Default Weight: Rarer items will be assigned to enemy drop slots less frequently than common items @@ -291,32 +268,26 @@ class LootTierDistro(Choice): option_full_random = 1 option_inverted_weight = 2 - class ShowBestiary(Toggle): "All entries in the bestiary are visible, without needing to kill one of a given enemy first" display_name = "Show Bestiary Entries" - class ShowDrops(Toggle): "All item drops in the bestiary are visible, without needing an enemy to drop one of a given item first" display_name = "Show Bestiary Item Drops" - class EnterSandman(Toggle): "The Ancient Pyramid is unlocked by the Twin Pyramid Keys, but the final boss door opens if you have all 5 Timespinner pieces" display_name = "Enter Sandman" - class DadPercent(Toggle): """The win condition is beating the boss of Emperor's Tower""" display_name = "Dad Percent" - class RisingTides(Toggle): """Random areas are flooded or drained, can be further specified with RisingTidesOverrides""" display_name = "Rising Tides" - def rising_tide_option(location: str, with_save_point_option: bool = False) -> Dict[Optional, Or]: if with_save_point_option: return { @@ -341,7 +312,6 @@ def rising_tide_option(location: str, with_save_point_option: bool = False) -> D "Flooded") } - class RisingTidesOverrides(OptionDict): """Odds for specific areas to be flooded or drained, only has effect when RisingTides is on. Areas that are not specified will roll with the default 33% chance of getting flooded or drained""" @@ -373,13 +343,11 @@ class RisingTidesOverrides(OptionDict): "Lab": { "Dry": 67, "Flooded": 33 }, } - class UnchainedKeys(Toggle): """Start with Twin Pyramid Key, which does not give free warp; warp items for Past, Present, (and ??? with Enter Sandman) can be found.""" display_name = "Unchained Keys" - class TrapChance(Range): """Chance of traps in the item pool. Traps will only replace filler items such as potions, vials and antidotes""" @@ -388,67 +356,256 @@ class TrapChance(Range): range_end = 100 default = 10 - class Traps(OptionList): """List of traps that may be in the item pool to find""" display_name = "Traps Types" valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" } default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" ] - class PresentAccessWithWheelAndSpindle(Toggle): """When inverted, allows using the refugee camp warp when both the Timespinner Wheel and Spindle is acquired.""" - display_name = "Past Wheel & Spindle Warp" - - -# Some options that are available in the timespinner randomizer arent currently implemented -timespinner_options: Dict[str, Option] = { - "StartWithJewelryBox": StartWithJewelryBox, - "DownloadableItems": DownloadableItems, - "EyeSpy": EyeSpy, - "StartWithMeyef": StartWithMeyef, - "QuickSeed": QuickSeed, - "SpecificKeycards": SpecificKeycards, - "Inverted": Inverted, - "GyreArchives": GyreArchives, - "Cantoran": Cantoran, - "LoreChecks": LoreChecks, - "BossRando": BossRando, - "EnemyRando": EnemyRando, - "DamageRando": DamageRando, - "DamageRandoOverrides": DamageRandoOverrides, - "HpCap": HpCap, - "LevelCap": LevelCap, - "ExtraEarringsXP": ExtraEarringsXP, - "BossHealing": BossHealing, - "ShopFill": ShopFill, - "ShopWarpShards": ShopWarpShards, - "ShopMultiplier": ShopMultiplier, - "LootPool": LootPool, - "DropRateCategory": DropRateCategory, - "FixedDropRate": FixedDropRate, - "LootTierDistro": LootTierDistro, - "ShowBestiary": ShowBestiary, - "ShowDrops": ShowDrops, - "EnterSandman": EnterSandman, - "DadPercent": DadPercent, - "RisingTides": RisingTides, - "RisingTidesOverrides": RisingTidesOverrides, - "UnchainedKeys": UnchainedKeys, - "TrapChance": TrapChance, - "Traps": Traps, - "PresentAccessWithWheelAndSpindle": PresentAccessWithWheelAndSpindle, - "DeathLink": DeathLink, -} - - -def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool: - return get_option_value(world, player, name) > 0 - - -def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, Dict, List]: - option = getattr(world, name, None) - if option == None: - return 0 - - return option[player].value + display_name = "Back to the future" + +@dataclass +class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin): + start_with_jewelry_box: StartWithJewelryBox + downloadable_items: DownloadableItems + eye_spy: EyeSpy + start_with_meyef: StartWithMeyef + quick_seed: QuickSeed + specific_keycards: SpecificKeycards + inverted: Inverted + gyre_archives: GyreArchives + cantoran: Cantoran + lore_checks: LoreChecks + boss_rando: BossRando + damage_rando: DamageRando + damage_rando_overrides: DamageRandoOverrides + hp_cap: HpCap + level_cap: LevelCap + extra_earrings_xp: ExtraEarringsXP + boss_healing: BossHealing + shop_fill: ShopFill + shop_warp_shards: ShopWarpShards + shop_multiplier: ShopMultiplier + loot_pool: LootPool + drop_rate_category: DropRateCategory + fixed_drop_rate: FixedDropRate + loot_tier_distro: LootTierDistro + show_bestiary: ShowBestiary + show_drops: ShowDrops + enter_sandman: EnterSandman + dad_percent: DadPercent + rising_tides: RisingTides + rising_tides_overrides: RisingTidesOverrides + unchained_keys: UnchainedKeys + back_to_the_future: PresentAccessWithWheelAndSpindle + trap_chance: TrapChance + traps: Traps + +class HiddenDamageRandoOverrides(DamageRandoOverrides): + """Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that + you don't specify will roll with 1/1/1 as odds""" + visibility = Visibility.none + +class HiddenRisingTidesOverrides(RisingTidesOverrides): + """Odds for specific areas to be flooded or drained, only has effect when RisingTides is on. + Areas that are not specified will roll with the default 33% chance of getting flooded or drained""" + visibility = Visibility.none + +class HiddenTraps(Traps): + """List of traps that may be in the item pool to find""" + visibility = Visibility.none + +class OptionsHider: + @classmethod + def hidden(cls, option: Type[Option[Any]]) -> Type[Option]: + new_option = AssembleOptions(f"{option}Hidden", option.__bases__, vars(option).copy()) + new_option.visibility = Visibility.none + new_option.__doc__ = option.__doc__ + return new_option + +class HasReplacedCamelCase(Toggle): + """For internal use will display a warning message if true""" + visibility = Visibility.none + +@dataclass +class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions): + StartWithJewelryBox: OptionsHider.hidden(StartWithJewelryBox) # type: ignore + DownloadableItems: OptionsHider.hidden(DownloadableItems) # type: ignore + EyeSpy: OptionsHider.hidden(EyeSpy) # type: ignore + StartWithMeyef: OptionsHider.hidden(StartWithMeyef) # type: ignore + QuickSeed: OptionsHider.hidden(QuickSeed) # type: ignore + SpecificKeycards: OptionsHider.hidden(SpecificKeycards) # type: ignore + Inverted: OptionsHider.hidden(Inverted) # type: ignore + GyreArchives: OptionsHider.hidden(GyreArchives) # type: ignore + Cantoran: OptionsHider.hidden(Cantoran) # type: ignore + LoreChecks: OptionsHider.hidden(LoreChecks) # type: ignore + BossRando: OptionsHider.hidden(BossRando) # type: ignore + DamageRando: OptionsHider.hidden(DamageRando) # type: ignore + DamageRandoOverrides: HiddenDamageRandoOverrides + HpCap: OptionsHider.hidden(HpCap) # type: ignore + LevelCap: OptionsHider.hidden(LevelCap) # type: ignore + ExtraEarringsXP: OptionsHider.hidden(ExtraEarringsXP) # type: ignore + BossHealing: OptionsHider.hidden(BossHealing) # type: ignore + ShopFill: OptionsHider.hidden(ShopFill) # type: ignore + ShopWarpShards: OptionsHider.hidden(ShopWarpShards) # type: ignore + ShopMultiplier: OptionsHider.hidden(ShopMultiplier) # type: ignore + LootPool: OptionsHider.hidden(LootPool) # type: ignore + DropRateCategory: OptionsHider.hidden(DropRateCategory) # type: ignore + FixedDropRate: OptionsHider.hidden(FixedDropRate) # type: ignore + LootTierDistro: OptionsHider.hidden(LootTierDistro) # type: ignore + ShowBestiary: OptionsHider.hidden(ShowBestiary) # type: ignore + ShowDrops: OptionsHider.hidden(ShowDrops) # type: ignore + EnterSandman: OptionsHider.hidden(EnterSandman) # type: ignore + DadPercent: OptionsHider.hidden(DadPercent) # type: ignore + RisingTides: OptionsHider.hidden(RisingTides) # type: ignore + RisingTidesOverrides: HiddenRisingTidesOverrides + UnchainedKeys: OptionsHider.hidden(UnchainedKeys) # type: ignore + PresentAccessWithWheelAndSpindle: OptionsHider.hidden(PresentAccessWithWheelAndSpindle) # type: ignore + TrapChance: OptionsHider.hidden(TrapChance) # type: ignore + Traps: HiddenTraps # type: ignore + DeathLink: OptionsHider.hidden(DeathLink) # type: ignore + has_replaced_options: HasReplacedCamelCase + + def handle_backward_compatibility(self) -> None: + if self.StartWithJewelryBox != StartWithJewelryBox.default and \ + self.start_with_jewelry_box == StartWithJewelryBox.default: + self.start_with_jewelry_box.value = self.StartWithJewelryBox.value + self.has_replaced_options.value = Toggle.option_true + if self.DownloadableItems != DownloadableItems.default and \ + self.downloadable_items == DownloadableItems.default: + self.downloadable_items.value = self.DownloadableItems.value + self.has_replaced_options.value = Toggle.option_true + if self.EyeSpy != EyeSpy.default and \ + self.eye_spy == EyeSpy.default: + self.eye_spy.value = self.EyeSpy.value + self.has_replaced_options.value = Toggle.option_true + if self.StartWithMeyef != StartWithMeyef.default and \ + self.start_with_meyef == StartWithMeyef.default: + self.start_with_meyef.value = self.StartWithMeyef.value + self.has_replaced_options.value = Toggle.option_true + if self.QuickSeed != QuickSeed.default and \ + self.quick_seed == QuickSeed.default: + self.quick_seed.value = self.QuickSeed.value + self.has_replaced_options.value = Toggle.option_true + if self.SpecificKeycards != SpecificKeycards.default and \ + self.specific_keycards == SpecificKeycards.default: + self.specific_keycards.value = self.SpecificKeycards.value + self.has_replaced_options.value = Toggle.option_true + if self.Inverted != Inverted.default and \ + self.inverted == Inverted.default: + self.inverted.value = self.Inverted.value + self.has_replaced_options.value = Toggle.option_true + if self.GyreArchives != GyreArchives.default and \ + self.gyre_archives == GyreArchives.default: + self.gyre_archives.value = self.GyreArchives.value + self.has_replaced_options.value = Toggle.option_true + if self.Cantoran != Cantoran.default and \ + self.cantoran == Cantoran.default: + self.cantoran.value = self.Cantoran.value + self.has_replaced_options.value = Toggle.option_true + if self.LoreChecks != LoreChecks.default and \ + self.lore_checks == LoreChecks.default: + self.lore_checks.value = self.LoreChecks.value + self.has_replaced_options.value = Toggle.option_true + if self.BossRando != BossRando.default and \ + self.boss_rando == BossRando.default: + self.boss_rando.value = self.BossRando.value + self.has_replaced_options.value = Toggle.option_true + if self.DamageRando != DamageRando.default and \ + self.damage_rando == DamageRando.default: + self.damage_rando.value = self.DamageRando.value + self.has_replaced_options.value = Toggle.option_true + if self.DamageRandoOverrides != DamageRandoOverrides.default and \ + self.damage_rando_overrides == DamageRandoOverrides.default: + self.damage_rando_overrides.value = self.DamageRandoOverrides.value + self.has_replaced_options.value = Toggle.option_true + if self.HpCap != HpCap.default and \ + self.hp_cap == HpCap.default: + self.hp_cap.value = self.HpCap.value + self.has_replaced_options.value = Toggle.option_true + if self.LevelCap != LevelCap.default and \ + self.level_cap == LevelCap.default: + self.level_cap.value = self.LevelCap.value + self.has_replaced_options.value = Toggle.option_true + if self.ExtraEarringsXP != ExtraEarringsXP.default and \ + self.extra_earrings_xp == ExtraEarringsXP.default: + self.extra_earrings_xp.value = self.ExtraEarringsXP.value + self.has_replaced_options.value = Toggle.option_true + if self.BossHealing != BossHealing.default and \ + self.boss_healing == BossHealing.default: + self.boss_healing.value = self.BossHealing.value + self.has_replaced_options.value = Toggle.option_true + if self.ShopFill != ShopFill.default and \ + self.shop_fill == ShopFill.default: + self.shop_fill.value = self.ShopFill.value + self.has_replaced_options.value = Toggle.option_true + if self.ShopWarpShards != ShopWarpShards.default and \ + self.shop_warp_shards == ShopWarpShards.default: + self.shop_warp_shards.value = self.ShopWarpShards.value + self.has_replaced_options.value = Toggle.option_true + if self.ShopMultiplier != ShopMultiplier.default and \ + self.shop_multiplier == ShopMultiplier.default: + self.shop_multiplier.value = self.ShopMultiplier.value + self.has_replaced_options.value = Toggle.option_true + if self.LootPool != LootPool.default and \ + self.loot_pool == LootPool.default: + self.loot_pool.value = self.LootPool.value + self.has_replaced_options.value = Toggle.option_true + if self.DropRateCategory != DropRateCategory.default and \ + self.drop_rate_category == DropRateCategory.default: + self.drop_rate_category.value = self.DropRateCategory.value + self.has_replaced_options.value = Toggle.option_true + if self.FixedDropRate != FixedDropRate.default and \ + self.fixed_drop_rate == FixedDropRate.default: + self.fixed_drop_rate.value = self.FixedDropRate.value + self.has_replaced_options.value = Toggle.option_true + if self.LootTierDistro != LootTierDistro.default and \ + self.loot_tier_distro == LootTierDistro.default: + self.loot_tier_distro.value = self.LootTierDistro.value + self.has_replaced_options.value = Toggle.option_true + if self.ShowBestiary != ShowBestiary.default and \ + self.show_bestiary == ShowBestiary.default: + self.show_bestiary.value = self.ShowBestiary.value + self.has_replaced_options.value = Toggle.option_true + if self.ShowDrops != ShowDrops.default and \ + self.show_drops == ShowDrops.default: + self.show_drops.value = self.ShowDrops.value + self.has_replaced_options.value = Toggle.option_true + if self.EnterSandman != EnterSandman.default and \ + self.enter_sandman == EnterSandman.default: + self.enter_sandman.value = self.EnterSandman.value + self.has_replaced_options.value = Toggle.option_true + if self.DadPercent != DadPercent.default and \ + self.dad_percent == DadPercent.default: + self.dad_percent.value = self.DadPercent.value + self.has_replaced_options.value = Toggle.option_true + if self.RisingTides != RisingTides.default and \ + self.rising_tides == RisingTides.default: + self.rising_tides.value = self.RisingTides.value + self.has_replaced_options.value = Toggle.option_true + if self.RisingTidesOverrides != RisingTidesOverrides.default and \ + self.rising_tides_overrides == RisingTidesOverrides.default: + self.rising_tides_overrides.value = self.RisingTidesOverrides.value + self.has_replaced_options.value = Toggle.option_true + if self.UnchainedKeys != UnchainedKeys.default and \ + self.unchained_keys == UnchainedKeys.default: + self.unchained_keys.value = self.UnchainedKeys.value + self.has_replaced_options.value = Toggle.option_true + if self.PresentAccessWithWheelAndSpindle != PresentAccessWithWheelAndSpindle.default and \ + self.back_to_the_future == PresentAccessWithWheelAndSpindle.default: + self.back_to_the_future.value = self.PresentAccessWithWheelAndSpindle.value + self.has_replaced_options.value = Toggle.option_true + if self.TrapChance != TrapChance.default and \ + self.trap_chance == TrapChance.default: + self.trap_chance.value = self.TrapChance.value + self.has_replaced_options.value = Toggle.option_true + if self.Traps != Traps.default and \ + self.traps == Traps.default: + self.traps.value = self.Traps.value + self.has_replaced_options.value = Toggle.option_true + if self.DeathLink != DeathLink.default and \ + self.death_link == DeathLink.default: + self.death_link.value = self.DeathLink.value + self.has_replaced_options.value = Toggle.option_true diff --git a/worlds/timespinner/PreCalculatedWeights.py b/worlds/timespinner/PreCalculatedWeights.py index ff7f031d3b6..c9d80d7a709 100644 --- a/worlds/timespinner/PreCalculatedWeights.py +++ b/worlds/timespinner/PreCalculatedWeights.py @@ -1,6 +1,6 @@ from typing import Tuple, Dict, Union, List -from BaseClasses import MultiWorld -from .Options import timespinner_options, is_option_enabled, get_option_value +from random import Random +from .Options import TimespinnerOptions class PreCalculatedWeights: pyramid_keys_unlock: str @@ -21,22 +21,22 @@ class PreCalculatedWeights: flood_lake_serene_bridge: bool flood_lab: bool - def __init__(self, world: MultiWorld, player: int): - if world and is_option_enabled(world, player, "RisingTides"): - weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(world, player) + def __init__(self, options: TimespinnerOptions, random: Random): + if options.rising_tides: + weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(options) self.flood_basement, self.flood_basement_high = \ - self.roll_flood_setting(world, player, weights_overrrides, "CastleBasement") - self.flood_xarion, _ = self.roll_flood_setting(world, player, weights_overrrides, "Xarion") - self.flood_maw, _ = self.roll_flood_setting(world, player, weights_overrrides, "Maw") - self.flood_pyramid_shaft, _ = self.roll_flood_setting(world, player, weights_overrrides, "AncientPyramidShaft") - self.flood_pyramid_back, _ = self.roll_flood_setting(world, player, weights_overrrides, "Sandman") - self.flood_moat, _ = self.roll_flood_setting(world, player, weights_overrrides, "CastleMoat") - self.flood_courtyard, _ = self.roll_flood_setting(world, player, weights_overrrides, "CastleCourtyard") - self.flood_lake_desolation, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeDesolation") - self.flood_lake_serene, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSerene") - self.flood_lake_serene_bridge, _ = self.roll_flood_setting(world, player, weights_overrrides, "LakeSereneBridge") - self.flood_lab, _ = self.roll_flood_setting(world, player, weights_overrrides, "Lab") + self.roll_flood_setting(random, weights_overrrides, "CastleBasement") + self.flood_xarion, _ = self.roll_flood_setting(random, weights_overrrides, "Xarion") + self.flood_maw, _ = self.roll_flood_setting(random, weights_overrrides, "Maw") + self.flood_pyramid_shaft, _ = self.roll_flood_setting(random, weights_overrrides, "AncientPyramidShaft") + self.flood_pyramid_back, _ = self.roll_flood_setting(random, weights_overrrides, "Sandman") + self.flood_moat, _ = self.roll_flood_setting(random, weights_overrrides, "CastleMoat") + self.flood_courtyard, _ = self.roll_flood_setting(random, weights_overrrides, "CastleCourtyard") + self.flood_lake_desolation, _ = self.roll_flood_setting(random, weights_overrrides, "LakeDesolation") + self.flood_lake_serene, _ = self.roll_flood_setting(random, weights_overrrides, "LakeSerene") + self.flood_lake_serene_bridge, _ = self.roll_flood_setting(random, weights_overrrides, "LakeSereneBridge") + self.flood_lab, _ = self.roll_flood_setting(random, weights_overrrides, "Lab") else: self.flood_basement = False self.flood_basement_high = False @@ -52,10 +52,12 @@ def __init__(self, world: MultiWorld, player: int): self.flood_lab = False self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \ - self.get_pyramid_keys_unlocks(world, player, self.flood_maw, self.flood_xarion) + self.get_pyramid_keys_unlocks(options, random, self.flood_maw, self.flood_xarion) @staticmethod - def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: bool, is_xarion_flooded: bool) -> Tuple[str, str, str, str]: + def get_pyramid_keys_unlocks(options: TimespinnerOptions, random: Random, + is_maw_flooded: bool, is_xarion_flooded: bool) -> Tuple[str, str, str, str]: + present_teleportation_gates: List[str] = [ "GateKittyBoss", "GateLeftLibrary", @@ -80,38 +82,30 @@ def get_pyramid_keys_unlocks(world: MultiWorld, player: int, is_maw_flooded: boo "GateRightPyramid" ) - if not world: - return ( - present_teleportation_gates[0], - present_teleportation_gates[0], - past_teleportation_gates[0], - ancient_pyramid_teleportation_gates[0] - ) - if not is_maw_flooded: past_teleportation_gates.append("GateMaw") if not is_xarion_flooded: present_teleportation_gates.append("GateXarion") - if is_option_enabled(world, player, "Inverted"): + if options.inverted: all_gates: Tuple[str, ...] = present_teleportation_gates else: all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates return ( - world.random.choice(all_gates), - world.random.choice(present_teleportation_gates), - world.random.choice(past_teleportation_gates), - world.random.choice(ancient_pyramid_teleportation_gates) + random.choice(all_gates), + random.choice(present_teleportation_gates), + random.choice(past_teleportation_gates), + random.choice(ancient_pyramid_teleportation_gates) ) @staticmethod - def get_flood_weights_overrides(world: MultiWorld, player: int) -> Dict[str, Union[str, Dict[str, int]]]: + def get_flood_weights_overrides(options: TimespinnerOptions) -> Dict[str, Union[str, Dict[str, int]]]: weights_overrides_option: Union[int, Dict[str, Union[str, Dict[str, int]]]] = \ - get_option_value(world, player, "RisingTidesOverrides") + options.rising_tides_overrides.value - default_weights: Dict[str, Dict[str, int]] = timespinner_options["RisingTidesOverrides"].default + default_weights: Dict[str, Dict[str, int]] = options.rising_tides_overrides.default if not weights_overrides_option: weights_overrides_option = default_weights @@ -123,13 +117,13 @@ def get_flood_weights_overrides(world: MultiWorld, player: int) -> Dict[str, Uni return weights_overrides_option @staticmethod - def roll_flood_setting(world: MultiWorld, player: int, - all_weights: Dict[str, Union[Dict[str, int], str]], key: str) -> Tuple[bool, bool]: + def roll_flood_setting(random: Random, all_weights: Dict[str, Union[Dict[str, int], str]], + key: str) -> Tuple[bool, bool]: weights: Union[Dict[str, int], str] = all_weights[key] if isinstance(weights, dict): - result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0] + result: str = random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0] else: result: str = weights diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index 757a41c3882..f737b461d0b 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -1,14 +1,16 @@ from typing import List, Set, Dict, Optional, Callable from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location -from .Options import is_option_enabled +from .Options import TimespinnerOptions from .Locations import LocationData, get_location_datas from .PreCalculatedWeights import PreCalculatedWeights from .LogicExtensions import TimespinnerLogic -def create_regions_and_locations(world: MultiWorld, player: int, precalculated_weights: PreCalculatedWeights): +def create_regions_and_locations(world: MultiWorld, player: int, options: TimespinnerOptions, + precalculated_weights: PreCalculatedWeights): + locations_per_region: Dict[str, List[LocationData]] = split_location_datas_per_region( - get_location_datas(world, player, precalculated_weights)) + get_location_datas(player, options, precalculated_weights)) regions = [ create_region(world, player, locations_per_region, 'Menu'), @@ -53,7 +55,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w create_region(world, player, locations_per_region, 'Space time continuum') ] - if is_option_enabled(world, player, "GyreArchives"): + if options.gyre_archives: regions.extend([ create_region(world, player, locations_per_region, 'Ravenlord\'s Lair'), create_region(world, player, locations_per_region, 'Ifrit\'s Lair'), @@ -64,10 +66,10 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w world.regions += regions - connectStartingRegion(world, player) + connectStartingRegion(world, player, options) flooded: PreCalculatedWeights = precalculated_weights - logic = TimespinnerLogic(world, player, precalculated_weights) + logic = TimespinnerLogic(player, options, precalculated_weights) connect(world, player, 'Lake desolation', 'Lower lake desolation', lambda state: flooded.flood_lake_desolation or logic.has_timestop(state) or state.has('Talaria Attachment', player)) connect(world, player, 'Lake desolation', 'Upper lake desolation', lambda state: logic.has_fire(state) and state.can_reach('Upper Lake Serene', 'Region', player), "Upper Lake Serene") @@ -123,7 +125,7 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w connect(world, player, 'Sealed Caves (Xarion)', 'Skeleton Shaft') connect(world, player, 'Sealed Caves (Xarion)', 'Space time continuum', logic.has_teleport) connect(world, player, 'Refugee Camp', 'Forest') - connect(world, player, 'Refugee Camp', 'Library', lambda state: is_option_enabled(world, player, "Inverted") and is_option_enabled(world, player, "PresentAccessWithWheelAndSpindle") and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) + connect(world, player, 'Refugee Camp', 'Library', lambda state: options.inverted and options.back_to_the_future and state.has_all({'Timespinner Wheel', 'Timespinner Spindle'}, player)) connect(world, player, 'Refugee Camp', 'Space time continuum', logic.has_teleport) connect(world, player, 'Forest', 'Refugee Camp') connect(world, player, 'Forest', 'Left Side forest Caves', lambda state: flooded.flood_lake_serene_bridge or state.has('Talaria Attachment', player) or logic.has_timestop(state)) @@ -178,11 +180,11 @@ def create_regions_and_locations(world: MultiWorld, player: int, precalculated_w connect(world, player, 'Space time continuum', 'Royal towers (lower)', lambda state: logic.can_teleport_to(state, "Past", "GateRoyalTowers")) connect(world, player, 'Space time continuum', 'Caves of Banishment (Maw)', lambda state: logic.can_teleport_to(state, "Past", "GateMaw")) connect(world, player, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: logic.can_teleport_to(state, "Past", "GateCavesOfBanishment")) - connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not is_option_enabled(world, player, "UnchainedKeys") and is_option_enabled(world, player, "EnterSandman"))) + connect(world, player, 'Space time continuum', 'Ancient Pyramid (entrance)', lambda state: logic.can_teleport_to(state, "Time", "GateGyre") or (not options.unchained_keys and options.enter_sandman)) connect(world, player, 'Space time continuum', 'Ancient Pyramid (left)', lambda state: logic.can_teleport_to(state, "Time", "GateLeftPyramid")) connect(world, player, 'Space time continuum', 'Ancient Pyramid (right)', lambda state: logic.can_teleport_to(state, "Time", "GateRightPyramid")) - if is_option_enabled(world, player, "GyreArchives"): + if options.gyre_archives: connect(world, player, 'The lab (upper)', 'Ravenlord\'s Lair', lambda state: state.has('Merchant Crow', player)) connect(world, player, 'Ravenlord\'s Lair', 'The lab (upper)') connect(world, player, 'Library top', 'Ifrit\'s Lair', lambda state: state.has('Kobo', player) and state.can_reach('Refugee Camp', 'Region', player), "Refugee Camp") @@ -220,12 +222,12 @@ def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str return region -def connectStartingRegion(world: MultiWorld, player: int): +def connectStartingRegion(world: MultiWorld, player: int, options: TimespinnerOptions): menu = world.get_region('Menu', player) tutorial = world.get_region('Tutorial', player) space_time_continuum = world.get_region('Space time continuum', player) - if is_option_enabled(world, player, "Inverted"): + if options.inverted: starting_region = world.get_region('Refugee Camp', player) else: starting_region = world.get_region('Lake desolation', player) diff --git a/worlds/timespinner/__init__.py b/worlds/timespinner/__init__.py index cab6fb648b9..66744cffdf8 100644 --- a/worlds/timespinner/__init__.py +++ b/worlds/timespinner/__init__.py @@ -1,12 +1,13 @@ -from typing import Dict, List, Set, Tuple, TextIO, Union -from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from typing import Dict, List, Set, Tuple, TextIO +from BaseClasses import Item, Tutorial, ItemClassification from .Items import get_item_names_per_category from .Items import item_table, starter_melee_weapons, starter_spells, filler_items, starter_progression_items from .Locations import get_location_datas, EventId -from .Options import is_option_enabled, get_option_value, timespinner_options +from .Options import BackwardsCompatiableTimespinnerOptions, Toggle from .PreCalculatedWeights import PreCalculatedWeights from .Regions import create_regions_and_locations from worlds.AutoWorld import World, WebWorld +import logging class TimespinnerWebWorld(WebWorld): theme = "ice" @@ -35,32 +36,34 @@ class TimespinnerWorld(World): Timespinner is a beautiful metroidvania inspired by classic 90s action-platformers. Travel back in time to change fate itself. Join timekeeper Lunais on her quest for revenge against the empire that killed her family. """ - - option_definitions = timespinner_options + options_dataclass = BackwardsCompatiableTimespinnerOptions + options: BackwardsCompatiableTimespinnerOptions game = "Timespinner" topology_present = True web = TimespinnerWebWorld() required_client_version = (0, 4, 2) item_name_to_id = {name: data.code for name, data in item_table.items()} - location_name_to_id = {location.name: location.code for location in get_location_datas(None, None, None)} + location_name_to_id = {location.name: location.code for location in get_location_datas(-1, None, None)} item_name_groups = get_item_names_per_category() precalculated_weights: PreCalculatedWeights def generate_early(self) -> None: - self.precalculated_weights = PreCalculatedWeights(self.multiworld, self.player) + self.options.handle_backward_compatibility() + + self.precalculated_weights = PreCalculatedWeights(self.options, self.random) # in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly - if self.multiworld.start_inventory[self.player].value.pop('Meyef', 0) > 0: - self.multiworld.StartWithMeyef[self.player].value = self.multiworld.StartWithMeyef[self.player].option_true - if self.multiworld.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0: - self.multiworld.QuickSeed[self.player].value = self.multiworld.QuickSeed[self.player].option_true - if self.multiworld.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0: - self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true + if self.options.start_inventory.value.pop('Meyef', 0) > 0: + self.options.start_with_meyef.value = Toggle.option_true + if self.options.start_inventory.value.pop('Talaria Attachment', 0) > 0: + self.options.quick_seed.value = Toggle.option_true + if self.options.start_inventory.value.pop('Jewelry Box', 0) > 0: + self.options.start_with_jewelry_box.value = Toggle.option_true def create_regions(self) -> None: - create_regions_and_locations(self.multiworld, self.player, self.precalculated_weights) + create_regions_and_locations(self.multiworld, self.player, self.options, self.precalculated_weights) def create_items(self) -> None: self.create_and_assign_event_items() @@ -74,7 +77,7 @@ def create_items(self) -> None: def set_rules(self) -> None: final_boss: str - if self.is_option_enabled("DadPercent"): + if self.options.dad_percent: final_boss = "Killed Emperor" else: final_boss = "Killed Nightmare" @@ -82,48 +85,74 @@ def set_rules(self) -> None: self.multiworld.completion_condition[self.player] = lambda state: state.has(final_boss, self.player) def fill_slot_data(self) -> Dict[str, object]: - slot_data: Dict[str, object] = {} - - ap_specific_settings: Set[str] = {"RisingTidesOverrides", "TrapChance"} - - for option_name in timespinner_options: - if (option_name not in ap_specific_settings): - slot_data[option_name] = self.get_option_value(option_name) - - slot_data["StinkyMaw"] = True - slot_data["ProgressiveVerticalMovement"] = False - slot_data["ProgressiveKeycards"] = False - slot_data["PersonalItems"] = self.get_personal_items() - slot_data["PyramidKeysGate"] = self.precalculated_weights.pyramid_keys_unlock - slot_data["PresentGate"] = self.precalculated_weights.present_key_unlock - slot_data["PastGate"] = self.precalculated_weights.past_key_unlock - slot_data["TimeGate"] = self.precalculated_weights.time_key_unlock - slot_data["Basement"] = int(self.precalculated_weights.flood_basement) + \ - int(self.precalculated_weights.flood_basement_high) - slot_data["Xarion"] = self.precalculated_weights.flood_xarion - slot_data["Maw"] = self.precalculated_weights.flood_maw - slot_data["PyramidShaft"] = self.precalculated_weights.flood_pyramid_shaft - slot_data["BackPyramid"] = self.precalculated_weights.flood_pyramid_back - slot_data["CastleMoat"] = self.precalculated_weights.flood_moat - slot_data["CastleCourtyard"] = self.precalculated_weights.flood_courtyard - slot_data["LakeDesolation"] = self.precalculated_weights.flood_lake_desolation - slot_data["DryLakeSerene"] = not self.precalculated_weights.flood_lake_serene - slot_data["LakeSereneBridge"] = self.precalculated_weights.flood_lake_serene_bridge - slot_data["Lab"] = self.precalculated_weights.flood_lab - - return slot_data + return { + # options + "StartWithJewelryBox": self.options.start_with_jewelry_box.value, + "DownloadableItems": self.options.downloadable_items.value, + "EyeSpy": self.options.eye_spy.value, + "StartWithMeyef": self.options.start_with_meyef.value, + "QuickSeed": self.options.quick_seed.value, + "SpecificKeycards": self.options.specific_keycards.value, + "Inverted": self.options.inverted.value, + "GyreArchives": self.options.gyre_archives.value, + "Cantoran": self.options.cantoran.value, + "LoreChecks": self.options.lore_checks.value, + "BossRando": self.options.boss_rando.value, + "DamageRando": self.options.damage_rando.value, + "DamageRandoOverrides": self.options.damage_rando_overrides.value, + "HpCap": self.options.hp_cap.value, + "LevelCap": self.options.level_cap.value, + "ExtraEarringsXP": self.options.extra_earrings_xp.value, + "BossHealing": self.options.boss_healing.value, + "ShopFill": self.options.shop_fill.value, + "ShopWarpShards": self.options.shop_warp_shards.value, + "ShopMultiplier": self.options.shop_multiplier.value, + "LootPool": self.options.loot_pool.value, + "DropRateCategory": self.options.drop_rate_category.value, + "FixedDropRate": self.options.fixed_drop_rate.value, + "LootTierDistro": self.options.loot_tier_distro.value, + "ShowBestiary": self.options.show_bestiary.value, + "ShowDrops": self.options.show_drops.value, + "EnterSandman": self.options.enter_sandman.value, + "DadPercent": self.options.dad_percent.value, + "RisingTides": self.options.rising_tides.value, + "UnchainedKeys": self.options.unchained_keys.value, + "PresentAccessWithWheelAndSpindle": self.options.back_to_the_future.value, + "Traps": self.options.traps.value, + "DeathLink": self.options.death_link.value, + "StinkyMaw": True, + # data + "PersonalItems": self.get_personal_items(), + "PyramidKeysGate": self.precalculated_weights.pyramid_keys_unlock, + "PresentGate": self.precalculated_weights.present_key_unlock, + "PastGate": self.precalculated_weights.past_key_unlock, + "TimeGate": self.precalculated_weights.time_key_unlock, + # rising tides + "Basement": int(self.precalculated_weights.flood_basement) + \ + int(self.precalculated_weights.flood_basement_high), + "Xarion": self.precalculated_weights.flood_xarion, + "Maw": self.precalculated_weights.flood_maw, + "PyramidShaft": self.precalculated_weights.flood_pyramid_shaft, + "BackPyramid": self.precalculated_weights.flood_pyramid_back, + "CastleMoat": self.precalculated_weights.flood_moat, + "CastleCourtyard": self.precalculated_weights.flood_courtyard, + "LakeDesolation": self.precalculated_weights.flood_lake_desolation, + "DryLakeSerene": not self.precalculated_weights.flood_lake_serene, + "LakeSereneBridge": self.precalculated_weights.flood_lake_serene_bridge, + "Lab": self.precalculated_weights.flood_lab + } def write_spoiler_header(self, spoiler_handle: TextIO) -> None: - if self.is_option_enabled("UnchainedKeys"): + if self.options.unchained_keys: spoiler_handle.write(f'Modern Warp Beacon unlock: {self.precalculated_weights.present_key_unlock}\n') spoiler_handle.write(f'Timeworn Warp Beacon unlock: {self.precalculated_weights.past_key_unlock}\n') - if self.is_option_enabled("EnterSandman"): + if self.options.enter_sandman: spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n') else: spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n') - if self.is_option_enabled("RisingTides"): + if self.options.rising_tides: flooded_areas: List[str] = [] if self.precalculated_weights.flood_basement: @@ -159,6 +188,15 @@ def write_spoiler_header(self, spoiler_handle: TextIO) -> None: spoiler_handle.write(f'Flooded Areas: {flooded_areas_string}\n') + if self.options.has_replaced_options: + warning = \ + f"NOTICE: Timespinner options for player '{self.player_name}' where renamed from PasCalCase to snake_case, " \ + "please update your yaml" + + spoiler_handle.write("\n") + spoiler_handle.write(warning) + logging.warning(warning) + def create_item(self, name: str) -> Item: data = item_table[name] @@ -176,41 +214,41 @@ def create_item(self, name: str) -> Item: if not item.advancement: return item - if (name == 'Tablet' or name == 'Library Keycard V') and not self.is_option_enabled("DownloadableItems"): + if (name == 'Tablet' or name == 'Library Keycard V') and not self.options.downloadable_items: item.classification = ItemClassification.filler - elif name == 'Oculus Ring' and not self.is_option_enabled("EyeSpy"): + elif name == 'Oculus Ring' and not self.options.eye_spy: item.classification = ItemClassification.filler - elif (name == 'Kobo' or name == 'Merchant Crow') and not self.is_option_enabled("GyreArchives"): + elif (name == 'Kobo' or name == 'Merchant Crow') and not self.options.gyre_archives: item.classification = ItemClassification.filler elif name in {"Timeworn Warp Beacon", "Modern Warp Beacon", "Mysterious Warp Beacon"} \ - and not self.is_option_enabled("UnchainedKeys"): + and not self.options.unchained_keys: item.classification = ItemClassification.filler return item def get_filler_item_name(self) -> str: - trap_chance: int = self.get_option_value("TrapChance") - enabled_traps: List[str] = self.get_option_value("Traps") + trap_chance: int = self.options.trap_chance.value + enabled_traps: List[str] = self.options.traps.value - if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps: - return self.multiworld.random.choice(enabled_traps) + if self.random.random() < (trap_chance / 100) and enabled_traps: + return self.random.choice(enabled_traps) else: - return self.multiworld.random.choice(filler_items) + return self.random.choice(filler_items) def get_excluded_items(self) -> Set[str]: excluded_items: Set[str] = set() - if self.is_option_enabled("StartWithJewelryBox"): + if self.options.start_with_jewelry_box: excluded_items.add('Jewelry Box') - if self.is_option_enabled("StartWithMeyef"): + if self.options.start_with_meyef: excluded_items.add('Meyef') - if self.is_option_enabled("QuickSeed"): + if self.options.quick_seed: excluded_items.add('Talaria Attachment') - if self.is_option_enabled("UnchainedKeys"): + if self.options.unchained_keys: excluded_items.add('Twin Pyramid Key') - if not self.is_option_enabled("EnterSandman"): + if not self.options.enter_sandman: excluded_items.add('Mysterious Warp Beacon') else: excluded_items.add('Timeworn Warp Beacon') @@ -224,8 +262,8 @@ def get_excluded_items(self) -> Set[str]: return excluded_items def assign_starter_items(self, excluded_items: Set[str]) -> None: - non_local_items: Set[str] = self.multiworld.non_local_items[self.player].value - local_items: Set[str] = self.multiworld.local_items[self.player].value + non_local_items: Set[str] = self.options.non_local_items.value + local_items: Set[str] = self.options.local_items.value local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if item in local_items or not item in non_local_items) @@ -247,27 +285,26 @@ def assign_starter_items(self, excluded_items: Set[str]) -> None: self.assign_starter_item(excluded_items, 'Tutorial: Yo Momma 2', local_starter_spells) def assign_starter_item(self, excluded_items: Set[str], location: str, item_list: Tuple[str, ...]) -> None: - item_name = self.multiworld.random.choice(item_list) + item_name = self.random.choice(item_list) self.place_locked_item(excluded_items, location, item_name) def place_first_progression_item(self, excluded_items: Set[str]) -> None: - if self.is_option_enabled("QuickSeed") or self.is_option_enabled("Inverted") \ - or self.precalculated_weights.flood_lake_desolation: + if self.options.quick_seed or self.options.inverted or self.precalculated_weights.flood_lake_desolation: return - for item in self.multiworld.precollected_items[self.player]: - if item.name in starter_progression_items and not item.name in excluded_items: + for item_name in self.options.start_inventory.value.keys(): + if item_name in starter_progression_items: return local_starter_progression_items = tuple( item for item in starter_progression_items - if item not in excluded_items and item not in self.multiworld.non_local_items[self.player].value) + if item not in excluded_items and item not in self.options.non_local_items.value) if not local_starter_progression_items: return - progression_item = self.multiworld.random.choice(local_starter_progression_items) + progression_item = self.random.choice(local_starter_progression_items) self.multiworld.local_early_items[self.player][progression_item] = 1 @@ -307,9 +344,3 @@ def get_personal_items(self) -> Dict[int, int]: personal_items[location.address] = location.item.code return personal_items - - def is_option_enabled(self, option: str) -> bool: - return is_option_enabled(self.multiworld, self.player, option) - - def get_option_value(self, option: str) -> Union[int, Dict, List]: - return get_option_value(self.multiworld, self.player, option) diff --git a/worlds/tloz/ItemPool.py b/worlds/tloz/ItemPool.py index 5b90e99722d..4acda4ef41f 100644 --- a/worlds/tloz/ItemPool.py +++ b/worlds/tloz/ItemPool.py @@ -80,7 +80,7 @@ def generate_itempool(tlozworld): location.item.classification = ItemClassification.progression def get_pool_core(world): - random = world.multiworld.random + random = world.random pool = [] placed_items = {} @@ -132,14 +132,6 @@ def get_pool_core(world): else: pool.append(fragment) - # Level 9 junk fill - if world.options.ExpandedPool > 0: - spots = random.sample(level_locations[8], len(level_locations[8]) // 2) - for spot in spots: - junk = random.choice(list(minor_items.keys())) - placed_items[spot] = junk - minor_items[junk] -= 1 - # Finish Pool final_pool = basic_pool if world.options.ExpandedPool: diff --git a/worlds/tloz/Locations.py b/worlds/tloz/Locations.py index 5b30357c940..9715cc68429 100644 --- a/worlds/tloz/Locations.py +++ b/worlds/tloz/Locations.py @@ -99,6 +99,14 @@ "Potion Shop Item Left", "Potion Shop Item Middle", "Potion Shop Item Right" ] +take_any_locations = [ + "Take Any Item Left", "Take Any Item Middle", "Take Any Item Right" +] + +sword_cave_locations = [ + "Starting Sword Cave", "White Sword Pond", "Magical Sword Grave" +] + food_locations = [ "Level 7 Map", "Level 7 Boss", "Level 7 Triforce", "Level 7 Key Drop (Goriyas)", "Level 7 Bomb Drop (Moldorms North)", "Level 7 Bomb Drop (Goriyas North)", diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py index a1f9081418e..c8c76bd85a8 100644 --- a/worlds/tloz/__init__.py +++ b/worlds/tloz/__init__.py @@ -12,7 +12,8 @@ from .ItemPool import generate_itempool, starting_weapons, dangerous_weapon_locations from .Items import item_table, item_prices, item_game_ids from .Locations import location_table, level_locations, major_locations, shop_locations, all_level_locations, \ - standard_level_locations, shop_price_location_ids, secret_money_ids, location_ids, food_locations + standard_level_locations, shop_price_location_ids, secret_money_ids, location_ids, food_locations, \ + take_any_locations, sword_cave_locations from .Options import TlozOptions from .Rom import TLoZDeltaPatch, get_base_rom_path, first_quest_dungeon_items_early, first_quest_dungeon_items_late from .Rules import set_rules @@ -87,6 +88,21 @@ class TLoZWorld(World): } } + location_name_groups = { + "Shops": set(shop_locations), + "Take Any": set(take_any_locations), + "Sword Caves": set(sword_cave_locations), + "Level 1": set(level_locations[0]), + "Level 2": set(level_locations[1]), + "Level 3": set(level_locations[2]), + "Level 4": set(level_locations[3]), + "Level 5": set(level_locations[4]), + "Level 6": set(level_locations[5]), + "Level 7": set(level_locations[6]), + "Level 8": set(level_locations[7]), + "Level 9": set(level_locations[8]) + } + for k, v in item_name_to_id.items(): item_name_to_id[k] = v + base_id @@ -94,8 +110,8 @@ class TLoZWorld(World): if v is not None: location_name_to_id[k] = v + base_id - def __init__(self, world: MultiWorld, player: int): - super().__init__(world, player) + def __init__(self, multiworld: MultiWorld, player: int): + super().__init__(multiworld, player) self.generator_in_use = threading.Event() self.rom_name_available_event = threading.Event() self.levels = None @@ -307,7 +323,7 @@ def modify_multidata(self, multidata: dict): def get_filler_item_name(self) -> str: if self.filler_items is None: self.filler_items = [item for item in item_table if item_table[item].classification == ItemClassification.filler] - return self.multiworld.random.choice(self.filler_items) + return self.random.choice(self.filler_items) def fill_slot_data(self) -> Dict[str, Any]: if self.options.ExpandedPool: diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index f63193e6aee..5253e995143 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Any, Tuple, TypedDict +from typing import Dict, List, Any, Tuple, TypedDict, ClassVar, Union from logging import warning from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld from .items import item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names @@ -12,6 +12,14 @@ from worlds.AutoWorld import WebWorld, World from Options import PlandoConnection from decimal import Decimal, ROUND_HALF_UP +from settings import Group, Bool + + +class TunicSettings(Group): + class DisableLocalSpoiler(Bool): + """Disallows the TUNIC client from creating a local spoiler log.""" + + disable_local_spoiler: Union[DisableLocalSpoiler, bool] = False class TunicWeb(WebWorld): @@ -57,6 +65,7 @@ class TunicWorld(World): options: TunicOptions options_dataclass = TunicOptions + settings: ClassVar[TunicSettings] item_name_groups = item_name_groups location_name_groups = location_name_groups @@ -112,7 +121,7 @@ def stage_generate_early(cls, multiworld: MultiWorld) -> None: cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value, laurels_at_10_fairies=tunic.options.laurels_location == 3, fixed_shop=bool(tunic.options.fixed_shop), - plando=multiworld.plando_connections[tunic.player]) + plando=tunic.options.plando_connections) continue # lower value is more restrictive @@ -125,9 +134,9 @@ def stage_generate_early(cls, multiworld: MultiWorld) -> None: if tunic.options.fixed_shop: cls.seed_groups[group]["fixed_shop"] = True - if multiworld.plando_connections[tunic.player]: + if tunic.options.plando_connections: # loop through the connections in the player's yaml - for cxn in multiworld.plando_connections[tunic.player]: + for cxn in tunic.options.plando_connections: new_cxn = True for group_cxn in cls.seed_groups[group]["plando"]: # if neither entrance nor exit match anything in the group, add to group @@ -151,9 +160,9 @@ def stage_generate_early(cls, multiworld: MultiWorld) -> None: if new_cxn: cls.seed_groups[group]["plando"].value.append(cxn) - def create_item(self, name: str) -> TunicItem: + def create_item(self, name: str, classification: ItemClassification = None) -> TunicItem: item_data = item_table[name] - return TunicItem(name, item_data.classification, self.item_name_to_id[name], self.player) + return TunicItem(name, classification or item_data.classification, self.item_name_to_id[name], self.player) def create_items(self) -> None: @@ -183,14 +192,12 @@ def create_items(self) -> None: self.multiworld.get_location("Coins in the Well - 10 Coins", self.player).place_locked_item(laurels) elif self.options.laurels_location == "10_fairies": self.multiworld.get_location("Secret Gathering Place - 10 Fairy Reward", self.player).place_locked_item(laurels) - self.slot_data_items.append(laurels) items_to_create["Hero's Laurels"] = 0 if self.options.keys_behind_bosses: for rgb_hexagon, location in hexagon_locations.items(): hex_item = self.create_item(gold_hexagon if self.options.hexagon_quest else rgb_hexagon) self.multiworld.get_location(location, self.player).place_locked_item(hex_item) - self.slot_data_items.append(hex_item) items_to_create[rgb_hexagon] = 0 items_to_create[gold_hexagon] -= 3 @@ -236,33 +243,30 @@ def remove_filler(amount: int) -> None: remove_filler(items_to_create[gold_hexagon]) for hero_relic in item_name_groups["Hero Relics"]: - relic_item = TunicItem(hero_relic, ItemClassification.useful, self.item_name_to_id[hero_relic], self.player) - tunic_items.append(relic_item) + tunic_items.append(self.create_item(hero_relic, ItemClassification.useful)) items_to_create[hero_relic] = 0 if not self.options.ability_shuffling: for page in item_name_groups["Abilities"]: if items_to_create[page] > 0: - page_item = TunicItem(page, ItemClassification.useful, self.item_name_to_id[page], self.player) - tunic_items.append(page_item) + tunic_items.append(self.create_item(page, ItemClassification.useful)) items_to_create[page] = 0 if self.options.maskless: - mask_item = TunicItem("Scavenger Mask", ItemClassification.useful, self.item_name_to_id["Scavenger Mask"], self.player) - tunic_items.append(mask_item) + tunic_items.append(self.create_item("Scavenger Mask", ItemClassification.useful)) items_to_create["Scavenger Mask"] = 0 if self.options.lanternless: - lantern_item = TunicItem("Lantern", ItemClassification.useful, self.item_name_to_id["Lantern"], self.player) - tunic_items.append(lantern_item) + tunic_items.append(self.create_item("Lantern", ItemClassification.useful)) items_to_create["Lantern"] = 0 for item, quantity in items_to_create.items(): for _ in range(quantity): - tunic_item: TunicItem = self.create_item(item) - if item in slot_data_item_names: - self.slot_data_items.append(tunic_item) - tunic_items.append(tunic_item) + tunic_items.append(self.create_item(item)) + + for tunic_item in tunic_items: + if tunic_item.name in slot_data_item_names: + self.slot_data_items.append(tunic_item) self.multiworld.itempool += tunic_items @@ -373,7 +377,8 @@ def fill_slot_data(self) -> Dict[str, Any]: "Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"], "Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"], "Hexagon Quest Goal": self.options.hexagon_goal.value, - "Entrance Rando": self.tunic_portal_pairs + "Entrance Rando": self.tunic_portal_pairs, + "disable_local_spoiler": int(self.settings.disable_local_spoiler or self.multiworld.is_race), } for tunic_item in filter(lambda item: item.location is not None and item.code is not None, self.slot_data_items): diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index f49e7dff3e5..e999026dec7 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -169,100 +169,16 @@ def destination_scene(self) -> str: # the vanilla connection destination="Overworld Redux", tag="_rafters"), Portal(name="Temple Door Exit", region="Sealed Temple", destination="Overworld Redux", tag="_main"), - - Portal(name="Well Ladder Exit", region="Beneath the Well Ladder Exit", - destination="Overworld Redux", tag="_entrance"), - Portal(name="Well to Well Boss", region="Beneath the Well Back", - destination="Sewer_Boss", tag="_"), - Portal(name="Well Exit towards Furnace", region="Beneath the Well Back", - destination="Overworld Redux", tag="_west_aqueduct"), - - Portal(name="Well Boss to Well", region="Well Boss", - destination="Sewer", tag="_"), - Portal(name="Checkpoint to Dark Tomb", region="Dark Tomb Checkpoint", - destination="Crypt Redux", tag="_"), - - Portal(name="Dark Tomb to Overworld", region="Dark Tomb Entry Point", + + Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main", + destination="Fortress Courtyard", tag="_"), + Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower", + destination="East Forest Redux", tag="_"), + Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main", destination="Overworld Redux", tag="_"), - Portal(name="Dark Tomb to Furnace", region="Dark Tomb Dark Exit", - destination="Furnace", tag="_"), - Portal(name="Dark Tomb to Checkpoint", region="Dark Tomb Entry Point", - destination="Sewer_Boss", tag="_"), - - Portal(name="West Garden Exit near Hero's Grave", region="West Garden", - destination="Overworld Redux", tag="_lower"), - Portal(name="West Garden to Magic Dagger House", region="West Garden", - destination="archipelagos_house", tag="_"), - Portal(name="West Garden Exit after Boss", region="West Garden after Boss", - destination="Overworld Redux", tag="_upper"), - Portal(name="West Garden Shop", region="West Garden", - destination="Shop", tag="_"), - Portal(name="West Garden Laurels Exit", region="West Garden Laurels Exit Region", - destination="Overworld Redux", tag="_lowest"), - Portal(name="West Garden Hero's Grave", region="West Garden Hero's Grave Region", - destination="RelicVoid", tag="_teleporter_relic plinth"), - Portal(name="West Garden to Far Shore", region="West Garden Portal", - destination="Transit", tag="_teleporter_archipelagos_teleporter"), - - Portal(name="Magic Dagger House Exit", region="Magic Dagger House", - destination="Archipelagos Redux", tag="_"), - - Portal(name="Atoll Upper Exit", region="Ruined Atoll", - destination="Overworld Redux", tag="_upper"), - Portal(name="Atoll Lower Exit", region="Ruined Atoll Lower Entry Area", - destination="Overworld Redux", tag="_lower"), - Portal(name="Atoll Shop", region="Ruined Atoll", - destination="Shop", tag="_"), - Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", - destination="Transit", tag="_teleporter_atoll"), - Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", - destination="Library Exterior", tag="_"), - Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll Frog Eye", - destination="Frog Stairs", tag="_eye"), - Portal(name="Frog Stairs Mouth Entrance", region="Ruined Atoll Frog Mouth", - destination="Frog Stairs", tag="_mouth"), - - Portal(name="Frog Stairs Eye Exit", region="Frog Stairs Eye Exit", - destination="Atoll Redux", tag="_eye"), - Portal(name="Frog Stairs Mouth Exit", region="Frog Stairs Upper", - destination="Atoll Redux", tag="_mouth"), - Portal(name="Frog Stairs to Frog's Domain's Entrance", region="Frog Stairs to Frog's Domain", - destination="frog cave main", tag="_Entrance"), - Portal(name="Frog Stairs to Frog's Domain's Exit", region="Frog Stairs Lower", - destination="frog cave main", tag="_Exit"), - - Portal(name="Frog's Domain Ladder Exit", region="Frog's Domain Entry", - destination="Frog Stairs", tag="_Entrance"), - Portal(name="Frog's Domain Orb Exit", region="Frog's Domain Back", - destination="Frog Stairs", tag="_Exit"), - - Portal(name="Library Exterior Tree", region="Library Exterior Tree Region", - destination="Atoll Redux", tag="_"), - Portal(name="Library Exterior Ladder", region="Library Exterior Ladder Region", - destination="Library Hall", tag="_"), - - Portal(name="Library Hall Bookshelf Exit", region="Library Hall Bookshelf", - destination="Library Exterior", tag="_"), - Portal(name="Library Hero's Grave", region="Library Hero's Grave Region", - destination="RelicVoid", tag="_teleporter_relic plinth"), - Portal(name="Library Hall to Rotunda", region="Library Hall to Rotunda", - destination="Library Rotunda", tag="_"), - - Portal(name="Library Rotunda Lower Exit", region="Library Rotunda to Hall", - destination="Library Hall", tag="_"), - Portal(name="Library Rotunda Upper Exit", region="Library Rotunda to Lab", - destination="Library Lab", tag="_"), - - Portal(name="Library Lab to Rotunda", region="Library Lab Lower", - destination="Library Rotunda", tag="_"), - Portal(name="Library to Far Shore", region="Library Portal", - destination="Transit", tag="_teleporter_library teleporter"), - Portal(name="Library Lab to Librarian Arena", region="Library Lab to Librarian", - destination="Library Arena", tag="_"), - - Portal(name="Librarian Arena Exit", region="Library Arena", - destination="Library Lab", tag="_"), - + Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper", + destination="Forest Boss Room", tag="_"), + Portal(name="Forest to Belltower", region="East Forest", destination="Forest Belltower", tag="_"), Portal(name="Forest Guard House 1 Lower Entrance", region="East Forest", @@ -281,7 +197,14 @@ def destination_scene(self) -> str: # the vanilla connection destination="Sword Access", tag="_lower"), Portal(name="Forest Grave Path Upper Entrance", region="East Forest", destination="Sword Access", tag="_upper"), - + + Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper", + destination="East Forest Redux", tag="_upper"), + Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main", + destination="East Forest Redux", tag="_lower"), + Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="Guard House 1 Dance Fox Exit", region="Guard House 1 West", destination="East Forest Redux", tag="_upper"), Portal(name="Guard House 1 Lower Exit", region="Guard House 1 West", @@ -290,33 +213,54 @@ def destination_scene(self) -> str: # the vanilla connection destination="East Forest Redux", tag="_gate"), Portal(name="Guard House 1 to Guard Captain Room", region="Guard House 1 East", destination="Forest Boss Room", tag="_"), - - Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper", - destination="East Forest Redux", tag="_upper"), - Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main", - destination="East Forest Redux", tag="_lower"), - Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave", - destination="RelicVoid", tag="_teleporter_relic plinth"), - + Portal(name="Guard House 2 Lower Exit", region="Guard House 2 Lower", destination="East Forest Redux", tag="_lower"), Portal(name="Guard House 2 Upper Exit", region="Guard House 2 Upper", destination="East Forest Redux", tag="_upper"), - + Portal(name="Guard Captain Room Non-Gate Exit", region="Forest Boss Room", destination="East Forest Redux Laddercave", tag="_"), Portal(name="Guard Captain Room Gate Exit", region="Forest Boss Room", destination="Forest Belltower", tag="_"), + + Portal(name="Well Ladder Exit", region="Beneath the Well Ladder Exit", + destination="Overworld Redux", tag="_entrance"), + Portal(name="Well to Well Boss", region="Beneath the Well Back", + destination="Sewer_Boss", tag="_"), + Portal(name="Well Exit towards Furnace", region="Beneath the Well Back", + destination="Overworld Redux", tag="_west_aqueduct"), - Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main", - destination="Fortress Courtyard", tag="_"), - Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower", - destination="East Forest Redux", tag="_"), - Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main", + Portal(name="Well Boss to Well", region="Well Boss", + destination="Sewer", tag="_"), + Portal(name="Checkpoint to Dark Tomb", region="Dark Tomb Checkpoint", + destination="Crypt Redux", tag="_"), + + Portal(name="Dark Tomb to Overworld", region="Dark Tomb Entry Point", destination="Overworld Redux", tag="_"), - Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper", - destination="Forest Boss Room", tag="_"), + Portal(name="Dark Tomb to Furnace", region="Dark Tomb Dark Exit", + destination="Furnace", tag="_"), + Portal(name="Dark Tomb to Checkpoint", region="Dark Tomb Entry Point", + destination="Sewer_Boss", tag="_"), + Portal(name="West Garden Exit near Hero's Grave", region="West Garden", + destination="Overworld Redux", tag="_lower"), + Portal(name="West Garden to Magic Dagger House", region="West Garden", + destination="archipelagos_house", tag="_"), + Portal(name="West Garden Exit after Boss", region="West Garden after Boss", + destination="Overworld Redux", tag="_upper"), + Portal(name="West Garden Shop", region="West Garden", + destination="Shop", tag="_"), + Portal(name="West Garden Laurels Exit", region="West Garden Laurels Exit Region", + destination="Overworld Redux", tag="_lowest"), + Portal(name="West Garden Hero's Grave", region="West Garden Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="West Garden to Far Shore", region="West Garden Portal", + destination="Transit", tag="_teleporter_archipelagos_teleporter"), + + Portal(name="Magic Dagger House Exit", region="Magic Dagger House", + destination="Archipelagos Redux", tag="_"), + Portal(name="Fortress Courtyard to Fortress Grave Path Lower", region="Fortress Courtyard", destination="Fortress Reliquary", tag="_Lower"), Portal(name="Fortress Courtyard to Fortress Grave Path Upper", region="Fortress Courtyard Upper", @@ -333,12 +277,12 @@ def destination_scene(self) -> str: # the vanilla connection destination="Overworld Redux", tag="_"), Portal(name="Fortress Courtyard Shop", region="Fortress Exterior near cave", destination="Shop", tag="_"), - + Portal(name="Beneath the Vault to Fortress Interior", region="Beneath the Vault Back", destination="Fortress Main", tag="_"), Portal(name="Beneath the Vault to Fortress Courtyard", region="Beneath the Vault Ladder Exit", destination="Fortress Courtyard", tag="_"), - + Portal(name="Fortress Interior Main Exit", region="Eastern Vault Fortress", destination="Fortress Courtyard", tag="_Big Door"), Portal(name="Fortress Interior to Beneath the Earth", region="Eastern Vault Fortress", @@ -351,14 +295,14 @@ def destination_scene(self) -> str: # the vanilla connection destination="Fortress East", tag="_upper"), Portal(name="Fortress Interior to East Fortress Lower", region="Eastern Vault Fortress", destination="Fortress East", tag="_lower"), - + Portal(name="East Fortress to Interior Lower", region="Fortress East Shortcut Lower", destination="Fortress Main", tag="_lower"), Portal(name="East Fortress to Courtyard", region="Fortress East Shortcut Upper", destination="Fortress Courtyard", tag="_"), Portal(name="East Fortress to Interior Upper", region="Fortress East Shortcut Upper", destination="Fortress Main", tag="_upper"), - + Portal(name="Fortress Grave Path Lower Exit", region="Fortress Grave Path", destination="Fortress Courtyard", tag="_Lower"), Portal(name="Fortress Hero's Grave", region="Fortress Hero's Grave Region", @@ -370,11 +314,67 @@ def destination_scene(self) -> str: # the vanilla connection Portal(name="Dusty Exit", region="Fortress Leaf Piles", destination="Fortress Reliquary", tag="_"), - + Portal(name="Siege Engine Arena to Fortress", region="Fortress Arena", destination="Fortress Main", tag="_"), Portal(name="Fortress to Far Shore", region="Fortress Arena Portal", destination="Transit", tag="_teleporter_spidertank"), + + Portal(name="Atoll Upper Exit", region="Ruined Atoll", + destination="Overworld Redux", tag="_upper"), + Portal(name="Atoll Lower Exit", region="Ruined Atoll Lower Entry Area", + destination="Overworld Redux", tag="_lower"), + Portal(name="Atoll Shop", region="Ruined Atoll", + destination="Shop", tag="_"), + Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", + destination="Transit", tag="_teleporter_atoll"), + Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", + destination="Library Exterior", tag="_"), + Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll Frog Eye", + destination="Frog Stairs", tag="_eye"), + Portal(name="Frog Stairs Mouth Entrance", region="Ruined Atoll Frog Mouth", + destination="Frog Stairs", tag="_mouth"), + + Portal(name="Frog Stairs Eye Exit", region="Frog Stairs Eye Exit", + destination="Atoll Redux", tag="_eye"), + Portal(name="Frog Stairs Mouth Exit", region="Frog Stairs Upper", + destination="Atoll Redux", tag="_mouth"), + Portal(name="Frog Stairs to Frog's Domain's Entrance", region="Frog Stairs to Frog's Domain", + destination="frog cave main", tag="_Entrance"), + Portal(name="Frog Stairs to Frog's Domain's Exit", region="Frog Stairs Lower", + destination="frog cave main", tag="_Exit"), + + Portal(name="Frog's Domain Ladder Exit", region="Frog's Domain Entry", + destination="Frog Stairs", tag="_Entrance"), + Portal(name="Frog's Domain Orb Exit", region="Frog's Domain Back", + destination="Frog Stairs", tag="_Exit"), + + Portal(name="Library Exterior Tree", region="Library Exterior Tree Region", + destination="Atoll Redux", tag="_"), + Portal(name="Library Exterior Ladder", region="Library Exterior Ladder Region", + destination="Library Hall", tag="_"), + + Portal(name="Library Hall Bookshelf Exit", region="Library Hall Bookshelf", + destination="Library Exterior", tag="_"), + Portal(name="Library Hero's Grave", region="Library Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="Library Hall to Rotunda", region="Library Hall to Rotunda", + destination="Library Rotunda", tag="_"), + + Portal(name="Library Rotunda Lower Exit", region="Library Rotunda to Hall", + destination="Library Hall", tag="_"), + Portal(name="Library Rotunda Upper Exit", region="Library Rotunda to Lab", + destination="Library Lab", tag="_"), + + Portal(name="Library Lab to Rotunda", region="Library Lab Lower", + destination="Library Rotunda", tag="_"), + Portal(name="Library to Far Shore", region="Library Portal", + destination="Transit", tag="_teleporter_library teleporter"), + Portal(name="Library Lab to Librarian Arena", region="Library Lab to Librarian", + destination="Library Arena", tag="_"), + + Portal(name="Librarian Arena Exit", region="Library Arena", + destination="Library Lab", tag="_"), Portal(name="Stairs to Top of the Mountain", region="Lower Mountain Stairs", destination="Mountaintop", tag="_"), @@ -1183,6 +1183,8 @@ class DeadEnd(IntEnum): [], "Library Hero's Grave Region": [], + "Library Hall to Rotunda": + [], }, "Library Hero's Grave Region": { "Library Hall": diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 0bd8c5e8068..a4295cf9f2a 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -24,10 +24,10 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]: regions: Dict[str, Region] = {} if world.options.entrance_rando: portal_pairs = pair_portals(world) - # 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) + sorted_portal_pairs = sort_portals(portal_pairs) + for portal1, portal2 in sorted_portal_pairs.items(): + world.multiworld.spoiler.set_entrance(portal1, portal2, "both", world.player) else: portal_pairs = vanilla_portals() @@ -504,3 +504,29 @@ def update_reachable_regions(connected_regions: Set[str], traversal_reqs: Dict[s connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic) return connected_regions + + +# sort the portal dict by the name of the first portal, referring to the portal order in the master portal list +def sort_portals(portal_pairs: Dict[Portal, Portal]) -> Dict[str, str]: + sorted_pairs: Dict[str, str] = {} + reference_list: List[str] = [portal.name for portal in portal_mapping] + reference_list.append("Shop Portal") + + # note: this is not necessary yet since the shop portals aren't numbered yet -- they will be when decoupled happens + # due to plando, there can be a variable number of shops + # I could either do it like this, or just go up to like 200, this seemed better + # shop_count = 0 + # for portal1, portal2 in portal_pairs.items(): + # if portal1.name.startswith("Shop"): + # shop_count += 1 + # if portal2.name.startswith("Shop"): + # shop_count += 1 + # reference_list.extend([f"Shop Portal {i + 1}" for i in range(shop_count)]) + + for name in reference_list: + for portal1, portal2 in portal_pairs.items(): + if name == portal1.name: + sorted_pairs[portal1.name] = portal2.name + break + return sorted_pairs + diff --git a/worlds/tunic/locations.py b/worlds/tunic/locations.py index 2d87140fe50..09916228163 100644 --- a/worlds/tunic/locations.py +++ b/worlds/tunic/locations.py @@ -208,15 +208,15 @@ class TunicLocationData(NamedTuple): "Monastery - Monastery Chest": TunicLocationData("Quarry", "Monastery Back"), "Quarry - [Back Entrance] Bushes Holy Cross": TunicLocationData("Quarry Back", "Quarry Back", location_group="Holy Cross"), "Quarry - [Back Entrance] Chest": TunicLocationData("Quarry Back", "Quarry Back"), - "Quarry - [Central] Near Shortcut Ladder": TunicLocationData("Quarry", "Quarry"), + "Quarry - [Central] Near Shortcut Ladder": TunicLocationData("Quarry Back", "Quarry Back"), "Quarry - [East] Near Telescope": TunicLocationData("Quarry", "Quarry"), "Quarry - [East] Upper Floor": TunicLocationData("Quarry", "Quarry"), - "Quarry - [Central] Below Entry Walkway": TunicLocationData("Quarry", "Quarry"), + "Quarry - [Central] Below Entry Walkway": TunicLocationData("Quarry Back", "Quarry Back"), "Quarry - [East] Obscured Near Winding Staircase": TunicLocationData("Quarry", "Quarry"), "Quarry - [East] Obscured Beneath Scaffolding": TunicLocationData("Quarry", "Quarry"), "Quarry - [East] Obscured Near Telescope": TunicLocationData("Quarry", "Quarry"), "Quarry - [Back Entrance] Obscured Behind Wall": TunicLocationData("Quarry Back", "Quarry Back"), - "Quarry - [Central] Obscured Below Entry Walkway": TunicLocationData("Quarry", "Quarry"), + "Quarry - [Central] Obscured Below Entry Walkway": TunicLocationData("Quarry Back", "Quarry Back"), "Quarry - [Central] Top Floor Overhang": TunicLocationData("Quarry", "Quarry"), "Quarry - [East] Near Bridge": TunicLocationData("Quarry", "Quarry"), "Quarry - [Central] Above Ladder": TunicLocationData("Quarry", "Quarry Monastery Entry"), diff --git a/worlds/witness/data/WitnessLogic.txt b/worlds/witness/data/WitnessLogic.txt index 272ed176e34..b7814626ada 100644 --- a/worlds/witness/data/WitnessLogic.txt +++ b/worlds/witness/data/WitnessLogic.txt @@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: -158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True +159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers @@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry Elevator (Mountain Bottom Floor): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158530 - 0x3D9A6 (Elevator Door Close 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 diff --git a/worlds/witness/data/WitnessLogicExpert.txt b/worlds/witness/data/WitnessLogicExpert.txt index 63e7e36c243..1d1d010fde8 100644 --- a/worlds/witness/data/WitnessLogicExpert.txt +++ b/worlds/witness/data/WitnessLogicExpert.txt @@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: -158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True +159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Shapers & Dots & Full Dots 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers & Shapers & Dots & Full Dots @@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB 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 +158530 - 0x3D9A6 (Elevator Door Close 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 diff --git a/worlds/witness/data/WitnessLogicVanilla.txt b/worlds/witness/data/WitnessLogicVanilla.txt index 1aa9655361f..851031ab72f 100644 --- a/worlds/witness/data/WitnessLogicVanilla.txt +++ b/worlds/witness/data/WitnessLogicVanilla.txt @@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: -158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True +159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers @@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry Elevator (Mountain Bottom Floor): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158530 - 0x3D9A6 (Elevator Door Close 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 diff --git a/worlds/yugioh06/boosterpacks.py b/worlds/yugioh06/boosterpacks.py index f6f4ec7732c..645977d28de 100644 --- a/worlds/yugioh06/boosterpacks.py +++ b/worlds/yugioh06/boosterpacks.py @@ -1,13 +1,13 @@ -from typing import Dict, Set +from typing import Dict, List -booster_contents: Dict[str, Set[str]] = { - "LEGEND OF B.E.W.D.": { +booster_contents: Dict[str, List[str]] = { + "LEGEND OF B.E.W.D.": [ "Exodia", "Dark Magician", "Polymerization", "Skull Servant" - }, - "METAL RAIDERS": { + ], + "METAL RAIDERS": [ "Petit Moth", "Cocoon of Evolution", "Time Wizard", @@ -30,8 +30,8 @@ "Solemn Judgment", "Dream Clown", "Heavy Storm" - }, - "PHARAOH'S SERVANT": { + ], + "PHARAOH'S SERVANT": [ "Beast of Talwar", "Jinzo", "Gearfried the Iron Knight", @@ -43,8 +43,8 @@ "The Shallow Grave", "Nobleman of Crossout", "Magic Drain" - }, - "PHARAONIC GUARDIAN": { + ], + "PHARAONIC GUARDIAN": [ "Don Zaloog", "Reasoning", "Dark Snake Syndrome", @@ -71,8 +71,8 @@ "Book of Taiyou", "Dust Tornado", "Raigeki Break" - }, - "SPELL RULER": { + ], + "SPELL RULER": [ "Ritual", "Messenger of Peace", "Megamorph", @@ -94,8 +94,8 @@ "Senju of the Thousand Hands", "Sonic Bird", "Mystical Space Typhoon" - }, - "LABYRINTH OF NIGHTMARE": { + ], + "LABYRINTH OF NIGHTMARE": [ "Destiny Board", "Spirit Message 'I'", "Spirit Message 'N'", @@ -119,8 +119,8 @@ "United We Stand", "Earthbound Spirit", "The Masked Beast" - }, - "LEGACY OF DARKNESS": { + ], + "LEGACY OF DARKNESS": [ "Last Turn", "Yata-Garasu", "Opticlops", @@ -143,8 +143,8 @@ "Maharaghi", "Susa Soldier", "Emergency Provisions", - }, - "MAGICIAN'S FORCE": { + ], + "MAGICIAN'S FORCE": [ "Huge Revolution", "Oppressed People", "United Resistance", @@ -185,8 +185,8 @@ "Royal Magical Library", "Spell Shield Type-8", "Tribute Doll", - }, - "DARK CRISIS": { + ], + "DARK CRISIS": [ "Final Countdown", "Ojama Green", "Dark Scorpion Combination", @@ -213,8 +213,8 @@ "Spell Reproduction", "Contract with the Abyss", "Dark Master - Zorc" - }, - "INVASION OF CHAOS": { + ], + "INVASION OF CHAOS": [ "Ojama Delta Hurricane", "Ojama Yellow", "Ojama Black", @@ -241,8 +241,8 @@ "Cursed Seal of the Forbidden Spell", "Stray Lambs", "Manju of the Ten Thousand Hands" - }, - "ANCIENT SANCTUARY": { + ], + "ANCIENT SANCTUARY": [ "Monster Gate", "Wall of Revealing Light", "Mystik Wok", @@ -255,8 +255,8 @@ "King of the Swamp", "Enemy Controller", "Enchanting Fitting Room" - }, - "SOUL OF THE DUELIST": { + ], + "SOUL OF THE DUELIST": [ "Ninja Grandmaster Sasuke", "Mystic Swordsman LV2", "Mystic Swordsman LV4", @@ -272,8 +272,8 @@ "Level Up!", "Howling Insect", "Mobius the Frost Monarch" - }, - "RISE OF DESTINY": { + ], + "RISE OF DESTINY": [ "Homunculus the Alchemic Being", "Thestalos the Firestorm Monarch", "Roc from the Valley of Haze", @@ -283,8 +283,8 @@ "Ultimate Insect Lv3", "Divine Wrath", "Serial Spell" - }, - "FLAMING ETERNITY": { + ], + "FLAMING ETERNITY": [ "Insect Knight", "Chiron the Mage", "Granmarg the Rock Monarch", @@ -297,8 +297,8 @@ "Golem Sentry", "Rescue Cat", "Blade Rabbit" - }, - "THE LOST MILLENIUM": { + ], + "THE LOST MILLENIUM": [ "Ritual", "Megarock Dragon", "D.D. Survivor", @@ -311,8 +311,8 @@ "Elemental Hero Thunder Giant", "Aussa the Earth Charmer", "Brain Control" - }, - "CYBERNETIC REVOLUTION": { + ], + "CYBERNETIC REVOLUTION": [ "Power Bond", "Cyber Dragon", "Cyber Twin Dragon", @@ -322,8 +322,8 @@ "Miracle Fusion", "Elemental Hero Bubbleman", "Jerry Beans Man" - }, - "ELEMENTAL ENERGY": { + ], + "ELEMENTAL ENERGY": [ "V-Tiger Jet", "W-Wing Catapult", "VW-Tiger Catapult", @@ -344,8 +344,8 @@ "Elemental Hero Bladedge", "Pot of Avarice", "B.E.S. Tetran" - }, - "SHADOW OF INFINITY": { + ], + "SHADOW OF INFINITY": [ "Hamon, Lord of Striking Thunder", "Raviel, Lord of Phantasms", "Uria, Lord of Searing Flames", @@ -357,8 +357,8 @@ "Gokipon", "Demise, King of Armageddon", "Anteatereatingant" - }, - "GAME GIFT COLLECTION": { + ], + "GAME GIFT COLLECTION": [ "Ritual", "Valkyrion the Magna Warrior", "Alpha the Magnet Warrior", @@ -383,8 +383,8 @@ "Card Destruction", "Dark Magic Ritual", "Calamity of the Wicked" - }, - "Special Gift Collection": { + ], + "Special Gift Collection": [ "Gate Guardian", "Scapegoat", "Gil Garth", @@ -398,8 +398,8 @@ "Curse of Vampire", "Elemental Hero Flame Wingman", "Magician of Black Chaos" - }, - "Fairy Collection": { + ], + "Fairy Collection": [ "Silpheed", "Dunames Dark Witch", "Hysteric Fairy", @@ -416,8 +416,8 @@ "Asura Priest", "Manju of the Ten Thousand Hands", "Senju of the Thousand Hands" - }, - "Dragon Collection": { + ], + "Dragon Collection": [ "Victory D.", "Chaos Emperor Dragon - Envoy of the End", "Kaiser Glider", @@ -434,16 +434,16 @@ "Troop Dragon", "Horus the Black Flame Dragon LV4", "Pitch-Dark Dragon" - }, - "Warrior Collection A": { + ], + "Warrior Collection A": [ "Gate Guardian", "Gearfried the Iron Knight", "Dimensional Warrior", "Command Knight", "The Last Warrior from Another Planet", "Dream Clown" - }, - "Warrior Collection B": { + ], + "Warrior Collection B": [ "Don Zaloog", "Dark Scorpion - Chick the Yellow", "Dark Scorpion - Meanae the Thorn", @@ -467,8 +467,8 @@ "Blade Knight", "Marauding Captain", "Toon Goblin Attack Force" - }, - "Fiend Collection A": { + ], + "Fiend Collection A": [ "Sangan", "Castle of Dark Illusions", "Barox", @@ -480,8 +480,8 @@ "Spear Cretin", "Versago the Destroyer", "Toon Summoned Skull" - }, - "Fiend Collection B": { + ], + "Fiend Collection B": [ "Raviel, Lord of Phantasms", "Yata-Garasu", "Helpoemer", @@ -505,15 +505,15 @@ "Jowls of Dark Demise", "D. D. Trainer", "Earthbound Spirit" - }, - "Machine Collection A": { + ], + "Machine Collection A": [ "Cyber-Stein", "Mechanicalchaser", "Jinzo", "UFO Turtle", "Cyber-Tech Alligator" - }, - "Machine Collection B": { + ], + "Machine Collection B": [ "X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", @@ -531,8 +531,8 @@ "Red Gadget", "Yellow Gadget", "B.E.S. Tetran" - }, - "Spellcaster Collection A": { + ], + "Spellcaster Collection A": [ "Exodia", "Dark Sage", "Dark Magician", @@ -544,8 +544,8 @@ "Injection Fairy Lily", "Cosmo Queen", "Magician of Black Chaos" - }, - "Spellcaster Collection B": { + ], + "Spellcaster Collection B": [ "Jowgen the Spiritualist", "Tsukuyomi", "Manticore of Darkness", @@ -574,8 +574,8 @@ "Royal Magical Library", "Aussa the Earth Charmer", - }, - "Zombie Collection": { + ], + "Zombie Collection": [ "Skull Servant", "Regenerating Mummy", "Ryu Kokki", @@ -590,8 +590,8 @@ "Des Lacooda", "Wandering Mummy", "Royal Keeper" - }, - "Special Monsters A": { + ], + "Special Monsters A": [ "X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", @@ -626,8 +626,8 @@ "Fushi No Tori", "Maharaghi", "Susa Soldier" - }, - "Special Monsters B": { + ], + "Special Monsters B": [ "Polymerization", "Mystic Swordsman LV2", "Mystic Swordsman LV4", @@ -656,8 +656,8 @@ "Level Up!", "Ultimate Insect Lv3", "Ultimate Insect Lv5" - }, - "Reverse Collection": { + ], + "Reverse Collection": [ "Magical Merchant", "Castle of Dark Illusions", "Magician of Faith", @@ -675,8 +675,8 @@ "Spear Cretin", "Nobleman of Crossout", "Aussa the Earth Charmer" - }, - "LP Recovery Collection": { + ], + "LP Recovery Collection": [ "Mystik Wok", "Poison of the Old Man", "Hysteric Fairy", @@ -691,8 +691,8 @@ "Elemental Hero Steam Healer", "Fushi No Tori", "Emergency Provisions" - }, - "Special Summon Collection A": { + ], + "Special Summon Collection A": [ "Perfectly Ultimate Great Moth", "Dark Sage", "Polymerization", @@ -726,8 +726,8 @@ "Morphing Jar #2", "Spear Cretin", "Dark Magic Curtain" - }, - "Special Summon Collection B": { + ], + "Special Summon Collection B": [ "Monster Gate", "Chaos Emperor Dragon - Envoy of the End", "Ojama Trio", @@ -756,8 +756,8 @@ "Tribute Doll", "Enchanting Fitting Room", "Stray Lambs" - }, - "Special Summon Collection C": { + ], + "Special Summon Collection C": [ "Hamon, Lord of Striking Thunder", "Raviel, Lord of Phantasms", "Uria, Lord of Searing Flames", @@ -782,13 +782,13 @@ "Ultimate Insect Lv5", "Rescue Cat", "Anteatereatingant" - }, - "Equipment Collection": { + ], + "Equipment Collection": [ "Megamorph", "Cestus of Dagla", "United We Stand" - }, - "Continuous Spell/Trap A": { + ], + "Continuous Spell/Trap A": [ "Destiny Board", "Spirit Message 'I'", "Spirit Message 'N'", @@ -801,8 +801,8 @@ "Solemn Wishes", "Embodiment of Apophis", "Toon World" - }, - "Continuous Spell/Trap B": { + ], + "Continuous Spell/Trap B": [ "Hamon, Lord of Striking Thunder", "Uria, Lord of Searing Flames", "Wave-Motion Cannon", @@ -815,8 +815,8 @@ "Skull Zoma", "Pitch-Black Power Stone", "Metal Reflect Slime" - }, - "Quick/Counter Collection": { + ], + "Quick/Counter Collection": [ "Mystik Wok", "Poison of the Old Man", "Scapegoat", @@ -841,8 +841,8 @@ "Book of Moon", "Serial Spell", "Mystical Space Typhoon" - }, - "Direct Damage Collection": { + ], + "Direct Damage Collection": [ "Hamon, Lord of Striking Thunder", "Chaos Emperor Dragon - Envoy of the End", "Dark Snake Syndrome", @@ -868,8 +868,8 @@ "Jowls of Dark Demise", "Stealth Bird", "Elemental Hero Bladedge", - }, - "Direct Attack Collection": { + ], + "Direct Attack Collection": [ "Victory D.", "Dark Scorpion Combination", "Spirit Reaper", @@ -880,8 +880,8 @@ "Toon Mermaid", "Toon Summoned Skull", "Toon Dark Magician Girl" - }, - "Monster Destroy Collection": { + ], + "Monster Destroy Collection": [ "Hamon, Lord of Striking Thunder", "Inferno", "Ninja Grandmaster Sasuke", @@ -912,12 +912,12 @@ "Offerings to the Doomed", "Divine Wrath", "Dream Clown" - }, + ], } def get_booster_locations(booster: str) -> Dict[str, str]: return { f"{booster} {i}": content - for i, content in enumerate(booster_contents[booster]) + for i, content in enumerate(booster_contents[booster], 1) } diff --git a/worlds/yugioh06/structure_deck.py b/worlds/yugioh06/structure_deck.py index d58223f2e21..3559e7c5153 100644 --- a/worlds/yugioh06/structure_deck.py +++ b/worlds/yugioh06/structure_deck.py @@ -1,7 +1,7 @@ -from typing import Dict, Set +from typing import Dict, List -structure_contents: Dict[str, Set] = { - "dragons_roar": { +structure_contents: Dict[str, List[str]] = { + "dragons_roar": [ "Luster Dragon", "Armed Dragon LV3", "Armed Dragon LV5", @@ -14,9 +14,9 @@ "Stamping Destruction", "Heavy Storm", "Dust Tornado", - "Mystical Space Typhoon", - }, - "zombie_madness": { + "Mystical Space Typhoon" + ], + "zombie_madness": [ "Pyramid Turtle", "Regenerating Mummy", "Ryu Kokki", @@ -26,9 +26,9 @@ "Reload", "Heavy Storm", "Dust Tornado", - "Mystical Space Typhoon", - }, - "blazing_destruction": { + "Mystical Space Typhoon" + ], + "blazing_destruction": [ "Inferno", "Solar Flare Dragon", "UFO Turtle", @@ -38,9 +38,9 @@ "Level Limit - Area B", "Heavy Storm", "Dust Tornado", - "Mystical Space Typhoon", - }, - "fury_from_the_deep": { + "Mystical Space Typhoon" + ], + "fury_from_the_deep": [ "Mother Grizzly", "Water Beaters", "Gravity Bind", @@ -48,9 +48,9 @@ "Mobius the Frost Monarch", "Heavy Storm", "Dust Tornado", - "Mystical Space Typhoon", - }, - "warriors_triumph": { + "Mystical Space Typhoon" + ], + "warriors_triumph": [ "Gearfried the Iron Knight", "D.D. Warrior Lady", "Marauding Captain", @@ -60,9 +60,9 @@ "Reload", "Heavy Storm", "Dust Tornado", - "Mystical Space Typhoon", - }, - "spellcasters_judgement": { + "Mystical Space Typhoon" + ], + "spellcasters_judgement": [ "Dark Magician", "Apprentice Magician", "Breaker the Magical Warrior", @@ -70,14 +70,18 @@ "Skilled Dark Magician", "Tsukuyomi", "Magical Dimension", - "Mage PowerSpell-Counter Cards", + "Mage Power", + "Spell-Counter Cards", "Heavy Storm", "Dust Tornado", - "Mystical Space Typhoon", - }, - "none": {}, + "Mystical Space Typhoon" + ], + "none": [], } def get_deck_content_locations(deck: str) -> Dict[str, str]: - return {f"{deck} {i}": content for i, content in enumerate(structure_contents[deck])} + return { + f"{deck} {i}": content + for i, content in enumerate(structure_contents[deck], 1) + }