diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9f7b9ef2f0a3..9c0cd14f8b27 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,10 +24,10 @@ env:
# NOTE: since appimage/appimagetool and appimage/type2-runtime does not have tags anymore,
# we check the sha256 and require manual intervention if it was updated.
APPIMAGE_FORK: 'PopTracker'
- APPIMAGETOOL_VERSION: 'r-2025-10-19'
- APPIMAGETOOL_X86_64_HASH: '9493a6b253a01f84acb9c624c38810ecfa11d99daa829b952b0bff43113080f9'
- APPIMAGE_RUNTIME_VERSION: 'r-2025-08-11'
- APPIMAGE_RUNTIME_X86_64_HASH: 'e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b'
+ APPIMAGETOOL_VERSION: 'r-2025-11-18'
+ APPIMAGETOOL_X86_64_HASH: '4577a452b30af2337123fbb383aea154b618e51ad5448c3b62085cbbbfbfd9a2'
+ APPIMAGE_RUNTIME_VERSION: 'r-2025-11-07'
+ APPIMAGE_RUNTIME_X86_64_HASH: '27ddd3f78e483fc5f7856e413d7c17092917f8c35bfe3318a0d378aa9435ad17'
permissions: # permissions required for attestation
id-token: 'write'
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index cf9ce08faf38..0061dd15b000 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -11,7 +11,7 @@ on:
- "!.github/workflows/**"
- ".github/workflows/docker.yml"
branches:
- - "*"
+ - "main"
tags:
- "v?[0-9]+.[0-9]+.[0-9]*"
workflow_dispatch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e7a4be9cdf68..7f81e5750746 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,10 +12,10 @@ env:
# NOTE: since appimage/appimagetool and appimage/type2-runtime does not have tags anymore,
# we check the sha256 and require manual intervention if it was updated.
APPIMAGE_FORK: 'PopTracker'
- APPIMAGETOOL_VERSION: 'r-2025-10-19'
- APPIMAGETOOL_X86_64_HASH: '9493a6b253a01f84acb9c624c38810ecfa11d99daa829b952b0bff43113080f9'
- APPIMAGE_RUNTIME_VERSION: 'r-2025-08-11'
- APPIMAGE_RUNTIME_X86_64_HASH: 'e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b'
+ APPIMAGETOOL_VERSION: 'r-2025-11-18'
+ APPIMAGETOOL_X86_64_HASH: '4577a452b30af2337123fbb383aea154b618e51ad5448c3b62085cbbbfbfd9a2'
+ APPIMAGE_RUNTIME_VERSION: 'r-2025-11-07'
+ APPIMAGE_RUNTIME_X86_64_HASH: '27ddd3f78e483fc5f7856e413d7c17092917f8c35bfe3318a0d378aa9435ad17'
permissions: # permissions required for attestation
id-token: 'write'
diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml
index 90a5d70b8e0c..b08b389005ec 100644
--- a/.github/workflows/unittests.yml
+++ b/.github/workflows/unittests.yml
@@ -59,7 +59,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pytest pytest-subtests pytest-xdist
+ pip install -r ci-requirements.txt
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
python Launcher.py --update_settings # make sure host.yaml exists for tests
- name: Unittests
diff --git a/.run/Build APWorld.run.xml b/.run/Build APWorld.run.xml
index db6a305e7bb3..fe41bfe48945 100644
--- a/.run/Build APWorld.run.xml
+++ b/.run/Build APWorld.run.xml
@@ -12,8 +12,8 @@
-
-
+
+
diff --git a/BaseClasses.py b/BaseClasses.py
index bf00ffe6303d..f9c5853642f7 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -1726,9 +1726,10 @@ def create_playthrough(self, create_paths: bool = True) -> None:
logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (
location.item.name, location.item.player, location.name, location.player) for location in
sphere_candidates])
- if any([multiworld.worlds[location.item.player].options.accessibility != 'minimal' for location in sphere_candidates]):
- raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
- f'Something went terribly wrong here.')
+ if not multiworld.has_beaten_game(state):
+ raise RuntimeError("During playthrough generation, the game was determined to be unbeatable. "
+ "Something went terribly wrong here. "
+ f"Unreachable progression items: {sphere_candidates}")
else:
self.unreachables = sphere_candidates
break
diff --git a/CommonClient.py b/CommonClient.py
old mode 100644
new mode 100755
index 41cc08d1d0a9..1111adb080cc
--- a/CommonClient.py
+++ b/CommonClient.py
@@ -323,7 +323,7 @@ def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int])
hint_cost: int | None
"""Current Hint Cost per Hint from the server"""
hint_points: int | None
- """Current avaliable Hint Points from the server"""
+ """Current available Hint Points from the server"""
player_names: dict[int, str]
"""Current lookup of slot number to player display name from server (includes aliases)"""
@@ -572,6 +572,10 @@ def is_uninteresting_item_send(self, print_json_packet: dict) -> bool:
return print_json_packet.get("type", "") == "ItemSend" \
and not self.slot_concerns_self(print_json_packet["receiving"]) \
and not self.slot_concerns_self(print_json_packet["item"].player)
+
+ def is_connection_change(self, print_json_packet: dict) -> bool:
+ """Helper function for filtering out connection changes."""
+ return print_json_packet.get("type", "") in ["Join","Part"]
def on_print(self, args: dict):
logger.info(args["text"])
diff --git a/CustomWorldsInstalled.xlsx b/CustomWorldsInstalled.xlsx
index c240f628ad1e..a4b1938bacfb 100644
Binary files a/CustomWorldsInstalled.xlsx and b/CustomWorldsInstalled.xlsx differ
diff --git a/Generate.py b/Generate.py
index 1044a9880d88..ae575a0a761f 100644
--- a/Generate.py
+++ b/Generate.py
@@ -189,6 +189,11 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
yaml[category][key] = option
elif category_name not in yaml:
logging.warning(f"Meta: Category {category_name} is not present in {path}.")
+ elif key == "triggers":
+ if "triggers" not in yaml[category_name]:
+ yaml[category_name][key] = []
+ for trigger in option:
+ yaml[category_name][key].append(trigger)
else:
yaml[category_name][key] = option
@@ -342,7 +347,9 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
elif isinstance(new_value, list):
cleaned_value.extend(new_value)
elif isinstance(new_value, dict):
- cleaned_value = dict(Counter(cleaned_value) + Counter(new_value))
+ counter_value = Counter(cleaned_value)
+ counter_value.update(new_value)
+ cleaned_value = dict(counter_value)
else:
raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
@@ -356,13 +363,18 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
for element in new_value:
cleaned_value.remove(element)
elif isinstance(new_value, dict):
- cleaned_value = dict(Counter(cleaned_value) - Counter(new_value))
+ counter_value = Counter(cleaned_value)
+ counter_value.subtract(new_value)
+ cleaned_value = dict(counter_value)
else:
raise Exception(f"Cannot apply remove to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
cleaned_weights[option_name] = cleaned_value
else:
- cleaned_weights[option_name] = new_weights[option]
+ # Options starting with + and - may modify values in-place, and new_weights may be shared by multiple slots
+ # using the same .yaml, so ensure that the new value is a copy.
+ cleaned_value = copy.deepcopy(new_weights[option])
+ cleaned_weights[option_name] = cleaned_value
new_options = set(cleaned_weights) - set(weights)
weights.update(cleaned_weights)
if new_options:
@@ -385,6 +397,8 @@ def roll_meta_option(option_key, game: str, category_dict: dict) -> Any:
if options[option_key].supports_weighting:
return get_choice(option_key, category_dict)
return category_dict[option_key]
+ if option_key == "triggers":
+ return category_dict[option_key]
raise Options.OptionError(f"Error generating meta option {option_key} for {game}.")
diff --git a/Launcher.py b/Launcher.py
index adc3cb96ef24..89421ff30508 100644
--- a/Launcher.py
+++ b/Launcher.py
@@ -75,12 +75,17 @@ def open_patch():
launch([*exe, file], component.cli)
-def generate_yamls():
+def generate_yamls(*args):
from Options import generate_yaml_templates
+ parser = argparse.ArgumentParser(description="Generate Template Options", usage="[-h] [--skip_open_folder]")
+ parser.add_argument("--skip_open_folder", action="store_true")
+ args = parser.parse_args(args)
+
target = Utils.user_path("Players", "Templates")
generate_yaml_templates(target, False)
- open_folder(target)
+ if not args.skip_open_folder:
+ open_folder(target)
def browse_files():
@@ -213,12 +218,17 @@ def launch(exe, in_terminal=False):
def create_shortcut(button: Any, component: Component) -> None:
from pyshortcuts import make_shortcut
- script = sys.argv[0]
- wkdir = Utils.local_path()
+ env = os.environ
+ if "APPIMAGE" in env:
+ script = env["ARGV0"]
+ wkdir = None # defaults to ~ on Linux
+ else:
+ script = sys.argv[0]
+ wkdir = Utils.local_path()
script = f"{script} \"{component.display_name}\""
make_shortcut(script, name=f"Archipelago {component.display_name}", icon=local_path("data", "icon.ico"),
- startmenu=False, terminal=False, working_dir=wkdir)
+ startmenu=False, terminal=False, working_dir=wkdir, noexe=Utils.is_frozen())
button.menu.dismiss()
diff --git a/Main.py b/Main.py
index 849d16fa299f..24eccc25ffa8 100644
--- a/Main.py
+++ b/Main.py
@@ -326,7 +326,7 @@ def precollect_hint(location: Location, auto_status: HintStatus):
if current_sphere:
spheres.append(dict(current_sphere))
- multidata: NetUtils.MultiData | bytes = {
+ multidata: NetUtils.MultiData = {
"slot_data": slot_data,
"slot_info": slot_info,
"connect_names": {name: (0, player) for player, name in multiworld.player_name.items()},
@@ -350,11 +350,11 @@ def precollect_hint(location: Location, auto_status: HintStatus):
for key in ("slot_data", "er_hint_data"):
multidata[key] = convert_to_base_types(multidata[key])
- multidata = zlib.compress(restricted_dumps(multidata), 9)
+ serialized_multidata = zlib.compress(restricted_dumps(multidata), 9)
with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f:
f.write(bytes([3])) # version of format
- f.write(multidata)
+ f.write(serialized_multidata)
output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result():
diff --git a/MultiServer.py b/MultiServer.py
index 261efafb44f9..712841297b60 100644
--- a/MultiServer.py
+++ b/MultiServer.py
@@ -503,7 +503,7 @@ def _load(self, decoded_obj: MultiData, game_data_packages: typing.Dict[str, typ
self.read_data["race_mode"] = lambda: decoded_obj.get("race_mode", 0)
mdata_ver = decoded_obj["minimum_versions"]["server"]
if mdata_ver > version_tuple:
- raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver},"
+ raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver}, "
f"however this server is of version {version_tuple}")
self.generator_version = Version(*decoded_obj["version"])
clients_ver = decoded_obj["minimum_versions"].get("clients", {})
@@ -906,9 +906,12 @@ def on_client_status_change(self, team: int, slot: int):
if targets:
self.broadcast(targets, [{"cmd": "SetReply", "key": key, "value": self.client_game_state[team, slot]}])
+from pony.orm import db_session
class Ashipelago:
+
webhook_active = False
webhook_queue: queue.SimpleQueue
+ room_id: int
room_url: str
seed_url: str
admin_password: str
@@ -928,6 +931,7 @@ def __init__(self, ctx: Context):
# Initializes required fields needed to properly manage the newly started room
def start_room(self, room: Room, is_tracked: int, webhook_settings: dict, admin_password):
+ self.room_id = room.id
self.room_url = urlsafe_b64encode(room.id.bytes).rstrip(b'=').decode('ascii')
self.seed_url = urlsafe_b64encode(room.seed.id.bytes).rstrip(b'=').decode('ascii')
self.admin_password = admin_password
@@ -1101,6 +1105,13 @@ def push_player_connection(self):
}
self._push_to_webhook(connection)
+ # Manually refreshes the room timeout
+ def refresh_room(self):
+ with db_session:
+ room = Room.get(id=self.room_id)
+ room.multisave = pickle.dumps(self.ctx.get_save())
+ room.last_activity = datetime.datetime.utcnow()
+
# Helper function used to enqueue a message to be pushed to Dynxbot
def _push_to_webhook(self, message: typing.Dict[str, typing.Any]):
if not self.webhook_active:
@@ -1373,6 +1384,7 @@ async def on_client_joined(ctx: Context, client: Client):
# Ashipelago customization
ctx.dynx.push_player_connection()
+ ctx.dynx.refresh_room()
async def on_client_left(ctx: Context, client: Client):
if len(ctx.clients[client.team][client.slot]) < 1:
diff --git a/Options.py b/Options.py
index d4e42fc02d8c..c37d0cee2810 100644
--- a/Options.py
+++ b/Options.py
@@ -688,6 +688,12 @@ class Range(NumericOption):
range_start = 0
range_end = 1
+ _RANDOM_OPTS = [
+ "random", "random-low", "random-middle", "random-high",
+ "random-range-low--", "random-range-middle--",
+ "random-range-high--", "random-range--",
+ ]
+
def __init__(self, value: int):
if value < self.range_start:
raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}")
@@ -713,9 +719,26 @@ def from_text(cls, text: str) -> Range:
# these are the conditions where "true" and "false" make sense
if text == "true":
return cls.from_any(cls.default)
- else: # "false"
- return cls(0)
- return cls(int(text))
+ # "false"
+ return cls(0)
+
+ try:
+ num = int(text)
+ except ValueError:
+ # text is not a number
+ # Handle conditionally acceptable values here rather than in the f-string
+ default = ""
+ truefalse = ""
+ if hasattr(cls, "default"):
+ default = ", default"
+ if cls.range_start == 0 and cls.default != 0:
+ truefalse = ", \"true\", \"false\""
+ raise Exception(f"Invalid range value {text!r}. Acceptable values are: "
+ f"{default}, high, low{truefalse}, "
+ f"{', '.join(cls._RANDOM_OPTS)}.")
+
+ return cls(num)
+
@classmethod
def weighted_range(cls, text) -> Range:
@@ -731,9 +754,7 @@ def weighted_range(cls, text) -> Range:
return cls(random.randint(cls.range_start, cls.range_end))
else:
raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. "
- f"Acceptable values are: random, random-high, random-middle, random-low, "
- f"random-range-low--, random-range-middle--, "
- f"random-range-high--, or random-range--.")
+ f"Acceptable values are: {', '.join(cls._RANDOM_OPTS)}.")
@classmethod
def custom_range(cls, text) -> Range:
@@ -1018,6 +1039,8 @@ class PlandoTexts(Option[typing.List[PlandoText]], VerifyKeys):
supports_weighting = False
display_name = "Plando Texts"
+ visibility = Visibility.template | Visibility.complex_ui | Visibility.spoiler
+
def __init__(self, value: typing.Iterable[PlandoText]) -> None:
self.value = list(deepcopy(value))
super().__init__()
@@ -1144,6 +1167,8 @@ class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=Connect
entrances: typing.ClassVar[typing.AbstractSet[str]]
exits: typing.ClassVar[typing.AbstractSet[str]]
+ visibility = Visibility.template | Visibility.complex_ui | Visibility.spoiler
+
duplicate_exits: bool = False
"""Whether or not exits should be allowed to be duplicate."""
@@ -1435,6 +1460,7 @@ class DeathLink(Toggle):
class ItemLinks(OptionList):
"""Share part of your item pool with other players."""
display_name = "Item Links"
+ visibility = Visibility.template | Visibility.complex_ui | Visibility.spoiler
rich_text_doc = True
default = []
schema = Schema([
@@ -1519,6 +1545,7 @@ class PlandoItems(Option[typing.List[PlandoItem]]):
default = ()
supports_weighting = False
display_name = "Plando Items"
+ visibility = Visibility.template | Visibility.spoiler
def __init__(self, value: typing.Iterable[PlandoItem]) -> None:
self.value = list(deepcopy(value))
@@ -1726,11 +1753,16 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
def dictify_range(option: Range):
data = {option.default: 50}
- for sub_option in ["random", "random-low", "random-high"]:
+ for sub_option in ["random", "random-low", "random-high",
+ f"random-range-{option.range_start}-{option.range_end}"]:
if sub_option != option.default:
data[sub_option] = 0
-
- notes = {}
+ notes = {
+ "random-low": "random value weighted towards lower values",
+ "random-high": "random value weighted towards higher values",
+ f"random-range-{option.range_start}-{option.range_end}": f"random value between "
+ f"{option.range_start} and {option.range_end}"
+ }
for name, number in getattr(option, "special_range_names", {}).items():
notes[name] = f"equivalent to {number}"
if number in data:
diff --git a/OptionsCreator.py b/OptionsCreator.py
new file mode 100644
index 000000000000..6fafda824bae
--- /dev/null
+++ b/OptionsCreator.py
@@ -0,0 +1,674 @@
+if __name__ == "__main__":
+ import ModuleUpdate
+
+ ModuleUpdate.update()
+
+
+from kvui import (ThemedApp, ScrollBox, MainLayout, ContainerLayout, dp, Widget, MDBoxLayout, TooltipLabel, MDLabel,
+ ToggleButton, MarkupDropdown, ResizableTextField)
+from kivy.uix.behaviors.button import ButtonBehavior
+from kivymd.uix.behaviors import RotateBehavior
+from kivymd.uix.anchorlayout import MDAnchorLayout
+from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelContent, MDExpansionPanelHeader
+from kivymd.uix.list import MDListItem, MDListItemTrailingIcon, MDListItemSupportingText
+from kivymd.uix.slider import MDSlider
+from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText
+from kivymd.uix.menu import MDDropdownMenu
+from kivymd.uix.button import MDButton, MDButtonText, MDIconButton
+from kivymd.uix.dialog import MDDialog
+from kivy.core.text.markup import MarkupLabel
+from kivy.utils import escape_markup
+from kivy.lang.builder import Builder
+from kivy.properties import ObjectProperty
+from textwrap import dedent
+from copy import deepcopy
+import Utils
+import typing
+import webbrowser
+import re
+from urllib.parse import urlparse
+from worlds.AutoWorld import AutoWorldRegister, World
+from Options import (Option, Toggle, TextChoice, Choice, FreeText, NamedRange, Range, OptionSet, OptionList, Removed,
+ OptionCounter, Visibility)
+
+
+def validate_url(x):
+ try:
+ result = urlparse(x)
+ return all([result.scheme, result.netloc])
+ except AttributeError:
+ return False
+
+
+def filter_tooltip(tooltip):
+ if tooltip is None:
+ tooltip = "No tooltip available."
+ tooltip = dedent(tooltip).strip().replace("\n", " ").replace("&", "&") \
+ .replace("[", "&bl;").replace("]", "&br;")
+ tooltip = re.sub(r"\*\*(.+?)\*\*", r"[b]\g<1>[/b]", tooltip)
+ tooltip = re.sub(r"\*(.+?)\*", r"[i]\g<1>[/i]", tooltip)
+ return escape_markup(tooltip)
+
+
+def option_can_be_randomized(option: typing.Type[Option]):
+ # most options can be randomized, so we should just check for those that cannot
+ if not option.supports_weighting:
+ return False
+ elif issubclass(option, FreeText) and not issubclass(option, TextChoice):
+ return False
+ return True
+
+
+def check_random(value: typing.Any):
+ if not isinstance(value, str):
+ return value # cannot be random if evaluated
+ if value.startswith("random-"):
+ return "random"
+ return value
+
+
+class TrailingPressedIconButton(ButtonBehavior, RotateBehavior, MDListItemTrailingIcon):
+ pass
+
+
+class WorldButton(ToggleButton):
+ world_cls: typing.Type[World]
+
+
+class VisualRange(MDBoxLayout):
+ option: typing.Type[Range]
+ name: str
+ tag: MDLabel = ObjectProperty(None)
+ slider: MDSlider = ObjectProperty(None)
+
+ def __init__(self, *args, option: typing.Type[Range], name: str, **kwargs):
+ self.option = option
+ self.name = name
+ super().__init__(*args, **kwargs)
+
+ def update_points(*update_args):
+ pass
+
+ self.slider._update_points = update_points
+
+
+class VisualChoice(MDButton):
+ option: typing.Type[Choice]
+ name: str
+ text: MDButtonText = ObjectProperty(None)
+
+ def __init__(self, *args, option: typing.Type[Choice], name: str, **kwargs):
+ self.option = option
+ self.name = name
+ super().__init__(*args, **kwargs)
+
+
+class VisualNamedRange(MDBoxLayout):
+ option: typing.Type[NamedRange]
+ name: str
+ range: VisualRange = ObjectProperty(None)
+ choice: MDButton = ObjectProperty(None)
+
+ def __init__(self, *args, option: typing.Type[NamedRange], name: str, range_widget: VisualRange, **kwargs):
+ self.option = option
+ self.name = name
+ super().__init__(*args, **kwargs)
+ self.range = range_widget
+ self.add_widget(self.range)
+
+
+class VisualFreeText(ResizableTextField):
+ option: typing.Type[FreeText] | typing.Type[TextChoice]
+ name: str
+
+ def __init__(self, *args, option: typing.Type[FreeText] | typing.Type[TextChoice], name: str, **kwargs):
+ self.option = option
+ self.name = name
+ super().__init__(*args, **kwargs)
+
+
+class VisualTextChoice(MDBoxLayout):
+ option: typing.Type[TextChoice]
+ name: str
+ choice: VisualChoice = ObjectProperty(None)
+ text: VisualFreeText = ObjectProperty(None)
+
+ def __init__(self, *args, option: typing.Type[TextChoice], name: str, choice: VisualChoice,
+ text: VisualFreeText, **kwargs):
+ self.option = option
+ self.name = name
+ super(MDBoxLayout, self).__init__(*args, **kwargs)
+ self.choice = choice
+ self.text = text
+ self.add_widget(self.choice)
+ self.add_widget(self.text)
+
+
+class VisualToggle(MDBoxLayout):
+ button: MDIconButton = ObjectProperty(None)
+ option: typing.Type[Toggle]
+ name: str
+
+ def __init__(self, *args, option: typing.Type[Toggle], name: str, **kwargs):
+ self.option = option
+ self.name = name
+ super().__init__(*args, **kwargs)
+
+
+class CounterItemValue(ResizableTextField):
+ pat = re.compile('[^0-9]')
+
+ def insert_text(self, substring, from_undo=False):
+ return super().insert_text(re.sub(self.pat, "", substring), from_undo=from_undo)
+
+
+class VisualListSetCounter(MDDialog):
+ button: MDIconButton = ObjectProperty(None)
+ option: typing.Type[OptionSet] | typing.Type[OptionList] | typing.Type[OptionCounter]
+ scrollbox: ScrollBox = ObjectProperty(None)
+ add: MDIconButton = ObjectProperty(None)
+ save: MDButton = ObjectProperty(None)
+ input: ResizableTextField = ObjectProperty(None)
+ dropdown: MDDropdownMenu
+ valid_keys: typing.Iterable[str]
+
+ def __init__(self, *args, option: typing.Type[OptionSet] | typing.Type[OptionList],
+ name: str, valid_keys: typing.Iterable[str], **kwargs):
+ self.option = option
+ self.name = name
+ self.valid_keys = valid_keys
+ super().__init__(*args, **kwargs)
+ self.dropdown = MarkupDropdown(caller=self.input, border_margin=dp(2),
+ width=self.input.width, position="bottom")
+ self.input.bind(text=self.on_text)
+ self.input.bind(on_text_validate=self.validate_add)
+
+ def validate_add(self, instance):
+ if self.valid_keys:
+ if self.input.text not in self.valid_keys:
+ MDSnackbar(MDSnackbarText(text="Item must be a valid key for this option."), y=dp(24),
+ pos_hint={"center_x": 0.5}, size_hint_x=0.5).open()
+ return
+
+ if not issubclass(self.option, OptionList):
+ if any(self.input.text == child.text.text for child in self.scrollbox.layout.children):
+ MDSnackbar(MDSnackbarText(text="This value is already in the set."), y=dp(24),
+ pos_hint={"center_x": 0.5}, size_hint_x=0.5).open()
+ return
+
+ self.add_set_item(self.input.text)
+ self.input.set_text(self.input, "")
+
+ def remove_item(self, button: MDIconButton):
+ list_item = button.parent
+ self.scrollbox.layout.remove_widget(list_item)
+
+ def add_set_item(self, key: str, value: int | None = None):
+ text = MDListItemSupportingText(text=key, id="value")
+ if issubclass(self.option, OptionCounter):
+ value_txt = CounterItemValue(text=str(value) if value else "1")
+ item = MDListItem(text,
+ value_txt,
+ MDIconButton(icon="minus", on_release=self.remove_item), focus_behavior=False)
+ item.value = value_txt
+ else:
+ item = MDListItem(text, MDIconButton(icon="minus", on_release=self.remove_item), focus_behavior=False)
+ item.text = text
+ self.scrollbox.layout.add_widget(item)
+
+ def on_text(self, instance, value):
+ if not self.valid_keys:
+ return
+ if len(value) >= 3:
+ self.dropdown.items.clear()
+
+ def on_press(txt):
+ split_text = MarkupLabel(text=txt, markup=True).markup
+ self.input.set_text(self.input, "".join(text_frag for text_frag in split_text
+ if not text_frag.startswith("[")))
+ self.input.focus = True
+ self.dropdown.dismiss()
+
+ lowered = value.lower()
+ for item_name in self.valid_keys:
+ try:
+ index = item_name.lower().index(lowered)
+ except ValueError:
+ pass # substring not found
+ else:
+ text = escape_markup(item_name)
+ text = text[:index] + "[b]" + text[index:index + len(value)] + "[/b]" + text[index + len(value):]
+ self.dropdown.items.append({
+ "text": text,
+ "on_release": lambda txt=text: on_press(txt),
+ "markup": True
+ })
+ if not self.dropdown.parent:
+ self.dropdown.open()
+ else:
+ self.dropdown.dismiss()
+
+
+class OptionsCreator(ThemedApp):
+ base_title: str = "Archipelago Options Creator"
+ container: ContainerLayout
+ main_layout: MainLayout
+ scrollbox: ScrollBox
+ main_panel: MainLayout
+ player_options: MainLayout
+ option_layout: MainLayout
+ name_input: ResizableTextField
+ game_label: MDLabel
+ current_game: str
+ options: typing.Dict[str, typing.Any]
+
+ def __init__(self):
+ self.title = self.base_title + " " + Utils.__version__
+ self.icon = r"data/icon.png"
+ self.current_game = ""
+ self.options = {}
+ super().__init__()
+
+ def export_options(self, button: Widget):
+ if 0 < len(self.name_input.text) < 17 and self.current_game:
+ file_name = Utils.save_filename("Export Options File As...", [("YAML", ["*.yaml"])],
+ Utils.get_file_safe_name(f"{self.name_input.text}.yaml"))
+ options = {
+ "name": self.name_input.text,
+ "description": f"YAML generated by Archipelago {Utils.__version__}.",
+ "game": self.current_game,
+ self.current_game: {k: check_random(v) for k, v in self.options.items()}
+ }
+ try:
+ with open(file_name, 'w') as f:
+ f.write(Utils.dump(options, sort_keys=False))
+ f.close()
+ MDSnackbar(MDSnackbarText(text="File saved successfully."), y=dp(24), pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ except FileNotFoundError:
+ MDSnackbar(MDSnackbarText(text="Saving cancelled."), y=dp(24), pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ elif not self.name_input.text:
+ MDSnackbar(MDSnackbarText(text="Name must not be empty."), y=dp(24), pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ elif not self.current_game:
+ MDSnackbar(MDSnackbarText(text="You must select a game to play."), y=dp(24), pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ else:
+ MDSnackbar(MDSnackbarText(text="Name cannot be longer than 16 characters."), y=dp(24),
+ pos_hint={"center_x": 0.5}, size_hint_x=0.5).open()
+
+ def create_range(self, option: typing.Type[Range], name: str):
+ def update_text(range_box: VisualRange):
+ self.options[name] = int(range_box.slider.value)
+ range_box.tag.text = str(int(range_box.slider.value))
+ return
+
+ box = VisualRange(option=option, name=name)
+ box.slider.bind(on_touch_move=lambda _, _1: update_text(box))
+ self.options[name] = option.default
+ return box
+
+ def create_named_range(self, option: typing.Type[NamedRange], name: str):
+ def set_to_custom(range_box: VisualNamedRange):
+ if (not self.options[name] == range_box.range.slider.value) \
+ and (not self.options[name] in option.special_range_names or
+ range_box.range.slider.value != option.special_range_names[self.options[name]]):
+ # we should validate the touch here,
+ # but this is much cheaper
+ self.options[name] = int(range_box.range.slider.value)
+ range_box.range.tag.text = str(int(range_box.range.slider.value))
+ set_button_text(range_box.choice, "Custom")
+
+ def set_button_text(button: MDButton, text: str):
+ button.text.text = text
+
+ def set_value(text: str, range_box: VisualNamedRange):
+ range_box.range.slider.value = min(max(option.special_range_names[text.lower()], option.range_start),
+ option.range_end)
+ range_box.range.tag.text = str(int(range_box.range.slider.value))
+ set_button_text(range_box.choice, text)
+ self.options[name] = text.lower()
+ range_box.range.slider.dropdown.dismiss()
+
+ def open_dropdown(button):
+ # for some reason this fixes an issue causing some to not open
+ box.range.slider.dropdown.open()
+
+ box = VisualNamedRange(option=option, name=name, range_widget=self.create_range(option, name))
+ if option.default in option.special_range_names:
+ # value can get mismatched in this case
+ box.range.slider.value = min(max(option.special_range_names[option.default], option.range_start),
+ option.range_end)
+ box.range.tag.text = str(int(box.range.slider.value))
+ box.range.slider.bind(on_touch_move=lambda _, _2: set_to_custom(box))
+ items = [
+ {
+ "text": choice.title(),
+ "on_release": lambda text=choice.title(): set_value(text, box)
+ }
+ for choice in option.special_range_names
+ ]
+ box.range.slider.dropdown = MDDropdownMenu(caller=box.choice, items=items)
+ box.choice.bind(on_release=open_dropdown)
+ self.options[name] = option.default
+ return box
+
+ def create_free_text(self, option: typing.Type[FreeText] | typing.Type[TextChoice], name: str):
+ text = VisualFreeText(option=option, name=name)
+
+ def set_value(instance):
+ self.options[name] = instance.text
+
+ text.bind(on_text_validate=set_value)
+ return text
+
+ def create_choice(self, option: typing.Type[Choice], name: str):
+ def set_button_text(button: VisualChoice, text: str):
+ button.text.text = text
+
+ def set_value(text, value):
+ set_button_text(main_button, text)
+ self.options[name] = value
+ dropdown.dismiss()
+
+ def open_dropdown(button):
+ # for some reason this fixes an issue causing some to not open
+ dropdown.open()
+
+ default_string = isinstance(option.default, str)
+ main_button = VisualChoice(option=option, name=name)
+ main_button.bind(on_release=open_dropdown)
+
+ items = [
+ {
+ "text": option.get_option_name(choice),
+ "on_release": lambda val=choice: set_value(option.get_option_name(val), option.name_lookup[val])
+ }
+ for choice in option.name_lookup
+ ]
+ dropdown = MDDropdownMenu(caller=main_button, items=items)
+ self.options[name] = option.name_lookup[option.default] if not default_string else option.default
+ return main_button
+
+ def create_text_choice(self, option: typing.Type[TextChoice], name: str):
+ def set_button_text(button: MDButton, text: str):
+ for child in button.children:
+ if isinstance(child, MDButtonText):
+ child.text = text
+
+ box = VisualTextChoice(option=option, name=name, choice=self.create_choice(option, name),
+ text=self.create_free_text(option, name))
+
+ def set_value(instance):
+ set_button_text(box.choice, "Custom")
+ self.options[name] = instance.text
+
+ box.text.bind(on_text_validate=set_value)
+ return box
+
+ def create_toggle(self, option: typing.Type[Toggle], name: str) -> Widget:
+ def set_value(instance: MDIconButton):
+ if instance.icon == "checkbox-outline":
+ instance.icon = "checkbox-blank-outline"
+ else:
+ instance.icon = "checkbox-outline"
+ self.options[name] = bool(not self.options[name])
+
+ self.options[name] = bool(option.default)
+ checkbox = VisualToggle(option=option, name=name)
+ checkbox.button.bind(on_release=set_value)
+
+ return checkbox
+
+ def create_popup(self, option: typing.Type[OptionList] | typing.Type[OptionSet] | typing.Type[OptionCounter],
+ name: str, world: typing.Type[World]):
+
+ valid_keys = sorted(option.valid_keys)
+ if option.verify_item_name:
+ valid_keys += list(world.item_name_to_id.keys())
+ if option.verify_location_name:
+ valid_keys += list(world.location_name_to_id.keys())
+
+ if not issubclass(option, OptionCounter):
+ def apply_changes(button):
+ self.options[name].clear()
+ for list_item in dialog.scrollbox.layout.children:
+ self.options[name].append(getattr(list_item.text, "text"))
+ dialog.dismiss()
+ else:
+ def apply_changes(button):
+ self.options[name].clear()
+ for list_item in dialog.scrollbox.layout.children:
+ self.options[name][getattr(list_item.text, "text")] = int(getattr(list_item.value, "text"))
+ dialog.dismiss()
+
+ dialog = VisualListSetCounter(option=option, name=name, valid_keys=valid_keys)
+ dialog.ids.container.spacing = dp(30)
+ dialog.scrollbox.layout.theme_bg_color = "Custom"
+ dialog.scrollbox.layout.md_bg_color = self.theme_cls.surfaceContainerLowColor
+ dialog.scrollbox.layout.spacing = dp(5)
+ dialog.scrollbox.layout.padding = [0, dp(5), 0, 0]
+
+ if name not in self.options:
+ # convert from non-mutable to mutable
+ # We use list syntax even for sets, set behavior is enforced through GUI
+ if issubclass(option, OptionCounter):
+ self.options[name] = deepcopy(option.default)
+ else:
+ self.options[name] = sorted(option.default)
+
+ if issubclass(option, OptionCounter):
+ for value in sorted(self.options[name]):
+ dialog.add_set_item(value, self.options[name].get(value, None))
+ else:
+ for value in sorted(self.options[name]):
+ dialog.add_set_item(value)
+
+ dialog.save.bind(on_release=apply_changes)
+ dialog.open()
+
+ def create_option_set_list_counter(self, option: typing.Type[OptionList] | typing.Type[OptionSet] |
+ typing.Type[OptionCounter], name: str, world: typing.Type[World]):
+ main_button = MDButton(MDButtonText(text="Edit"), on_release=lambda x: self.create_popup(option, name, world))
+ return main_button
+
+ def create_option(self, option: typing.Type[Option], name: str, world: typing.Type[World]) -> Widget:
+ option_base = MDBoxLayout(orientation="vertical", size_hint_y=None, padding=[0, 0, dp(5), dp(5)])
+
+ tooltip = filter_tooltip(option.__doc__)
+ option_label = TooltipLabel(text=f"[ref=0|{tooltip}]{getattr(option, 'display_name', name)}")
+ label_box = MDBoxLayout(orientation="horizontal")
+ label_anchor = MDAnchorLayout(anchor_x="right", anchor_y="center")
+ label_anchor.add_widget(option_label)
+ label_box.add_widget(label_anchor)
+
+ option_base.add_widget(label_box)
+ if issubclass(option, NamedRange):
+ option_base.add_widget(self.create_named_range(option, name))
+ elif issubclass(option, Range):
+ option_base.add_widget(self.create_range(option, name))
+ elif issubclass(option, Toggle):
+ option_base.add_widget(self.create_toggle(option, name))
+ elif issubclass(option, TextChoice):
+ option_base.add_widget(self.create_text_choice(option, name))
+ elif issubclass(option, Choice):
+ option_base.add_widget(self.create_choice(option, name))
+ elif issubclass(option, FreeText):
+ option_base.add_widget(self.create_free_text(option, name))
+ elif any(issubclass(option, cls) for cls in (OptionSet, OptionList, OptionCounter)):
+ option_base.add_widget(self.create_option_set_list_counter(option, name, world))
+ else:
+ option_base.add_widget(MDLabel(text="This option isn't supported by the option creator.\n"
+ "Please edit your yaml manually to set this option."))
+
+ if option_can_be_randomized(option):
+ def randomize_option(instance: Widget, value: str):
+ value = value == "down"
+ if value:
+ self.options[name] = "random-" + str(self.options[name])
+ else:
+ self.options[name] = self.options[name].replace("random-", "")
+ if self.options[name].isnumeric() or self.options[name] in ("True", "False"):
+ self.options[name] = eval(self.options[name])
+
+ base_object = instance.parent.parent
+ label_object = instance.parent
+ for child in base_object.children:
+ if child is not label_object:
+ child.disabled = value
+
+ default_random = option.default == "random"
+ random_toggle = ToggleButton(MDButtonText(text="Random?"), size_hint_x=None, width=dp(100),
+ state="down" if default_random else "normal")
+ random_toggle.bind(state=randomize_option)
+ label_box.add_widget(random_toggle)
+ if default_random:
+ randomize_option(random_toggle, "down")
+
+ return option_base
+
+ def create_options_panel(self, world_button: WorldButton):
+ self.option_layout.clear_widgets()
+ self.options.clear()
+ cls: typing.Type[World] = world_button.world_cls
+
+ self.current_game = cls.game
+ if not cls.web.options_page:
+ self.current_game = "None"
+ return
+ elif isinstance(cls.web.options_page, str):
+ self.current_game = "None"
+ if validate_url(cls.web.options_page):
+ webbrowser.open(cls.web.options_page)
+ MDSnackbar(MDSnackbarText(text="Launching in default browser..."), y=dp(24), pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ world_button.state = "normal"
+ else:
+ # attach onto archipelago.gg and see if we pass
+ new_url = "https://archipelago.gg/" + cls.web.options_page
+ if validate_url(new_url):
+ webbrowser.open(new_url)
+ MDSnackbar(MDSnackbarText(text="Launching in default browser..."), y=dp(24),
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ else:
+ MDSnackbar(MDSnackbarText(text="Invalid options page, please report to world developer."), y=dp(24),
+ pos_hint={"center_x": 0.5},
+ size_hint_x=0.5).open()
+ world_button.state = "normal"
+ # else just fall through
+ else:
+ expansion_box = ScrollBox()
+ expansion_box.layout.orientation = "vertical"
+ expansion_box.layout.spacing = dp(3)
+ expansion_box.scroll_type = ["bars"]
+ expansion_box.do_scroll_x = False
+ group_names = ["Game Options", *(group.name for group in cls.web.option_groups)]
+ groups = {name: [] for name in group_names}
+ for name, option in cls.options_dataclass.type_hints.items():
+ group = next((group.name for group in cls.web.option_groups if option in group.options), "Game Options")
+ groups[group].append((name, option))
+
+ for group, options in groups.items():
+ options = [(name, option) for name, option in options
+ if name and option.visibility & Visibility.simple_ui]
+ if not options:
+ continue # Game Options can be empty if every other option is in another group
+ # Can also have an option group of options that should not render on simple ui
+ group_item = MDExpansionPanel(size_hint_y=None)
+ group_header = MDExpansionPanelHeader(MDListItem(MDListItemSupportingText(text=group),
+ TrailingPressedIconButton(icon="chevron-right",
+ on_release=lambda x,
+ item=group_item:
+ self.tap_expansion_chevron(
+ item, x)),
+ md_bg_color=self.theme_cls.surfaceContainerLowestColor,
+ theme_bg_color="Custom",
+ on_release=lambda x, item=group_item:
+ self.tap_expansion_chevron(item, x)))
+ group_content = MDExpansionPanelContent(orientation="vertical", theme_bg_color="Custom",
+ md_bg_color=self.theme_cls.surfaceContainerLowestColor,
+ padding=[dp(12), dp(100), dp(12), 0],
+ spacing=dp(3))
+ group_item.add_widget(group_header)
+ group_item.add_widget(group_content)
+ group_box = ScrollBox()
+ group_box.layout.orientation = "vertical"
+ group_box.layout.spacing = dp(3)
+ for name, option in options:
+ group_content.add_widget(self.create_option(option, name, cls))
+ expansion_box.layout.add_widget(group_item)
+ self.option_layout.add_widget(expansion_box)
+ self.game_label.text = f"Game: {self.current_game}"
+
+ @staticmethod
+ def tap_expansion_chevron(panel: MDExpansionPanel, chevron: TrailingPressedIconButton | MDListItem):
+ if isinstance(chevron, MDListItem):
+ chevron = next((child for child in chevron.ids.trailing_container.children
+ if isinstance(child, TrailingPressedIconButton)), None)
+ panel.open() if not panel.is_open else panel.close()
+ if chevron:
+ panel.set_chevron_down(
+ chevron
+ ) if not panel.is_open else panel.set_chevron_up(chevron)
+
+ def build(self):
+ self.set_colors()
+ self.options = {}
+ self.container = Builder.load_file(Utils.local_path("data/optionscreator.kv"))
+ self.root = self.container
+ self.main_layout = self.container.ids.main
+ self.scrollbox = self.container.ids.scrollbox
+
+ def world_button_action(world_btn: WorldButton):
+ if self.current_game != world_btn.world_cls.game:
+ old_button = next((button for button in self.scrollbox.layout.children
+ if button.world_cls.game == self.current_game), None)
+ if old_button:
+ old_button.state = "normal"
+ else:
+ world_btn.state = "down"
+ self.create_options_panel(world_btn)
+
+ for world, cls in sorted(AutoWorldRegister.world_types.items(), key=lambda x: x[0]):
+ if world == "Archipelago":
+ continue
+ world_text = MDButtonText(text=world, size_hint_y=None, width=dp(150),
+ pos_hint={"x": 0.03, "center_y": 0.5})
+ world_text.text_size = (world_text.width, None)
+ world_text.bind(width=lambda *x, text=world_text: text.setter('text_size')(text, (text.width, None)),
+ texture_size=lambda *x, text=world_text: text.setter("height")(text,
+ world_text.texture_size[1]))
+ world_button = WorldButton(world_text, size_hint_x=None, width=dp(150), theme_width="Custom",
+ radius=(dp(5), dp(5), dp(5), dp(5)))
+ world_button.bind(on_release=world_button_action)
+ world_button.world_cls = cls
+ self.scrollbox.layout.add_widget(world_button)
+ self.main_panel = self.container.ids.player_layout
+ self.player_options = self.container.ids.player_options
+ self.game_label = self.container.ids.game
+ self.name_input = self.container.ids.player_name
+ self.option_layout = self.container.ids.options
+
+ def set_height(instance, value):
+ instance.height = value[1]
+
+ self.game_label.bind(texture_size=set_height)
+
+ # Uncomment to re-enable the Kivy console/live editor
+ # Ctrl-E to enable it, make sure numlock/capslock is disabled
+ # from kivy.modules.console import create_console
+ # from kivy.core.window import Window
+ # create_console(Window, self.container)
+
+ return self.container
+
+
+def launch():
+ OptionsCreator().run()
+
+
+if __name__ == "__main__":
+ Utils.init_logging("OptionsCreator")
+ launch()
diff --git a/README.md b/README.md
index fa87190565dd..608af1313c2f 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,7 @@ Currently, the following games are supported:
* Paint
* Celeste (Open World)
* Choo-Choo Charles
+* APQuest
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/Utils.py b/Utils.py
index 7978b229a4c9..b8c1b93c9b9f 100644
--- a/Utils.py
+++ b/Utils.py
@@ -49,7 +49,7 @@ def as_simple_string(self) -> str:
return ".".join(str(item) for item in self)
-__version__ = "0.6.4"
+__version__ = "0.6.5"
version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux")
@@ -315,12 +315,8 @@ def get_public_ipv6() -> str:
return ip
-OptionsType = Settings # TODO: remove when removing get_options
-
-
def get_options() -> Settings:
- # TODO: switch to Utils.deprecate after 0.4.4
- warnings.warn("Utils.get_options() is deprecated. Use the settings API instead.", DeprecationWarning)
+ deprecate("Utils.get_options() is deprecated. Use the settings API instead.")
return get_settings()
@@ -760,6 +756,11 @@ def _mp_open_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args:
res.put(open_filename(*args))
+def _mp_save_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args: Any) -> None:
+ if is_kivy_running():
+ raise RuntimeError("kivy should not be running in multiprocess")
+ res.put(save_filename(*args))
+
def _run_for_stdout(*args: str):
env = os.environ
if "LD_LIBRARY_PATH" in env:
@@ -810,6 +811,51 @@ def open_filename(title: str, filetypes: typing.Iterable[typing.Tuple[str, typin
initialfile=suggest or None)
+def save_filename(title: str, filetypes: typing.Iterable[typing.Tuple[str, typing.Iterable[str]]], suggest: str = "") \
+ -> typing.Optional[str]:
+ logging.info(f"Opening file save dialog for {title}.")
+
+ def run(*args: str):
+ return subprocess.run(args, capture_output=True, text=True).stdout.split("\n", 1)[0] or None
+
+ if is_linux:
+ # prefer native dialog
+ from shutil import which
+ kdialog = which("kdialog")
+ if kdialog:
+ k_filters = '|'.join((f'{text} (*{" *".join(ext)})' for (text, ext) in filetypes))
+ return run(kdialog, f"--title={title}", "--getsavefilename", suggest or ".", k_filters)
+ zenity = which("zenity")
+ if zenity:
+ z_filters = (f'--file-filter={text} ({", ".join(ext)}) | *{" *".join(ext)}' for (text, ext) in filetypes)
+ selection = (f"--filename={suggest}",) if suggest else ()
+ return run(zenity, f"--title={title}", "--file-selection", "--save", *z_filters, *selection)
+
+ # fall back to tk
+ try:
+ import tkinter
+ import tkinter.filedialog
+ except Exception as e:
+ logging.error('Could not load tkinter, which is likely not installed. '
+ f'This attempt was made because save_filename was used for "{title}".')
+ raise e
+ else:
+ if is_macos and is_kivy_running():
+ # on macOS, mixing kivy and tk does not work, so spawn a new process
+ # FIXME: performance of this is pretty bad, and we should (also) look into alternatives
+ from multiprocessing import Process, Queue
+ res: "Queue[typing.Optional[str]]" = Queue()
+ Process(target=_mp_save_filename, args=(res, title, filetypes, suggest)).start()
+ return res.get()
+ try:
+ root = tkinter.Tk()
+ except tkinter.TclError:
+ return None # GUI not available. None is the same as a user clicking "cancel"
+ root.withdraw()
+ return tkinter.filedialog.asksaveasfilename(title=title, filetypes=((t[0], ' '.join(t[1])) for t in filetypes),
+ initialfile=suggest or None)
+
+
def _mp_open_directory(res: "multiprocessing.Queue[typing.Optional[str]]", *args: Any) -> None:
if is_kivy_running():
raise RuntimeError("kivy should not be running in multiprocess")
diff --git a/WebHostLib/api/tracker.py b/WebHostLib/api/tracker.py
index 36692af42652..1e6e02bee778 100644
--- a/WebHostLib/api/tracker.py
+++ b/WebHostLib/api/tracker.py
@@ -58,6 +58,12 @@ class PlayerLocationsTotal(TypedDict):
total_locations: int
+class PlayerGame(TypedDict):
+ team: int
+ player: int
+ game: str
+
+
@api_endpoints.route("/tracker/")
@cache.memoize(timeout=60)
def tracker_data(tracker: UUID) -> dict[str, Any]:
@@ -80,7 +86,8 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
"""Slot aliases of all players."""
for team, players in all_players.items():
for player in players:
- player_aliases.append({"team": team, "player": player, "alias": tracker_data.get_player_alias(team, player)})
+ player_aliases.append(
+ {"team": team, "player": player, "alias": tracker_data.get_player_alias(team, player)})
player_items_received: list[PlayerItemsReceived] = []
"""Items received by each player."""
@@ -94,7 +101,8 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
for team, players in all_players.items():
for player in players:
player_checks_done.append(
- {"team": team, "player": player, "locations": sorted(tracker_data.get_player_checked_locations(team, player))})
+ {"team": team, "player": player,
+ "locations": sorted(tracker_data.get_player_checked_locations(team, player))})
total_checks_done: list[TeamTotalChecks] = [
{"team": team, "checks_done": checks_done}
@@ -144,7 +152,8 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
"""The current client status for each player."""
for team, players in all_players.items():
for player in players:
- player_status.append({"team": team, "player": player, "status": tracker_data.get_player_client_status(team, player)})
+ player_status.append(
+ {"team": team, "player": player, "status": tracker_data.get_player_client_status(team, player)})
return {
"aliases": player_aliases,
@@ -207,12 +216,20 @@ def static_tracker_data(tracker: UUID) -> dict[str, Any]:
player_locations_total.append(
{"team": team, "player": player, "total_locations": len(tracker_data.get_player_locations(player))})
+ player_game: list[PlayerGame] = []
+ """The played game per player slot."""
+ for team, players in all_players.items():
+ for player in players:
+ player_game.append({"team": team, "player": player, "game": tracker_data.get_player_game(player)})
+
return {
"groups": groups,
"datapackage": tracker_data._multidata["datapackage"],
"player_locations_total": player_locations_total,
+ "player_game": player_game,
}
+
# It should be exceedingly rare that slot data is needed, so it's separated out.
@api_endpoints.route("/slot_data_tracker/")
@cache.memoize(timeout=300)
diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py
index b36b19aff87e..d0fc96246b7a 100644
--- a/WebHostLib/misc.py
+++ b/WebHostLib/misc.py
@@ -1,5 +1,7 @@
import datetime
import os
+import warnings
+from enum import StrEnum
from typing import Any, IO, Dict, Iterator, List, Tuple, Union
import jinja2.exceptions
@@ -13,11 +15,25 @@
from .models import Seed, Room, Command, UUID, uuid4
from Utils import title_sorted
+class WebWorldTheme(StrEnum):
+ DIRT = "dirt"
+ GRASS = "grass"
+ GRASS_FLOWERS = "grassFlowers"
+ ICE = "ice"
+ JUNGLE = "jungle"
+ OCEAN = "ocean"
+ PARTY_TIME = "partyTime"
+ STONE = "stone"
def get_world_theme(game_name: str) -> str:
- if game_name in AutoWorldRegister.world_types:
- return AutoWorldRegister.world_types[game_name].web.theme
- return 'grass'
+ if game_name not in AutoWorldRegister.world_types:
+ return "grass"
+ chosen_theme = AutoWorldRegister.world_types[game_name].web.theme
+ available_themes = [theme.value for theme in WebWorldTheme]
+ if chosen_theme not in available_themes:
+ warnings.warn(f"Theme '{chosen_theme}' for {game_name} not valid, switching to default 'grass' theme.")
+ return "grass"
+ return chosen_theme
def get_visible_worlds() -> dict[str, type(World)]:
diff --git a/WebHostLib/options.py b/WebHostLib/options.py
index 02625366a63f..9e24ca34a86f 100644
--- a/WebHostLib/options.py
+++ b/WebHostLib/options.py
@@ -13,6 +13,7 @@
from worlds.AutoWorld import AutoWorldRegister
from . import app, cache
from .generate import get_meta
+from .misc import get_world_theme
def create() -> None:
@@ -22,12 +23,6 @@ def create() -> None:
Options.generate_yaml_templates(yaml_folder)
-def get_world_theme(game_name: str) -> str:
- if game_name in AutoWorldRegister.world_types:
- return AutoWorldRegister.world_types[game_name].web.theme
- return 'grass'
-
-
def render_options_page(template: str, world_name: str, is_complex: bool = False) -> Union[Response, str]:
world = AutoWorldRegister.world_types[world_name]
if world.hidden or world.web.options_page is False:
diff --git a/WebHostLib/static/assets/faq/en.md b/WebHostLib/static/assets/faq/en.md
index 588750065666..5b42cc6fc6c5 100644
--- a/WebHostLib/static/assets/faq/en.md
+++ b/WebHostLib/static/assets/faq/en.md
@@ -23,7 +23,7 @@ players to rely upon each other to complete their game.
While a multiworld game traditionally requires all players to be playing the same game, a multi-game multiworld allows
players to randomize any of the supported games, and send items between them. This allows players of different
games to interact with one another in a single multiplayer environment. Archipelago supports multi-game multiworlds.
-Here is a list of our [Supported Games](https://archipelago.gg/games).
+Here is a list of our [Supported Games](/games).
## Can I generate a single-player game with Archipelago?
@@ -33,7 +33,7 @@ play, open the Settings Page, pick your settings, and click Generate Game.
## How do I get started?
-We have a [Getting Started](https://archipelago.gg/tutorial/Archipelago/setup/en) guide that will help you get the
+We have a [Getting Started](/tutorial/Archipelago/setup/en) guide that will help you get the
software set up. You can use that guide to learn how to generate multiworlds. There are also basic instructions for
including multiple games, and hosting multiworlds on the website for ease and convenience.
@@ -57,7 +57,7 @@ their multiworld.
If a player must leave early, they can use Archipelago's release system. When a player releases their game, all items
in that game belonging to other players are sent out automatically. This allows other players to continue to play
-uninterrupted. Here is a list of all of our [Server Commands](https://archipelago.gg/tutorial/Archipelago/commands/en).
+uninterrupted. Here is a list of all of our [Server Commands](/tutorial/Archipelago/commands/en).
## What happens if an item is placed somewhere it is impossible to get?
diff --git a/WebHostLib/static/styles/sc2Tracker.css b/WebHostLib/static/styles/sc2Tracker.css
index 3048213e43cb..28a4e7bc8a22 100644
--- a/WebHostLib/static/styles/sc2Tracker.css
+++ b/WebHostLib/static/styles/sc2Tracker.css
@@ -241,12 +241,9 @@ input[type="checkbox"]{
}
/* Hidden items */
-.hidden-class:not(:has(img.acquired)){
+.hidden-class:not(:has(.f:not(.unacquired))), .hidden-item{
display: none;
}
-.hidden-item:not(.acquired){
- display:none;
-}
/* Keys */
#keys ol, #keys ul{
diff --git a/WebHostLib/templates/tracker__Starcraft2.html b/WebHostLib/templates/tracker__Starcraft2.html
index 932f21505d16..b7cc8a29c698 100644
--- a/WebHostLib/templates/tracker__Starcraft2.html
+++ b/WebHostLib/templates/tracker__Starcraft2.html
@@ -24,55 +24,55 @@
Filler Items
-
+
+{{minerals_count}}
-
+
+{{vespene_count}}
-
+
+{{supply_count}}
-
+
+{{max_supply_count}}
-
+
-{{reduced_supply_count}}
-
+
{{construction_speed_count}}
-
+
{{shield_regen_count}}
-
+
{{upgrade_speed_count}}
-
+
{{research_cost_count}}
@@ -116,133 +116,133 @@
Terran Items
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -250,155 +250,155 @@
Terran Items
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
+
+
+
@@ -406,156 +406,156 @@
Terran Items
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
+
@@ -563,74 +563,74 @@
Terran Items
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -638,94 +638,94 @@
Terran Items
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -734,96 +734,96 @@
Terran Items
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -871,133 +871,133 @@
Zerg Items
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
@@ -1005,47 +1005,47 @@
Zerg Items
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
@@ -1053,116 +1053,116 @@
Zerg Items
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
@@ -1170,75 +1170,75 @@
Zerg Items
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
@@ -1246,59 +1246,59 @@
Zerg Items
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -1306,9 +1306,9 @@
Zerg Items
-
-
-
+
+
+
@@ -1316,23 +1316,23 @@
Zerg Items
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -1341,64 +1341,64 @@
Zerg Items
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -1438,26 +1438,26 @@
Protoss Items
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1465,46 +1465,46 @@
Protoss Items
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
@@ -1512,38 +1512,38 @@
Protoss Items
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
@@ -1551,59 +1551,59 @@
Protoss Items
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -1611,9 +1611,9 @@
Protoss Items
-
-
-
+
+
+
@@ -1621,49 +1621,49 @@
Protoss Items
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
@@ -1671,10 +1671,10 @@
Protoss Items
-
-
-
-
+
+
+
+
@@ -1682,18 +1682,18 @@
Protoss Items
-
+
-
+
-
+
-
+
@@ -1701,96 +1701,96 @@
Protoss Items
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
@@ -1798,26 +1798,26 @@
Protoss Items
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1825,57 +1825,57 @@
Protoss Items
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
@@ -1883,49 +1883,49 @@
Protoss Items
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
@@ -1933,33 +1933,33 @@
Protoss Items
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1967,53 +1967,53 @@
Protoss Items
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
@@ -2021,16 +2021,16 @@
Protoss Items
-
-
-
+
+
+
-
-
-
+
+
+
@@ -2039,83 +2039,83 @@
Protoss Items
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
@@ -2129,44 +2129,44 @@
Nova Items
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
+
@@ -2178,41 +2178,41 @@
Kerrigan Items
-
+ {{kerrigan_level}}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py
index ead679fd980b..d1471aa6580a 100644
--- a/WebHostLib/tracker.py
+++ b/WebHostLib/tracker.py
@@ -959,7 +959,7 @@ def render_Timespinner_tracker(tracker_data: TrackerData, team: int, player: int
timespinner_location_ids = {
"Present": list(range(1337000, 1337085)),
- "Past": list(range(1337086, 1337175)),
+ "Past": list(range(1337086, 1337157)) + list(range(1337159, 1337175)),
"Ancient Pyramid": [
1337236,
1337246, 1337247, 1337248, 1337249]
@@ -1228,7 +1228,7 @@ def render_ChecksFinder_tracker(tracker_data: TrackerData, team: int, player: in
def render_Starcraft2_tracker(tracker_data: TrackerData, team: int, player: int) -> str:
SC2WOL_ITEM_ID_OFFSET = 1000
SC2HOTS_ITEM_ID_OFFSET = 2000
- SC2LOTV_ITEM_ID_OFFSET = 2000
+ SC2LOTV_ITEM_ID_OFFSET = 3000
SC2_KEY_ITEM_ID_OFFSET = 4000
NCO_LOCATION_ID_LOW = 20004500
NCO_LOCATION_ID_HIGH = NCO_LOCATION_ID_LOW + 1000
diff --git a/Zelda1Client.py b/Zelda1Client.py
index 6dd7a361658c..b4292ecf7539 100644
--- a/Zelda1Client.py
+++ b/Zelda1Client.py
@@ -289,7 +289,7 @@ async def nes_sync_task(ctx: ZeldaContext):
if not ctx.auth:
ctx.auth = ''.join([chr(i) for i in data_decoded['playerName'] if i != 0])
if ctx.auth == '':
- logger.info("Invalid ROM detected. No player name built into the ROM. Please regenerate"
+ logger.info("Invalid ROM detected. No player name built into the ROM. Please regenerate "
"the ROM using the same link but adding your slot name")
if ctx.awaiting_rom:
await ctx.server_auth(False)
diff --git a/ci-requirements.txt b/ci-requirements.txt
new file mode 100644
index 000000000000..80a1c783660e
--- /dev/null
+++ b/ci-requirements.txt
@@ -0,0 +1,2 @@
+pytest>=9.0.1,<10 # this includes subtests support
+pytest-xdist>=3.8.0
diff --git a/data/client.kv b/data/client.kv
index 08f4c8d71831..cf8e88446d2c 100644
--- a/data/client.kv
+++ b/data/client.kv
@@ -224,6 +224,7 @@
height: self.content.texture_size[1] + 80
:
layout: layout
+ box_height: dp(100)
bar_width: "12dp"
scroll_wheel_distance: 40
do_scroll_x: False
@@ -234,4 +235,11 @@
orientation: "vertical"
spacing: 10
size_hint_y: None
- height: self.minimum_height
+ height: max(self.minimum_height, root.box_height)
+
+:
+ valign: "middle"
+ halign: "center"
+ text_size: self.width, None
+ height: self.texture_size[1]
+
diff --git a/data/optionscreator.kv b/data/optionscreator.kv
new file mode 100644
index 000000000000..4c0cc8178264
--- /dev/null
+++ b/data/optionscreator.kv
@@ -0,0 +1,174 @@
+:
+ id: this
+ spacing: 15
+ orientation: "horizontal"
+ slider: slider
+ tag: tag
+ MDLabel:
+ id: tag
+ text: str(this.option.default) if not isinstance(this.option.default, str) else str(this.option.range_start)
+ MDSlider:
+ id: slider
+ min: this.option.range_start
+ max: this.option.range_end
+ value: min(max(this.option.default, this.option.range_start), this.option.range_end) if not isinstance(this.option.default, str) else this.option.range_start
+ step: 1
+ step_point_size: 0
+ MDSliderHandle:
+
+ MDSliderValueLabel:
+
+:
+ id: this
+ text: text
+ MDButtonText:
+ id: text
+ text: this.option.get_option_name(this.option.default if not isinstance(this.option.default, str) else list(this.option.options.values())[0])
+ theme_text_color: "Primary"
+
+:
+ id: this
+ orientation: "horizontal"
+ spacing: "10dp"
+ padding: (0, 0, "10dp", 0)
+ choice: choice
+
+ MDButton:
+ id: choice
+ text: text
+ MDButtonText:
+ id: text
+ text: this.option.default.title() if this.option.default in this.option.special_range_names else "Custom"
+
+:
+ multiline: False
+ font_size: "15sp"
+ text: self.option.default if isinstance(self.option.default, str) else ""
+ theme_height: "Custom"
+ height: "30dp"
+
+
+:
+ id: this
+ orientation: "horizontal"
+ spacing: "5dp"
+ padding: (0, 0, "10dp", 0)
+
+:
+ id: this
+ button: button
+ MDIconButton:
+ id: button
+ icon: "checkbox-outline" if this.option.default else "checkbox-blank-outline"
+
+:
+ height: "20dp"
+
+:
+ height: "30dp"
+
+:
+ id: this
+ scrollbox: scrollbox
+ add: add
+ save: save
+ input: input
+ focus_behavior: False
+
+ MDDialogHeadlineText:
+ text: getattr(this.option, "display_name", this.name)
+
+ MDDialogSupportingText:
+ text: "Add or Remove Entries"
+
+ MDDialogContentContainer:
+ orientation: "vertical"
+ spacing: 10
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ VisualListSetEntry:
+ id: input
+ height: "20dp"
+
+ MDIconButton:
+ id: add
+ icon: "plus"
+ theme_height: "Custom"
+ height: "20dp"
+ on_press: root.validate_add(input)
+
+ ScrollBox:
+ id: scrollbox
+ size_hint_y: None
+ adapt_minimum: False
+
+ MDButton:
+ id: save
+ MDButtonText:
+ text: "Save Changes"
+
+ContainerLayout:
+ md_bg_color: app.theme_cls.backgroundColor
+
+ MainLayout:
+ id: main
+ cols: 3
+ padding: 3, 5, 0, 3
+ spacing: "2dp"
+
+ ScrollBox:
+ id: scrollbox
+ size_hint_x: None
+ width: "150dp"
+
+ MDDivider:
+ orientation: "vertical"
+ width: "4dp"
+
+ MainLayout:
+ id: player_layout
+ rows: 2
+ spacing: "20dp"
+
+ MDBoxLayout:
+ id: player_options
+ orientation: "horizontal"
+ height: "75dp"
+ size_hint_y: None
+ padding: ["10dp", "30dp", "10dp", 0]
+ spacing: "10dp"
+
+ ResizableTextField:
+ id: player_name
+ multiline: False
+
+ MDTextFieldHintText:
+ text: "Player Name"
+
+ MDTextFieldMaxLengthText:
+ max_text_length: 16
+
+ MDBoxLayout:
+ orientation: "vertical"
+ spacing: "15dp"
+
+ MDLabel:
+ id: game
+ text: "Game: None"
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+
+ MDButton:
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+ on_press: app.export_options(self)
+ theme_width: "Custom"
+ size_hint_y: 1
+ size_hint_x: 1
+
+ MDButtonText:
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+ text: "Export Options"
+
+ MainLayout:
+ cols: 1
+ id: options
diff --git a/deploy/example_config.yaml b/deploy/example_config.yaml
index d74f7f238fcf..f7b25d7b6abc 100644
--- a/deploy/example_config.yaml
+++ b/deploy/example_config.yaml
@@ -8,3 +8,7 @@ SELFLAUNCH: false
# Host Address. This is the address encoded into the patch that will be used for client auto-connect.
# Set as your local IP (192.168.x.x) to serve over LAN.
HOST_ADDRESS: localhost
+
+# Asset redistribution rights. If true, the host affirms they have been given explicit permission to redistribute
+# the proprietary assets in WebHostLib
+#ASSET_RIGHTS: false
diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS
index 7b8e48af142e..e4ef3fe73e28 100644
--- a/docs/CODEOWNERS
+++ b/docs/CODEOWNERS
@@ -15,6 +15,10 @@
# A Link to the Past
/worlds/alttp/ @Berserker66
+# APQuest
+# NewSoupVi is acting maintainer, but world belongs to core with the exception of the music
+/worlds/apquest/ @NewSoupVi
+
# Sudoku (APSudoku)
/worlds/apsudoku/ @EmilyV99
diff --git a/docs/apworld specification.md b/docs/apworld specification.md
index 7022e0ac3df2..8494aecd5578 100644
--- a/docs/apworld specification.md
+++ b/docs/apworld specification.md
@@ -44,9 +44,9 @@ These get automatically added to the `archipelago.json` of an .apworld if it is
["Build apworlds" launcher component](#build-apworlds-launcher-component),
which is the correct way to package your `.apworld` as a world developer. Do not write these fields yourself.
-### "Build apworlds" Launcher Component
+### "Build APWorlds" Launcher Component
-In the Archipelago Launcher, there is a "Build apworlds" component that will package all world folders to `.apworld`,
+In the Archipelago Launcher, there is a "Build APWorlds" component that will package all world folders to `.apworld`,
and add `archipelago.json` manifest files to them.
These .apworld files will be output to `build/apworlds` (relative to the Archipelago root directory).
The `archipelago.json` file in each .apworld will automatically include the appropriate
diff --git a/docs/network protocol.md b/docs/network protocol.md
index c3fc578a896c..0f6a246c1d7f 100644
--- a/docs/network protocol.md
+++ b/docs/network protocol.md
@@ -647,6 +647,16 @@ class Version(NamedTuple):
build: int
```
+If constructing version information as a dict for a custom client rather than as a NamedTuple built into the CommonClient, you must add the `class` key to allow Archipelago to compare version support.
+```
+"version": {
+ "class": "Version",
+ "build": X,
+ "major": Y,
+ "minor": Z
+ }
+```
+
### SlotType
An enum representing the nature of a slot.
diff --git a/docs/options api.md b/docs/options api.md
index 193d3953426a..7234d1cc4980 100644
--- a/docs/options api.md
+++ b/docs/options api.md
@@ -269,7 +269,8 @@ placed on them.
### PriorityLocations
Marks locations given here as `LocationProgressType.Priority` forcing progression items on them if any are available in
-the pool.
+the pool. Progression items without a deprioritized flag will be used first when filling priority_locations. Progression items with
+a deprioritized flag will be used next.
### ItemLinks
Allows users to share their item pool with other players. Currently item links are per game. A link of one game between
diff --git a/docs/webhost api.md b/docs/webhost api.md
index 048211348404..1229704b9c6a 100644
--- a/docs/webhost api.md
+++ b/docs/webhost api.md
@@ -385,6 +385,8 @@ Will provide a dict of static tracker data with the following keys:
- This hash can then be sent to the datapackage API to receive the appropriate datapackage as necessary
- The number of checks found vs. total checks available per player (`player_locations_total`)
- Same logic as the multitracker template: found = len(player_checks_done.locations) / total = player_locations_total.total_locations (all available checks).
+- The game each player is playing (`player_game`)
+ - Provided as a list of objects with `team`, `player`, and `game`.
Example:
```json
@@ -409,10 +411,10 @@ Example:
],
"datapackage": {
"Archipelago": {
- "checksum": "ac9141e9ad0318df2fa27da5f20c50a842afeecb",
+ "checksum": "ac9141e9ad0318df2fa27da5f20c50a842afeecb"
},
"The Messenger": {
- "checksum": "6991cbcda7316b65bcb072667f3ee4c4cae71c0b",
+ "checksum": "6991cbcda7316b65bcb072667f3ee4c4cae71c0b"
}
},
"player_locations_total": [
@@ -427,6 +429,18 @@ Example:
"total_locations": 20
}
],
+ "player_game": [
+ {
+ "team": 0,
+ "player": 1,
+ "game": "Archipelago"
+ },
+ {
+ "team": 0,
+ "player": 2,
+ "game": "The Messenger"
+ }
+ ]
}
```
diff --git a/entrance_rando.py b/entrance_rando.py
index 578059ae6f5b..a417767036ee 100644
--- a/entrance_rando.py
+++ b/entrance_rando.py
@@ -525,7 +525,7 @@ def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
running_time = time.perf_counter() - start_time
if running_time > 1.0:
- logging.info(f"Took {running_time:.4f} seconds during entrance randomization for player {world.player},"
+ logging.info(f"Took {running_time:.4f} seconds during entrance randomization for player {world.player}, "
f"named {world.multiworld.player_name[world.player]}")
return er_state
diff --git a/kvui.py b/kvui.py
index 1989dd164a63..b0091b5a200e 100644
--- a/kvui.py
+++ b/kvui.py
@@ -35,6 +35,17 @@
Config.set("kivy", "exit_on_escape", "0")
Config.set("graphics", "multisamples", "0") # multisamples crash old intel drivers
+# Workaround for Kivy issue #9226.
+# caused by kivy by default using probesysfs,
+# which assumes all multi touch deviecs are touch screens.
+# workaround provided by Snu of the kivy commmunity c:
+from kivy.utils import platform
+if platform == "linux":
+ options = Config.options("input")
+ for option in options:
+ if Config.get("input", option) == "probesysfs":
+ Config.remove_option("input", option)
+
# Workaround for an issue where importing kivy.core.window before loading sounds
# will hang the whole application on Linux once the first sound is loaded.
# kivymd imports kivy.core.window, so we have to do this before the first kivymd import.
@@ -127,7 +138,7 @@ def __init__(self, **kwargs):
val = kwargs.pop(kwarg, "None")
if val != "None":
image_args[kwarg.replace("image_", "")] = val
- super().__init__()
+ super().__init__(**kwargs)
self.image = ApAsyncImage(**image_args)
def set_center(button, center):
@@ -143,6 +154,7 @@ def add_widget(self, widget, index=0, canvas=None):
class ScrollBox(MDScrollView):
layout: MDBoxLayout = ObjectProperty(None)
+ box_height: int = NumericProperty(dp(100))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -153,6 +165,7 @@ class ToggleButton(MDButton, ToggleButtonBehavior):
def __init__(self, *args, **kwargs):
super(ToggleButton, self).__init__(*args, **kwargs)
self.bind(state=self._update_bg)
+ self._update_bg(self, self.state)
def _update_bg(self, _, state: str):
if self.disabled:
@@ -170,7 +183,7 @@ def _update_bg(self, _, state: str):
child.text_color = self.theme_cls.onPrimaryColor
child.icon_color = self.theme_cls.onPrimaryColor
else:
- self.md_bg_color = self.theme_cls.surfaceContainerLowestColor
+ self.md_bg_color = self.theme_cls.surfaceContainerLowColor
for child in self.children:
if child.theme_text_color == "Primary":
child.theme_text_color = "Custom"
@@ -184,7 +197,6 @@ def _update_bg(self, _, state: str):
class ResizableTextField(MDTextField):
"""
Resizable MDTextField that manually overrides the builtin sizing.
-
Note that in order to use this, the sizing must be specified from within a .kv rule.
"""
def __init__(self, *args, **kwargs):
@@ -248,7 +260,7 @@ def on_cursor_leave(self, *args):
class ToolTip(MDTooltipPlain):
- pass
+ markup = True
class ServerToolTip(ToolTip):
@@ -283,6 +295,8 @@ def create_tooltip(self, text, x, y):
def on_mouse_pos(self, window, pos):
if not self.get_root_window():
return # Abort if not displayed
+ if self.disabled:
+ return
super().on_mouse_pos(window, pos)
if self.refs and self.hovered:
diff --git a/requirements.txt b/requirements.txt
index e455a5fd61f9..de01f2749707 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,18 +1,18 @@
colorama>=0.4.6
websockets>=13.0.1,<14
-PyYAML>=6.0.2
-jellyfish>=1.1.3
+PyYAML>=6.0.3
+jellyfish>=1.2.1
jinja2>=3.1.6
-schema>=0.7.7
+schema>=0.7.8
kivy>=2.3.1
bsdiff4>=1.2.6
-platformdirs>=4.3.6
-certifi>=2025.4.26
-cython>=3.0.12
-cymem>=2.0.11
-orjson>=3.10.15
-typing_extensions>=4.12.2
-requests~=2.32.0
-pyshortcuts>=1.9.1
+platformdirs>=4.5.0
+certifi>=2025.11.12
+cython>=3.2.1
+cymem>=2.0.13
+orjson>=3.11.4
+typing_extensions>=4.15.0
+pyshortcuts>=1.9.6
kivymd @ git+https://github.com/kivymd/KivyMD@5ff9d0d
kivymd>=2.0.1.dev0
+requests~=2.32.0
diff --git a/setup.py b/setup.py
index 05e677f0c74d..949b1e3e303b 100644
--- a/setup.py
+++ b/setup.py
@@ -394,11 +394,11 @@ def run(self) -> None:
manifest = json.load(manifest_file)
assert "game" in manifest, (
- f"World directory {world_directory} has an archipelago.json manifest file, but it"
+ f"World directory {world_directory} has an archipelago.json manifest file, but it "
"does not define a \"game\"."
)
assert manifest["game"] == worldtype.game, (
- f"World directory {world_directory} has an archipelago.json manifest file, but value of the"
+ f"World directory {world_directory} has an archipelago.json manifest file, but value of the "
f"\"game\" field ({manifest['game']} does not equal the World class's game ({worldtype.game})."
)
else:
diff --git a/test/general/test_options.py b/test/general/test_options.py
index f5111e91388d..cbd8d7b53335 100644
--- a/test/general/test_options.py
+++ b/test/general/test_options.py
@@ -44,19 +44,19 @@ def test_item_links_name_groups(self):
}],
[{
"name": "ItemLinkGroup",
- "item_pool": ["Hammer", "Bow"],
+ "item_pool": ["Hammer", "Sword"],
"link_replacement": False,
"replacement_item": None,
}]
]
# we really need some sort of test world but generic doesn't have enough items for this
- world = AutoWorldRegister.world_types["A Link to the Past"]
+ world = AutoWorldRegister.world_types["APQuest"]
plando_options = PlandoOptions.from_option_string("bosses")
item_links = [ItemLinks.from_any(item_link_groups[0]), ItemLinks.from_any(item_link_groups[1])]
for link in item_links:
link.verify(world, "tester", plando_options)
self.assertIn("Hammer", link.value[0]["item_pool"])
- self.assertIn("Bow", link.value[0]["item_pool"])
+ self.assertIn("Sword", link.value[0]["item_pool"])
# TODO test that the group created using these options has the items
diff --git a/test/general/test_player_options.py b/test/general/test_player_options.py
index ea7f19e3d917..7dd612f4f1db 100644
--- a/test/general/test_player_options.py
+++ b/test/general/test_player_options.py
@@ -37,3 +37,23 @@ def test_update_weights(self):
self.assertEqual(new_weights["dict_2"]["option_g"], 50)
self.assertEqual(len(new_weights["set_1"]), 2)
self.assertIn("option_d", new_weights["set_1"])
+
+ def test_update_dict_supports_negatives_and_zeroes(self):
+ original_options = {
+ "dict_1": {"a": 1, "b": -1},
+ "dict_2": {"a": 1, "b": -1},
+ }
+ new_weights = Generate.update_weights(
+ original_options,
+ {
+ "+dict_1": {"a": -2, "b": 2},
+ "-dict_2": {"a": 1, "b": 2},
+ },
+ "Tested",
+ "",
+ )
+ self.assertEqual(new_weights["dict_1"]["a"], -1)
+ self.assertEqual(new_weights["dict_1"]["b"], 1)
+ self.assertEqual(new_weights["dict_2"]["a"], 0)
+ self.assertEqual(new_weights["dict_2"]["b"], -3)
+ self.assertIn("a", new_weights["dict_2"])
diff --git a/test/hosting/__main__.py b/test/hosting/__main__.py
index 8f2a34a7a896..7ac2700eb8c6 100644
--- a/test/hosting/__main__.py
+++ b/test/hosting/__main__.py
@@ -70,13 +70,13 @@ def expect_equal(first: Any, second: Any, msg: str = "") -> None:
empty_file = str(Path(tempdir) / "empty")
open(empty_file, "w").close()
sys.argv += ["--config_override", empty_file] # tests #5541
- multis = [["VVVVVV"], ["Temp World"], ["VVVVVV", "Temp World"]]
+ multis = [["APQuest"], ["Temp World"], ["APQuest", "Temp World"]]
p1_games: list[str] = []
data_paths: list[Path | None] = []
rooms: list[str] = []
multidata: Path | None
- copy_world("VVVVVV", "Temp World")
+ copy_world("APQuest", "Temp World")
try:
for n, games in enumerate(multis, 1):
print(f"Generating [{n}] {', '.join(games)} offline")
diff --git a/test/hosting/world.py b/test/hosting/world.py
index 20f8df8cb1bb..9a523f6845ae 100644
--- a/test/hosting/world.py
+++ b/test/hosting/world.py
@@ -20,7 +20,7 @@ def copy(src: str, dst: str) -> None:
src_cls = AutoWorldRegister.world_types[src]
src_folder = Path(src_cls.__file__).parent
worlds_folder = src_folder.parent
- if (not src_cls.__file__.endswith("__init__.py") or not src_folder.is_dir()
+ if (not src_cls.__file__.endswith(("__init__.py", "world.py")) or not src_folder.is_dir()
or not (worlds_folder / "generic").is_dir()):
raise ValueError(f"Unsupported layout for copy_world from {src}")
dst_folder = worlds_folder / dst_folder_name
@@ -28,11 +28,14 @@ def copy(src: str, dst: str) -> None:
raise ValueError(f"Destination {dst_folder} already exists")
shutil.copytree(src_folder, dst_folder)
_new_worlds[dst] = str(dst_folder)
- with open(dst_folder / "__init__.py", "r", encoding="utf-8-sig") as f:
- contents = f.read()
- contents = re.sub(r'game\s*(:\s*[a-zA-Z\[\]]+)?\s*=\s*[\'"]' + re.escape(src) + r'[\'"]', f'game = "{dst}"', contents)
- with open(dst_folder / "__init__.py", "w", encoding="utf-8") as f:
- f.write(contents)
+
+ for potential_world_class_file in ("__init__.py", "world.py"):
+ with open(dst_folder / potential_world_class_file, "r", encoding="utf-8-sig") as f:
+ contents = f.read()
+ r_src = re.escape(src)
+ contents = re.sub(r'game\s*(:\s*[a-zA-Z\[\]]+)?\s*=\s*[\'"]' + r_src + r'[\'"]', f'game = "{dst}"', contents)
+ with open(dst_folder / "__init__.py", "w", encoding="utf-8") as f:
+ f.write(contents)
def delete(name: str) -> None:
diff --git a/test/programs/data/one_player/test.yaml b/test/programs/data/one_player/test.yaml
index 47d79cf636c1..a93493702750 100644
--- a/test/programs/data/one_player/test.yaml
+++ b/test/programs/data/one_player/test.yaml
@@ -2,8 +2,8 @@ description: Almost blank test yaml
name: Player{NUMBER}
game:
- Timespinner: 1 # what else
+ APQuest: 1 # what else
requires:
version: 0.2.6
-Timespinner: {}
+APQuest: {}
diff --git a/test/webhost/test_option_presets.py b/test/webhost/test_option_presets.py
index efacddb22e6a..7f05de5d114b 100644
--- a/test/webhost/test_option_presets.py
+++ b/test/webhost/test_option_presets.py
@@ -2,7 +2,7 @@
from BaseClasses import PlandoOptions
from worlds import AutoWorldRegister
-from Options import OptionCounter, NamedRange, NumericOption, OptionList, OptionSet
+from Options import OptionCounter, NamedRange, NumericOption, OptionList, OptionSet, Visibility
class TestOptionPresets(unittest.TestCase):
@@ -19,6 +19,9 @@ def test_option_presets_have_valid_options(self):
# pass in all plando options in case a preset wants to require certain plando options
# for some reason
option.verify(world_type, "Test Player", PlandoOptions(sum(PlandoOptions)))
+ if not (Visibility.complex_ui in option.visibility or Visibility.simple_ui in option.visibility):
+ self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' is not "
+ f"visible in any supported UI.")
supported_types = [NumericOption, OptionSet, OptionList, OptionCounter]
if not any([issubclass(option.__class__, t) for t in supported_types]):
self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' "
diff --git a/worlds/AutoSNIClient.py b/worlds/AutoSNIClient.py
index 98243ce3fc97..d2558bb98dc6 100644
--- a/worlds/AutoSNIClient.py
+++ b/worlds/AutoSNIClient.py
@@ -1,14 +1,26 @@
from __future__ import annotations
import abc
+from bisect import bisect_right
+from dataclasses import dataclass
+import enum
import logging
-from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union, TypeGuard
+from typing import (TYPE_CHECKING, Any, ClassVar, Dict, Generic, Iterable,
+ Optional, Sequence, Tuple, TypeGuard, TypeVar, Union)
+
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components
if TYPE_CHECKING:
from SNIClient import SNIContext
+SNES_READ_CHUNK_SIZE = 2048
+"""
+note: SNI v0.0.101 currently has a bug where reads from
+RetroArch >2048 bytes will only return the last ~2048 bytes read.
+https://github.com/alttpo/sni/issues/51
+"""
+
component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe"),
description="A client for connecting to SNES consoles via Super Nintendo Interface.")
components.append(component)
@@ -91,3 +103,119 @@ async def deathlink_kill_player(self, ctx: SNIContext) -> None:
def on_package(self, ctx: SNIContext, cmd: str, args: Dict[str, Any]) -> None:
""" override this with code to handle packages from the server """
pass
+
+
+@dataclass(frozen=True, slots=True, order=True)
+class Read:
+ """ snes memory read - address and size in bytes """
+ address: int
+ size: int
+
+
+@dataclass(frozen=True, slots=True)
+class _MemRead:
+ location: Read
+ data: bytes
+
+
+_T_Enum = TypeVar("_T_Enum", bound=enum.Enum)
+
+
+class SnesData(Generic[_T_Enum]):
+ _ranges: Sequence[_MemRead]
+ """ sorted by address """
+
+ def __init__(self, ranges: Sequence[tuple[Read, bytes]]) -> None:
+ self._ranges = [_MemRead(r, d) for r, d in ranges]
+
+ def get(self, read: _T_Enum) -> bytes:
+ assert isinstance(read.value, Read), read.value
+ address = read.value.address
+ index = bisect_right(self._ranges, address, key=lambda r: r.location.address) - 1
+ assert index >= 0, (self._ranges, read.value)
+ mem_read = self._ranges[index]
+ sub_index = address - mem_read.location.address
+ return mem_read.data[sub_index:sub_index + read.value.size]
+
+
+class SnesReader(Generic[_T_Enum]):
+ """
+ how to use:
+ ```
+ from enum import Enum
+ from worlds.AutoSNIClient import Read, SNIClient, SnesReader
+
+ class MyGameMemory(Enum):
+ game_mode = Read(WRAM_START + 0x0998, 1)
+ send_queue = Read(SEND_QUEUE_START, 8 * 127)
+ ...
+
+ snes_reader = SnesReader(MyGameMemory)
+
+ snes_data = await snes_reader.read(ctx)
+ if snes_data is None:
+ snes_logger.info("error reading from snes")
+ return
+
+ game_mode = snes_data.get(MyGameMemory.game_mode)
+ ```
+ """
+ _ranges: Sequence[Read]
+ """ sorted by address """
+
+ def __init__(self, reads: type[_T_Enum]) -> None:
+ self._ranges = self._make_ranges(reads)
+
+ @staticmethod
+ def _make_ranges(reads: type[enum.Enum]) -> Sequence[Read]:
+
+ unprocessed_reads: list[Read] = []
+ for e in reads:
+ assert isinstance(e.value, Read), (reads.__name__, e, e.value)
+ unprocessed_reads.append(e.value)
+ unprocessed_reads.sort()
+
+ ranges: list[Read] = []
+ for read in unprocessed_reads:
+ # v end of the previous range
+ if len(ranges) == 0 or read.address - (ranges[-1].address + ranges[-1].size) > 255:
+ ranges.append(read)
+ else: # combine with previous range
+ chunk_address = ranges[-1].address
+ assert read.address >= chunk_address, "sort() didn't work? or something"
+ original_chunk_size = ranges[-1].size
+ new_size = max((read.address + read.size) - chunk_address,
+ original_chunk_size)
+ ranges[-1] = Read(chunk_address, new_size)
+ logging.debug(f"{len(ranges)=} {max(r.size for r in ranges)=}")
+ return ranges
+
+ async def read(self, ctx: "SNIContext") -> SnesData[_T_Enum] | None:
+ """
+ returns `None` if reading fails,
+ otherwise returns the data for the registered `Enum`
+ """
+ from SNIClient import snes_read
+
+ reads: list[tuple[Read, bytes]] = []
+ for r in self._ranges:
+ if r.size < SNES_READ_CHUNK_SIZE: # most common
+ response = await snes_read(ctx, r.address, r.size)
+ if response is None:
+ return None
+ reads.append((r, response))
+ else: # big read
+ # Problems were reported with big reads,
+ # so we chunk it into smaller pieces.
+ read_so_far = 0
+ collection: list[bytes] = []
+ while read_so_far < r.size:
+ remaining_size = r.size - read_so_far
+ chunk_size = min(SNES_READ_CHUNK_SIZE, remaining_size)
+ response = await snes_read(ctx, r.address + read_so_far, chunk_size)
+ if response is None:
+ return None
+ collection.append(response)
+ read_so_far += chunk_size
+ reads.append((r, b"".join(collection)))
+ return SnesData(reads)
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py
index 17b881b314c6..6230193b44cc 100644
--- a/worlds/AutoWorld.py
+++ b/worlds/AutoWorld.py
@@ -235,7 +235,7 @@ class WebWorld(metaclass=WebWorldRegister):
tutorials: List["Tutorial"] = {}
"""docs folder will also be scanned for tutorial guides. Each Tutorial class is to be used for one guide."""
- theme = "grass"
+ theme: str = "grass"
"""Choose a theme for you /game/* pages.
Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, stone"""
diff --git a/worlds/Files.py b/worlds/Files.py
index a41424183f63..ddd1f4e1ce92 100644
--- a/worlds/Files.py
+++ b/worlds/Files.py
@@ -21,6 +21,10 @@
from Utils import Version
+class ImproperlyConfiguredAutoPatchError(Exception):
+ pass
+
+
class AutoPatchRegister(abc.ABCMeta):
patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {}
file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {}
@@ -30,8 +34,28 @@ def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> Aut
new_class = super().__new__(mcs, name, bases, dct)
if "game" in dct:
AutoPatchRegister.patch_types[dct["game"]] = new_class
- if not dct["patch_file_ending"]:
- raise Exception(f"Need an expected file ending for {name}")
+
+ if not callable(getattr(new_class, "patch", None)):
+ raise ImproperlyConfiguredAutoPatchError(
+ f"Container {new_class} uses metaclass AutoPatchRegister, but does not have a patch method defined."
+ )
+
+ patch_file_ending = dct.get("patch_file_ending")
+ if patch_file_ending == ".zip":
+ raise ImproperlyConfiguredAutoPatchError(
+ f'Auto patch container {new_class} uses file ending ".zip", which is not allowed.'
+ )
+ if patch_file_ending is None:
+ raise ImproperlyConfiguredAutoPatchError(
+ f"Need an expected file ending for auto patch container {new_class}"
+ )
+
+ existing_handler = AutoPatchRegister.file_endings.get(patch_file_ending)
+ if existing_handler:
+ raise ImproperlyConfiguredAutoPatchError(
+ f"Two auto patch containers are using the same file extension: {new_class}, {existing_handler}"
+ )
+
AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class
return new_class
diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py
index 2ec70832c02d..bcc545a964cd 100644
--- a/worlds/LauncherComponents.py
+++ b/worlds/LauncherComponents.py
@@ -5,7 +5,7 @@
from enum import Enum, auto
from typing import Optional, Callable, List, Iterable, Tuple
-from Utils import local_path, open_filename, is_frozen, is_kivy_running
+from Utils import local_path, open_filename, is_frozen, is_kivy_running, open_file, user_path
class Type(Enum):
@@ -204,6 +204,18 @@ def install_apworld(apworld_path: str = "") -> None:
Utils.messagebox("Install complete.", f"Installed APWorld from {source}.")
+def export_datapackage() -> None:
+ import json
+
+ from worlds import network_data_package
+
+ path = user_path("datapackage_export.json")
+ with open(path, "w") as f:
+ json.dump(network_data_package, f, indent=4)
+
+ open_file(path)
+
+
components: List[Component] = [
# Launcher
Component('Launcher', 'Launcher', component_type=Type.HIDDEN),
@@ -213,6 +225,8 @@ def install_apworld(apworld_path: str = "") -> None:
description="Host a generated multiworld on your computer."),
Component('Generate', 'Generate', cli=True,
description="Generate a multiworld with the YAMLs in the players folder."),
+ Component("Options Creator", "OptionsCreator", "ArchipelagoOptionsCreator", component_type=Type.TOOL,
+ description="Visual creator for Archipelago option files."),
Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld"),
description="Install an APWorld to play games not included with Archipelago by default."),
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient,
@@ -230,8 +244,10 @@ def install_apworld(apworld_path: str = "") -> None:
Component('Zillion Client', 'ZillionClient',
file_identifier=SuffixIdentifier('.apzl')),
- #MegaMan Battle Network 3
- Component('MMBN3 Client', 'MMBN3Client', file_identifier=SuffixIdentifier('.apbn3'))
+ # MegaMan Battle Network 3
+ Component('MMBN3 Client', 'MMBN3Client', file_identifier=SuffixIdentifier('.apbn3')),
+
+ Component("Export Datapackage", func=export_datapackage, component_type=Type.TOOL),
]
@@ -275,11 +291,11 @@ def _build_apworlds(*launch_args: str):
manifest = json.load(manifest_file)
assert "game" in manifest, (
- f"World directory {world_directory} has an archipelago.json manifest file, but it"
+ f"World directory {world_directory} has an archipelago.json manifest file, but it "
"does not define a \"game\"."
)
assert manifest["game"] == worldtype.game, (
- f"World directory {world_directory} has an archipelago.json manifest file, but value of the"
+ f"World directory {world_directory} has an archipelago.json manifest file, but value of the "
f"\"game\" field ({manifest['game']} does not equal the World class's game ({worldtype.game})."
)
else:
@@ -302,5 +318,5 @@ def _build_apworlds(*launch_args: str):
open_folder(apworlds_folder)
- components.append(Component('Build APWorlds', func=_build_apworlds, cli=True,
+ components.append(Component("Build APWorlds", func=_build_apworlds, cli=True,
description="Build APWorlds from loose-file world folders."))
diff --git a/worlds/RAC1/ItemPool.py b/worlds/RAC1/ItemPool.py
new file mode 100644
index 000000000000..a3c9082eadba
--- /dev/null
+++ b/worlds/RAC1/ItemPool.py
@@ -0,0 +1,40 @@
+from BaseClasses import ItemClassification
+from .data import Items
+from .data.Items import ItemData
+
+
+def get_classification(item: ItemData) -> ItemClassification:
+ if (item in Items.PLANETS
+ or item in Items.ALL_PACKS
+ or item in Items.GADGETS
+ or item in Items.ALL_BOOTS
+ or item in Items.GOLD_BOLTS):
+ return ItemClassification.progression
+ if item in [
+ Items.TAUNTER,
+ Items.O2_MASK,
+ Items.PILOTS_HELMET,
+ Items.PROGRESSIVE_HELMET,
+ Items.CODEBOT,
+ Items.RARITANIUM,
+ Items.HOVERBOARD,
+ Items.ZOOMERATOR,
+ Items.PROGRESSIVE_HOVERBOARD,
+ Items.BOMB_GLOVE,
+ Items.PROGRESSIVE_BOMB,
+ Items.BLASTER,
+ Items.MINE_GLOVE,
+ Items.PROGRESSIVE_MINE,
+ Items.DEVASTATOR,
+ Items.PROGRESSIVE_DEVASTATOR,
+ Items.VISIBOMB,
+ Items.RYNO,
+ Items.PROGRESSIVE_TRADE,
+ ]:
+ return ItemClassification.progression
+ if (item == Items.SONIC_SUMMONER
+ or item in Items.ALL_WEAPONS
+ or item in Items.ALL_EXTRA_ITEMS):
+ return ItemClassification.useful
+
+ return ItemClassification.filler
diff --git a/worlds/RAC1/Logic.py b/worlds/RAC1/Logic.py
new file mode 100644
index 000000000000..d3aec75a5288
--- /dev/null
+++ b/worlds/RAC1/Logic.py
@@ -0,0 +1,391 @@
+import logging
+
+from BaseClasses import CollectionState
+from .data import Items
+
+rac_logger = logging.getLogger("Ratchet & Clank")
+rac_logger.setLevel(logging.DEBUG)
+
+
+def can_swingshot(state: CollectionState, player: int) -> bool:
+ return state.has(Items.SWINGSHOT.name, player)
+
+
+def can_improved_jump(state: CollectionState, player: int) -> bool:
+ return (state.has_any_count(Items.PROG[Items.HELI_PACK.name], player) or
+ state.has_any_count(Items.PROG[Items.THRUSTER_PACK.name], player))
+
+
+def can_heli_high_jump(state: CollectionState, player: int) -> bool: # relevant for eudora gold bolt
+ return state.has_any_count(Items.PROG[Items.HELI_PACK.name], player)
+
+
+def can_glide(state: CollectionState, player: int) -> bool: # gliding is not possible without the heli pack
+ return state.has_any_count(Items.PROG[Items.HELI_PACK.name], player)
+
+
+def can_ground_pound(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.THRUSTER_PACK.name], player)
+
+
+def has_hydro_pack(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.HYDRO_PACK.name], player)
+
+
+def can_grind(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.GRINDBOOTS.name], player)
+
+
+def has_magneboots(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.MAGNEBOOTS.name], player)
+
+
+def has_hydrodisplacer(state: CollectionState, player: int) -> bool:
+ return state.has(Items.HYDRODISPLACER.name, player)
+
+
+def has_raritanium(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.RARITANIUM.name], player)
+
+
+def has_zoomerator(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.ZOOMERATOR.name], player)
+
+
+def has_hoverboard(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.HOVERBOARD.name], player)
+
+
+def has_o2_mask(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.O2_MASK.name], player)
+
+
+def has_trespasser(state: CollectionState, player: int) -> bool:
+ return state.has(Items.TRESPASSER.name, player)
+
+
+def has_visibomb(state: CollectionState, player: int) -> bool:
+ return state.has(Items.VISIBOMB.name, player)
+
+
+def has_hologuise(state: CollectionState, player: int) -> bool:
+ return state.has(Items.HOLOGUISE.name, player)
+
+
+def has_pilots_helmet(state: CollectionState, player: int) -> bool:
+ return state.has_any_count(Items.PROG[Items.PILOTS_HELMET.name], player)
+
+
+def has_codebot(state: CollectionState, player: int) -> bool:
+ return state.has(Items.CODEBOT.name, player)
+
+
+def has_taunter(state: CollectionState, player: int) -> bool:
+ return state.has(Items.TAUNTER.name, player)
+
+# TODO Logic for accessing dig spots on each planet
+def has_metal_detector(state: CollectionState, player: int) -> bool:
+ return state.has(Items.METAL_DETECTOR.name, player)
+
+
+def has_explosive_weapon(state: CollectionState, player: int) -> bool:
+ return (state.has_any_count(Items.PROG[Items.BOMB_GLOVE.name], player) or
+ state.has_any_count(Items.PROG[Items.MINE_GLOVE.name], player) or
+ state.has_any_count(Items.PROG[Items.DEVASTATOR.name], player) or
+ state.has_any([Items.VISIBOMB.name, Items.RYNO.name], player))
+
+
+def has_long_range_weapon(state: CollectionState, player: int) -> bool:
+ return (state.has_any_count(Items.PROG[Items.BLASTER.name], player) or
+ state.has_any_count(Items.PROG[Items.DEVASTATOR.name], player) or
+ state.has_any([Items.VISIBOMB.name, Items.RYNO.name], player))
+
+
+def has_40_gold_bolts(state: CollectionState, player: int) -> bool:
+ lookup: dict[int, tuple[str, int]] = {
+ 1: (Items.GOLD_BOLT_1.name, 40),
+ 2: (Items.GOLD_BOLT_2.name, 20),
+ 3: (Items.GOLD_BOLT_3.name, 14),
+ 4: (Items.GOLD_BOLT_4.name, 10),
+ 5: (Items.GOLD_BOLT_5.name, 8),
+ 6: (Items.GOLD_BOLT_6.name, 7),
+ 7: (Items.GOLD_BOLT_7.name, 6),
+ 8: (Items.GOLD_BOLT_8.name, 5),
+ 9: (Items.GOLD_BOLT_9.name, 5),
+ 10: (Items.GOLD_BOLT_10.name, 4),
+ 11: (Items.GOLD_BOLT_11.name, 4),
+ 12: (Items.GOLD_BOLT_12.name, 4),
+ 13: (Items.GOLD_BOLT_13.name, 4),
+ 14: (Items.GOLD_BOLT_14.name, 3),
+ 15: (Items.GOLD_BOLT_15.name, 3),
+ 16: (Items.GOLD_BOLT_16.name, 3),
+ 17: (Items.GOLD_BOLT_17.name, 3),
+ 18: (Items.GOLD_BOLT_18.name, 3),
+ 19: (Items.GOLD_BOLT_19.name, 3),
+ 20: (Items.GOLD_BOLT_20.name, 2),
+ 21: (Items.GOLD_BOLT_21.name, 2),
+ 22: (Items.GOLD_BOLT_22.name, 2),
+ 23: (Items.GOLD_BOLT_23.name, 2),
+ 24: (Items.GOLD_BOLT_24.name, 2),
+ 25: (Items.GOLD_BOLT_25.name, 2),
+ 26: (Items.GOLD_BOLT_26.name, 2),
+ 27: (Items.GOLD_BOLT_27.name, 2),
+ 28: (Items.GOLD_BOLT_28.name, 2),
+ 29: (Items.GOLD_BOLT_29.name, 2),
+ 30: (Items.GOLD_BOLT_30.name, 2),
+ 31: (Items.GOLD_BOLT_31.name, 2),
+ 32: (Items.GOLD_BOLT_32.name, 2),
+ 33: (Items.GOLD_BOLT_33.name, 2),
+ 34: (Items.GOLD_BOLT_34.name, 2),
+ 35: (Items.GOLD_BOLT_35.name, 2),
+ 36: (Items.GOLD_BOLT_36.name, 2),
+ 37: (Items.GOLD_BOLT_37.name, 2),
+ 38: (Items.GOLD_BOLT_38.name, 2),
+ 39: (Items.GOLD_BOLT_39.name, 2),
+ 40: (Items.GOLD_BOLT_40.name, 1),
+ }
+ item, count = lookup[state.multiworld.worlds[player].options.pack_size_gold_bolts.value]
+ #if state.count(item, player) < count:
+ #rac_logger.debug(f"Missing gold bolt packs from world, expected {count} but only had"
+ #f" {state.count(item, player)}. Can reach "
+ #f"{state.prog_items}")
+ return state.has(item, player, count)
+
+
+# Novalis
+def novalis_underwater_caves_rule(state: CollectionState, player: int) -> bool:
+ return has_hydro_pack(state, player)
+
+
+def novalis_gold_weapon_rule(state: CollectionState, player: int) -> bool:
+ return has_40_gold_bolts(state, player) and has_metal_detector(state, player)
+
+
+def novalis_skillpoint_rule(state: CollectionState, player: int) -> bool:
+ return has_long_range_weapon(state, player)
+
+
+# Eudora
+def eudora_suck_cannon_rule(state: CollectionState, player: int) -> bool:
+ return (can_improved_jump(state, player)
+ and can_glide(state, player))
+
+
+def eudora_henchman_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and has_trespasser(state, player)
+ and can_improved_jump(state, player))
+
+
+# Rilgar
+def rilgar_hoverboard_rule(state: CollectionState, player: int) -> bool:
+ return (has_hoverboard(state, player)
+ and can_improved_jump(state, player))
+
+
+def rilgar_bouncer_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and can_improved_jump(state, player)
+ and has_hydrodisplacer(state, player))
+
+
+def rilgar_underwater_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (rilgar_bouncer_rule(state, player)
+ and has_o2_mask(state, player))
+
+
+def rilgar_ryno_rule(state: CollectionState, player: int) -> bool:
+ return (can_improved_jump(state, player)
+ and has_metal_detector(state, player))
+
+
+# Blarg
+def blarg_outside_gold_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (has_o2_mask(state, player)
+ and has_trespasser(state, player))
+
+
+# Umbris
+def umbris_snagglebeast_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and can_glide(state, player)
+ and has_hydrodisplacer(state, player))
+
+
+def umbris_pressure_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and can_glide(state, player))
+
+
+def umbris_jump_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and can_glide(state, player)
+ and has_hydrodisplacer(state, player))
+
+
+# Orxon
+def orxon_nanotech_rule(state: CollectionState, player: int) -> bool:
+ return (has_o2_mask(state, player)
+ and can_glide(state, player))
+
+
+def orxon_ultra_nanotech_rule(state: CollectionState, player: int) -> bool:
+ return (orxon_nanotech_rule(state, player)
+ and has_metal_detector(state, player))
+
+
+def orxon_visibomb_rule(state: CollectionState, player: int) -> bool:
+ return (has_o2_mask(state, player)
+ and has_metal_detector(state, player))
+
+
+def orxon_visibomb_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (has_o2_mask(state, player)
+ and has_visibomb(state, player)
+ and can_glide(state, player)
+ and can_swingshot(state, player)
+ and has_magneboots(state, player))
+
+
+def orxon_ratchet_infobot_rule(state: CollectionState, player: int) -> bool:
+ return (has_o2_mask(state, player)
+ and can_glide(state, player)
+ and can_swingshot(state, player)
+ and has_magneboots(state, player))
+
+
+# Pokitaru
+def pokitaru_ship_rule(state: CollectionState, player: int) -> bool:
+ return (has_pilots_helmet(state, player)
+ and can_ground_pound(state, player))
+
+
+def pokitaru_persuader_rule(state: CollectionState, player: int) -> bool:
+ return (has_raritanium(state, player)
+ and has_trespasser(state, player)
+ and has_hydrodisplacer(state, player))
+
+
+def pokitaru_gold_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and can_ground_pound(state, player))
+
+
+# Hoven
+def hoven_infobot_rule(state: CollectionState, player: int) -> bool:
+ return (has_long_range_weapon(state, player)
+ and can_improved_jump(state, player))
+
+
+def hoven_raritanium_rule(state: CollectionState, player: int) -> bool:
+ return (can_swingshot(state, player)
+ and can_improved_jump(state, player))
+
+
+# Gemlik
+def gemlik_quark_rule(state: CollectionState, player: int) -> bool:
+ return (has_magneboots(state, player)
+ and can_improved_jump(state, player)
+ and has_long_range_weapon(state, player)
+ and has_trespasser(state, player)
+ and can_swingshot(state, player))
+
+
+def gemlik_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (has_visibomb(state, player)
+ and can_improved_jump(state, player)
+ and has_trespasser(state, player))
+
+
+def gemlik_gold_weapon_rule(state: CollectionState, player: int) -> bool:
+ return (has_magneboots(state, player)
+ and can_improved_jump(state, player)
+ and has_long_range_weapon(state, player)
+ and has_trespasser(state, player)
+ and can_swingshot(state, player)
+ and has_40_gold_bolts(state, player)
+ and has_metal_detector(state, player))
+
+
+# Oltanis
+def oltanis_main_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (can_grind(state, player)
+ and can_swingshot(state, player))
+
+
+def oltanis_final_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (can_grind(state, player)
+ and can_swingshot(state, player)
+ and has_magneboots(state, player))
+
+
+# Quartu
+def quartu_infiltrate_rule(state: CollectionState, player: int) -> bool:
+ return (has_hologuise(state, player)
+ and can_swingshot(state, player)
+ and can_ground_pound(state, player))
+
+
+def quartu_codebot_rule(state: CollectionState, player: int) -> bool:
+ return (has_codebot(state, player)
+ and can_swingshot(state, player))
+
+
+def quartu_bolt_grabber_rule(state: CollectionState, player: int) -> bool:
+ return (has_hydro_pack(state, player)
+ and has_o2_mask(state, player))
+
+
+# Kalebo III
+def kalebo_hologuise_rule(state: CollectionState, player: int) -> bool:
+ return (has_hoverboard(state, player)
+ and can_swingshot(state, player)
+ and can_grind(state, player))
+
+
+# Drek's Fleet
+def fleet_infobot_rule(state: CollectionState, player: int) -> bool:
+ return (has_magneboots(state, player)
+ and has_pilots_helmet(state, player)
+ and has_hologuise(state, player)
+ and can_swingshot(state, player))
+
+
+def fleet_water_rule(state: CollectionState, player: int) -> bool:
+ return (has_o2_mask(state, player)
+ and has_hydro_pack(state, player))
+
+
+def fleet_second_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (has_magneboots(state, player)
+ and has_pilots_helmet(state, player)
+ and has_hologuise(state, player)
+ and can_swingshot(state, player))
+
+
+# Veldin
+def veldin_global_rule(state: CollectionState, player: int) -> bool:
+ return (has_trespasser(state, player)
+ and has_magneboots(state, player)
+ and has_hydrodisplacer(state, player)
+ and can_ground_pound(state, player))
+
+
+def veldin_grind_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (veldin_global_rule(state, player)
+ and can_grind(state, player)
+ and can_swingshot(state, player))
+
+
+def veldin_halfway_bolt_rule(state: CollectionState, player: int) -> bool:
+ return veldin_global_rule(state, player)
+
+
+def veldin_taunter_bolt_rule(state: CollectionState, player: int) -> bool:
+ return (veldin_global_rule(state, player)
+ and has_taunter(state, player))
+
+
+def veldin_defeat_drek_rule(state: CollectionState, player: int) -> bool:
+ return (veldin_global_rule(state, player)
+ and can_swingshot(state, player))
diff --git a/worlds/RAC1/Options.py b/worlds/RAC1/Options.py
new file mode 100644
index 000000000000..975933108bad
--- /dev/null
+++ b/worlds/RAC1/Options.py
@@ -0,0 +1,435 @@
+from dataclasses import dataclass
+from typing import Any
+
+from Options import (Choice, PerGameCommonOptions, Range, TextChoice, Toggle)
+
+
+class ItemOptions(Choice):
+ """Template
+ vanilla: Option selects the vanilla items for these locations.
+ random_same: Option selects an item from the same item group as the vanilla item for these locations.
+ random_item: Option selects any weapon, gadget, pack, helmet, boots, item, or infobot to be shuffled to these
+ locations.
+ unrestricted: Option selects anything to be shuffled to these locations (including Gold Bolts and Skillpoints).
+ """
+ value: int
+ option_vanilla = 0
+ option_random_same = 1
+ option_random_item = 2
+ option_unrestricted = 3
+ alias_true = 3
+ alias_false = 0
+
+
+class StartingItem(Choice):
+ """Randomize what weapon you start the game with.
+ vanilla: Start with the Bomb Glove.
+ random_same: Start with a random weapon.
+ random_item: Start with any random equipable item, weapons or gadgets.
+ """
+ display_name = "Starting Item"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_random_same = 1
+ option_random_item = 2
+ default = 0
+ alias_true = 2
+ alias_false = 0
+ pool = "StartItem"
+
+
+class StartingLocation(Toggle):
+ """Randomize what Planet you start on"""
+ display_name = "Shuffle Starting Planet"
+ default = 1
+
+
+class ShuffleWeapons(ItemOptions):
+ """Randomize Weapon locations
+ vanilla: Weapons are unshuffled.
+ random_same: Weapons are shuffled to other Weapon locations.
+ random_item: Weapons are shuffled anywhere, useful items are found at Weapon locations.
+ unrestricted: Weapons are shuffled anywhere, anything can be found at Weapon locations.
+ """
+ display_name = "Shuffle Weapons"
+ rich_text_doc = True
+ default = 3
+ pool = "Weapons"
+
+
+class EarlyWeapon(TextChoice):
+ """
+ Force a weapon to be in your sphere 1.
+ Set to off if 'Randomize Weapon locations' option is set to 'vanilla or random_same'.
+ """
+ display_name = "Early Weapon"
+ rich_text_doc = True
+
+
+class ShuffleGadgets(ItemOptions):
+ """Randomize Gadget locations
+ vanilla: Gadgets are unshuffled.
+ random_same: Gadgets are shuffled to other Gadget locations.
+ random_item: Gadgets are shuffled anywhere, useful items are found at Gadget locations.
+ unrestricted: Gadgets are shuffled anywhere, anything can be found at Gadget locations.
+ """
+ display_name = "Shuffle Gadgets"
+ rich_text_doc = True
+ default = 3
+ pool = "Gadgets"
+
+
+class ShufflePacks(ItemOptions):
+ """Randomize Pack locations
+ vanilla: Packs are unshuffled.
+ random_same: Packs are shuffled to other Pack locations.
+ random_item: Packs are shuffled anywhere, useful items are found at Pack locations.
+ unrestricted: Packs are shuffled anywhere, anything can be found at Pack locations.
+ """
+ display_name = "Shuffle Packs"
+ rich_text_doc = True
+ default = 3
+ pool = "Packs"
+
+
+class ShuffleHelmets(ItemOptions):
+ """Randomize Helmet locations
+ vanilla: Helmets are unshuffled.
+ random_same: Helmets are shuffled to other Helmet locations.
+ random_item: Helmets are shuffled anywhere, useful items are found at Helmet locations.
+ unrestricted: Helmets are shuffled anywhere, anything can be found at Helmet locations.
+ """
+ display_name = "Shuffle Helmets"
+ rich_text_doc = True
+ default = 3
+ pool = "Helmets"
+
+
+class ShuffleBoots(ItemOptions):
+ """Randomize Boot locations
+ vanilla: Boots are unshuffled.
+ random_same: Boots are shuffled to other Boot locations.
+ random_item: Boots are shuffled anywhere, useful items are found at Boot locations.
+ unrestricted: Boots are shuffled anywhere, anything can be found at Boot locations.
+ """
+ display_name = "Shuffle Boots"
+ rich_text_doc = True
+ default = 3
+ pool = "Boots"
+
+
+class ShuffleExtraItems(ItemOptions):
+ """Randomize Extra Item locations (Hoverboard, Persuader, etc...)
+ vanilla: Extra Items are unshuffled.
+ random_same: Extra Items are shuffled to other Extra Item locations.
+ random_item: Extra Items are shuffled anywhere, useful items are found at Extra Item locations.
+ unrestricted: Extra Items are shuffled anywhere, anything can be found at Extra Item locations.
+ """
+ display_name = "Shuffle Extra Items"
+ rich_text_doc = True
+ default = 3
+ pool = "ExtraItems"
+
+
+class ShuffleGoldBolts(Toggle):
+ """Randomize Gold Bolt locations"""
+ display_name = "Shuffle Gold Bolts"
+ default = 1
+
+
+class GoldBoltPackSize(Range):
+ """
+ Number of Gold Bolts received each time you collect a pack of Gold Bolts (Gold Bolts Shuffle Off forces this to 1)
+ """
+ display_name = "Gold Bolt Pack Size"
+ default = 8
+ range_start = 1
+ range_end = 40
+
+
+class BoltPackSize(Choice):
+ """Number of Bolts received each time you collect a pack of Bolts."""
+ display_name = "Bolt Pack Size"
+ option_0 = 0
+ option_1 = 1
+ option_10 = 10
+ option_100 = 100
+ option_250 = 250
+ option_500 = 500
+ option_750 = 750
+ option_1000 = 1000
+ option_2000 = 2000
+ option_3000 = 3000
+ option_4000 = 4000
+ option_5000 = 5000
+ option_6000 = 6000
+ option_7000 = 7000
+ option_8000 = 8000
+ option_9000 = 9000
+ option_10000 = 10000
+ option_12500 = 12500
+ option_15000 = 15000
+ option_17500 = 17500
+ option_20000 = 20000
+ option_25000 = 25000
+ option_30000 = 30000
+ option_40000 = 40000
+ option_50000 = 50000
+ option_75000 = 75000
+ option_100000 = 100000
+ default = option_15000
+
+
+class ShuffleInfobots(ItemOptions):
+ """Randomize Infobot locations
+ vanilla: Infobots are unshuffled.
+ random_same: Infobots are shuffled to other Infobot locations.
+ random_item: Infobots are shuffled anywhere, useful items are found at Infobot locations.
+ WARNING! Using random_same, or random_item with no other pool selected, is likely to fail on solo worlds.
+ unrestricted: Infobots are shuffled anywhere, anything can be found at Infobot locations.
+ """
+ display_name = "Shuffle Infobots" #
+ rich_text_doc = True
+ default = 3
+ pool = "Infobots"
+
+
+class ShuffleGoldWeapons(ItemOptions):
+ """Randomize Gold Weapon locations
+ vanilla: Gold Weapons are unshuffled.
+ random_same: Gold Weapons are shuffled to other Gold Weapon locations.
+ random_item: Gold Weapons are shuffled anywhere, useful items are found at Gold Weapon locations.
+ unrestricted: Gold Weapons are shuffled anywhere, anything can be found at Gold Weapon locations.
+ """
+ display_name = "Shuffle Gold Weapons"
+ rich_text_doc = True
+ default = 3
+ pool = "GoldenWeapons"
+
+
+class ShuffleSkillPoints(Toggle):
+ """Randomize Skillpoint locations"""
+ display_name = "Shuffle Skillpoints"
+ default = 1
+
+
+class EnableBoltMultiplier(Range):
+ """Enables the bolt multiplier feature without being in New Game+."""
+ display_name = "Bolt Multiplier"
+ default = 5
+ range_start = 1
+ range_end = 20
+
+
+class MDBoltMultiplier(Range):
+ """Bolt Multiplier when using the metal detector"""
+ display_name = "Metal Detector Bolt Multiplier"
+ default = 35
+ range_start = 1
+ range_end = 100
+
+
+# TODO Option: Vendor in logic without metal detector
+
+class ProgressiveOptions(Choice):
+ """Template
+ vanilla: These items are not progressive, each item is independent of other items.
+ progressive: These items are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: These items are progressive, the order of upgrading is reversed.
+ progressive_random: These items are progressive, the order of upgrading is random.
+ """
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+class GoldenWeaponProgression(ProgressiveOptions):
+ """
+ If enabled, make golden weapons and their standard variants progressive items.
+ vanilla: Golden Weapons and Weapons are not progressive, Golden Weapons do nothing until their base item is
+ found.
+ normal: Golden Weapons and Weapons are not progressive, each item is independent of other items.
+ progressive: Golden Weapons and Weapons are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Golden Weapons and Weapons are progressive, the order of upgrading is reversed.
+ progressive_random: Golden Weapons and Weapons are progressive, the order of upgrading is random."""
+ display_name = "Progressive Weapons"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_normal = 1
+ option_progressive = 2
+ option_progressive_reversed = 3
+ option_progressive_random = 4
+ alias_true = 0
+ alias_false = 1
+ default = 1
+
+
+class PackProgression(ProgressiveOptions):
+ """
+ vanilla: Packs are not progressive, each item is independent of other items.
+ progressive: Packs are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Packs are progressive, the order of upgrading is reversed.
+ progressive_random: Packs are progressive, the order of upgrading is random.
+ """
+ display_name = "Progressive Packs"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+class HelmetProgression(ProgressiveOptions):
+ """
+ vanilla: Helmets are not progressive, each item is independent of other items.
+ progressive: Helmets are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Helmets are progressive, the order of upgrading is reversed.
+ progressive_random: Helmets are progressive, the order of upgrading is random.
+ """
+ display_name = "Progressive Helmets"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+class BootsProgression(ProgressiveOptions):
+ """
+ vanilla: Grind and Magneboots are not progressive, each item is independent of other items.
+ progressive: Grind and Magneboots are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Grind and Magneboots are progressive, the order of upgrading is reversed.
+ progressive_random: Grind and Magneboots are progressive, the order of upgrading is random.
+ """
+ display_name = "Progressive Boots"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+class HoverboardProgression(ProgressiveOptions):
+ """
+ vanilla: Hoverboard and Zoomerator are not progressive, each item is independent of other items.
+ progressive: Hoverboard and Zoomerator are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Hoverboard and Zoomerator are progressive, the order of upgrading is reversed.
+ progressive_random: Hoverboard and Zoomerator are progressive, the order of upgrading is random.
+ """
+ display_name = "Progressive Hoverboard"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+class RaritaniumProgression(ProgressiveOptions):
+ """
+ vanilla: Raritanium and Persuader are not progressive, each item is independent of other items.
+ progressive: Raritanium and Persuader are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Raritanium and Persuader are progressive, the order of upgrading is reversed.
+ progressive_random: Raritanium and Persuader are progressive, the order of upgrading is random.
+ """
+ display_name = "Progressive Raritanium"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+class NanotechProgression(ProgressiveOptions):
+ """
+ vanilla: Nanotech is not progressive, each item is independent of other items.
+ progressive: Nanotech are progressive, collecting multiple of an item will upgrade it.
+ progressive_reversed: Nanotech are progressive, the order of upgrading is reversed.
+ progressive_random: Nanotech are progressive, the order of upgrading is random.
+ """
+ display_name = "Progressive Nanotech"
+ rich_text_doc = True
+ value: int
+ option_vanilla = 0
+ option_progressive = 1
+ option_progressive_reversed = 2
+ option_progressive_random = 3
+ alias_true = 1
+ alias_false = 0
+
+
+@dataclass
+class RacOptions(PerGameCommonOptions):
+ # death_link: DeathLink
+ starting_item: StartingItem
+ starting_location: StartingLocation
+ shuffle_weapons: ShuffleWeapons
+ shuffle_gadgets: ShuffleGadgets
+ shuffle_packs: ShufflePacks
+ shuffle_helmets: ShuffleHelmets
+ shuffle_boots: ShuffleBoots
+ shuffle_extra_items: ShuffleExtraItems
+ shuffle_gold_bolts: ShuffleGoldBolts
+ shuffle_infobots: ShuffleInfobots
+ shuffle_gold_weapons: ShuffleGoldWeapons
+ # shuffle_skill_points: ShuffleSkillPoints
+ pack_size_gold_bolts: GoldBoltPackSize
+ pack_size_bolts: BoltPackSize
+ metal_bolt_multiplier: MDBoltMultiplier
+ enable_bolt_multiplier: EnableBoltMultiplier
+ progressive_weapons: GoldenWeaponProgression
+ progressive_packs: PackProgression
+ progressive_helmets: HelmetProgression
+ progressive_boots: BootsProgression
+ progressive_hoverboard: HoverboardProgression
+ progressive_raritanium: RaritaniumProgression
+ progressive_nanotech: NanotechProgression
+
+
+def get_options_as_dict(options: RacOptions) -> dict[str, Any]:
+ return {
+ # "death_link",
+ "starting_item": options.starting_item.value,
+ "starting_location": options.starting_location.value,
+ "shuffle_weapons": options.shuffle_weapons.value,
+ "shuffle_gadgets": options.shuffle_gadgets.value,
+ "shuffle_packs": options.shuffle_packs.value,
+ "shuffle_helmets": options.shuffle_helmets.value,
+ "shuffle_boots": options.shuffle_boots.value,
+ "shuffle_extra_items": options.shuffle_extra_items.value,
+ "shuffle_gold_bolts": options.shuffle_gold_bolts.value,
+ "shuffle_infobots": options.shuffle_infobots.value,
+ "shuffle_gold_weapons": options.shuffle_gold_weapons.value,
+ # "shuffle_skill_points": options.shuffle_skill_points.value,
+ "pack_size_gold_bolts": options.pack_size_gold_bolts.value,
+ "pack_size_bolts": options.pack_size_bolts.value,
+ "metal_bolt_multiplier": options.metal_bolt_multiplier.value,
+ "enable_bolt_multiplier": options.enable_bolt_multiplier.value,
+ "progressive_weapons": options.progressive_weapons.value,
+ "progressive_packs": options.progressive_packs.value,
+ "progressive_helmets": options.progressive_helmets.value,
+ "progressive_boots": options.progressive_boots.value,
+ "progressive_hoverboard": options.progressive_hoverboard.value,
+ "progressive_raritanium": options.progressive_raritanium.value,
+ "progressive_nanotech": options.progressive_nanotech.value,
+ }
diff --git a/worlds/RAC1/Regions.py b/worlds/RAC1/Regions.py
new file mode 100644
index 000000000000..9169dda2ac11
--- /dev/null
+++ b/worlds/RAC1/Regions.py
@@ -0,0 +1,72 @@
+import typing
+
+from BaseClasses import CollectionState, Location, Region
+from .data import Planets
+from .data.Items import check_progressive_item, get_gold_bolts
+from .data.Locations import LocationData, POOL_GOLD_BOLT, POOL_GOLDEN_WEAPON
+from .data.Planets import PlanetData
+from ..generic.Rules import forbid_item
+
+if typing.TYPE_CHECKING:
+ from . import RacWorld
+
+
+class RacLocation(Location):
+ game: str = "Ratchet & Clank"
+
+
+def create_regions(world: 'RacWorld'):
+ # create all regions and populate with locations
+ menu = Region("Menu", world.player, world.multiworld)
+ world.multiworld.regions.append(menu)
+
+ for planet_data in Planets.LOGIC_PLANETS:
+ if planet_data.locations:
+ def generate_planet_access_rule(planet: PlanetData) -> typing.Callable[[CollectionState], bool]:
+ def planet_access_rule(state: CollectionState):
+ # Connect with special case access rules (not relevant for rac1?)
+ # if planet == Planets.TABORA:
+ # return (
+ # state.has(Items.coord_for_planet(planet.number).name, world.player)
+ # and can_heli(state, world.player)
+ # and can_swingshot(state, world.player)
+ # )
+ # if planet == Planets.ARANOS_PRISON:
+ # return (
+ # state.has(Items.coord_for_planet(planet.number).name, world.player)
+ # and state.has_all([
+ # Items.GRAVITY_BOOTS.name, Items.LEVITATOR.name, Items.INFILTRATOR.name], world.player
+ # )
+ # )
+ # Connect with general case access rule
+ # else:
+ return state.has(planet.name, world.player)
+
+ return planet_access_rule
+
+ region = Region(planet_data.name, world.player, world.multiworld)
+ world.multiworld.regions.append(region)
+ menu.connect(region, None, generate_planet_access_rule(planet_data))
+
+ for location_data in planet_data.locations:
+ def generate_access_rule(loc: LocationData) -> typing.Callable[[CollectionState], bool]:
+ def access_rule(state: CollectionState):
+ if loc.access_rule:
+ return loc.access_rule(state, world.player)
+ return True
+
+ return access_rule
+
+ region.add_locations({location_data.name: location_data.location_id}, RacLocation)
+ if POOL_GOLD_BOLT in location_data.pools:
+ location_data.vanilla_item = get_gold_bolts(world.options)
+ elif location_data.vanilla_item is not None:
+ location_data.vanilla_item = check_progressive_item(world.options, location_data.vanilla_item)
+
+ location = world.multiworld.get_location(location_data.name, world.player)
+ location.access_rule = generate_access_rule(location_data)
+ if POOL_GOLDEN_WEAPON in location_data.pools:
+ forbid_item(location, get_gold_bolts(world.options), world.player)
+
+ # from Utils import visualize_regions
+ # visualize_regions(world.multiworld.get_region("Menu", world.player), "my_world.puml")
diff --git a/worlds/RAC1/Simplifield Ratchet & Clank.yaml b/worlds/RAC1/Simplifield Ratchet & Clank.yaml
new file mode 100644
index 000000000000..a07e996b00b8
--- /dev/null
+++ b/worlds/RAC1/Simplifield Ratchet & Clank.yaml
@@ -0,0 +1,101 @@
+# Q. What is this file?
+# A. This file contains options which allow you to configure your multiworld experience while allowing
+# others to play how they want as well.
+#
+# Q. How do I use it?
+# A. The options in this file are weighted. This means the higher number you assign to a value, the
+# more chances you have for that option to be chosen. For example, an option like this:
+#
+# map_shuffle:
+# on: 5
+# off: 15
+#
+# Means you have 5 chances for map shuffle to occur, and 15 chances for map shuffle to be turned
+# off.
+#
+# Q. I've never seen a file like this before. What characters am I allowed to use?
+# A. This is a .yaml file. You are allowed to use most characters.
+# To test if your yaml is valid or not, you can use this website:
+# http://www.yamllint.com/
+# You can also verify that your Archipelago options are valid at this site:
+# https://archipelago.gg/check
+
+# Your name in-game, limited to 16 characters.
+# {player} will be replaced with the player's slot number.
+# {PLAYER} will be replaced with the player's slot number, if that slot number is greater than 1.
+# {number} will be replaced with the counter value of the name.
+# {NUMBER} will be replaced with the counter value of the name, if the counter value is greater than 1.
+name: Player{number}
+
+# Used to describe your yaml. Useful if you have multiple files.
+description: Default Ratchet & Clank Template
+
+game: Ratchet & Clank
+requires:
+ version: 0.6.1 # Version of Archipelago required for this yaml to work as expected.
+
+Ratchet & Clank:
+ # Game Options
+ progression_balancing:
+ # A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
+ #
+ # A lower setting means more getting stuck. A higher setting means less getting stuck.
+ #
+ # You can define additional values between the minimum and maximum values.
+ # Minimum value is 0
+ # Maximum value is 99
+ random: 0
+ random-low: 0
+ random-high: 0
+ disabled: 0 # equivalent to 0
+ normal: 50 # equivalent to 50
+ extreme: 0 # equivalent to 99
+
+ 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.
+ full: 50
+ minimal: 0
+
+
+ # Item & Location Options
+ local_items:
+ # Forces these items to be in their native world.
+ []
+
+ non_local_items:
+ # Forces these items to be outside their native world.
+ []
+
+ start_inventory:
+ # Start with these items.
+ {}
+
+ start_inventory_from_pool:
+ # Start with these items and don't place them in the world.
+ #
+ # The game decides what the replacement items will be.
+ {}
+
+ start_hints:
+ # Start with these item's locations prefilled into the ``!hint`` command.
+ []
+
+ start_location_hints:
+ # Start with these locations and their item prefilled into the ``!hint`` command.
+ []
+
+ exclude_locations:
+ # Prevent these locations from having an important item.
+ []
+
+ priority_locations:
+ # Prevent these locations from having an unimportant item.
+ []
+
+ item_links:
+ # Share part of your item pool with other players.
+ []
diff --git a/worlds/RAC1/__init__.py b/worlds/RAC1/__init__.py
new file mode 100644
index 000000000000..ced72fcd3f14
--- /dev/null
+++ b/worlds/RAC1/__init__.py
@@ -0,0 +1,440 @@
+import logging
+from typing import Any, Mapping, Optional
+
+from BaseClasses import CollectionState, Item, ItemClassification, Tutorial
+from Fill import fill_restrictive, FillError, sweep_from_pool
+from worlds.AutoWorld import WebWorld, World
+from worlds.LauncherComponents import Component, components, SuffixIdentifier, Type
+from . import ItemPool
+from .data import Items, Locations, Planets
+from .data.Items import ALL_WEAPONS, check_progressive_item, CollectableData, get_bolt_pack, progression_rules
+from .data.Locations import (ALL_POOLS, DEFAULT_LIST, LocationData, POOL_BOOT, POOL_EXTRA_ITEM, POOL_GADGET,
+ POOL_GOLD_BOLT, POOL_GOLDEN_WEAPON, POOL_HELMET, POOL_INFOBOT, POOL_PACK, POOL_SKILLPOINT,
+ POOL_WEAPON)
+from .data.Planets import ALL_LOCATIONS, location_groups, PlanetData
+from .Options import RacOptions, ShuffleGadgets, ShuffleInfobots, ShuffleWeapons, StartingItem, StartingLocation
+from .Regions import create_regions
+
+rac_logger = logging.getLogger("Ratchet & Clank")
+rac_logger.setLevel(logging.DEBUG)
+
+
+def run_client(_url: Optional[str] = None):
+ # from .RacClient import launch
+ # launch_subprocess(launch, name="RacClient")
+ components.append(Component("Ratchet & Clank Client", func=run_client, component_type=Type.CLIENT,
+ file_identifier=SuffixIdentifier(".aprac")))
+
+
+class RacWeb(WebWorld):
+ tutorials = [Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up Ratchet & Clank for Archipelago",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["Panad"],
+ )]
+
+
+class RacItem(Item):
+ game: str = "Ratchet & Clank"
+
+
+class RacWorld(World):
+ """
+ Ratchet & Clank is a third-person shooter platform video game developed by Insomniac Games
+ and published by Sony Computer Entertainment for the PlayStation 2 in 2002. It is the first
+ game in the Ratchet & Clank series and the first game developed by Insomniac to not be owned by Universal
+ Interactive.
+ """
+ game = "Ratchet & Clank"
+ web = RacWeb()
+ options_dataclass = RacOptions
+ options: RacOptions
+ topology_present = False
+ item_name_to_id = {item.name: item.item_id for item in Items.ALL}
+ location_name_to_id = {location.name: location.location_id for location in Planets.ALL_LOCATIONS if
+ location.location_id}
+ item_name_groups = Items.get_item_groups()
+ location_name_groups = location_groups
+ item_pool: dict[str, list[Item]] = {}
+ starting_planet = Items.NOVALIS_INFOBOT.name
+ preplaced_items: list[Item] = []
+ orders: dict[str, list[int]] = {}
+
+ def get_filler_item_name(self) -> str:
+ return get_bolt_pack(self.options)
+
+ def generate_early(self) -> None:
+ #rac_logger.debug(f"_________START EARLY GENERATION____________")
+ rac_logger.warning(
+ "INCOMPLETE WORLD! Slot '%s' is using an unfinished alpha world that is not stable yet!",
+ self.player_name)
+ rac_logger.warning("INCOMPLETE WORLD! Slot '%s' may require send_location/send_item for completion!",
+ self.player_name)
+ self.item_pool: dict[str, list[Item]] = {}
+ self.starting_planet = Items.NOVALIS_INFOBOT.name
+ self.preplaced_items = []
+ #rac_logger.debug(f"Pre-placed Item List: {self.preplaced_items}")
+ #rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}")
+
+ shuffle_pools: list = [
+ self.options.shuffle_infobots,
+ self.options.shuffle_packs,
+ self.options.shuffle_gadgets,
+ self.options.shuffle_helmets,
+ self.options.shuffle_boots,
+ self.options.shuffle_weapons,
+ self.options.shuffle_extra_items,
+ self.options.shuffle_gold_weapons,
+ ]
+ disabled_pools = []
+ restricted_pools = []
+ useful_pools = []
+ enabled_pools = []
+
+ if self.options.shuffle_gold_bolts.value:
+ enabled_pools += [POOL_GOLD_BOLT]
+ else:
+ disabled_pools += [POOL_GOLD_BOLT]
+ # if self.options.shuffle_skill_points.value:
+ # enabled_pools += [POOL_SKILLPOINT]
+ # else:
+ # disabled_pools += [POOL_SKILLPOINT]
+ #rac_logger.debug(f"Iterating through Options:")
+ for pool_option in shuffle_pools:
+ #rac_logger.debug(f"Option: {pool_option}")
+ match pool_option.value:
+ case Options.ItemOptions.option_vanilla:
+ disabled_pools += [pool_option.pool]
+ case Options.ItemOptions.option_random_same:
+ restricted_pools += [pool_option.pool]
+ case Options.ItemOptions.option_random_item:
+ useful_pools += [pool_option.pool]
+ case Options.ItemOptions.option_unrestricted:
+ enabled_pools += [pool_option.pool]
+
+ if not enabled_pools:
+ #rac_logger.debug(f"No pools enabled, setting to defaults")
+ disabled_pools = [pool for pool in ALL_POOLS if pool not in DEFAULT_LIST]
+ restricted_pools = []
+ useful_pools = []
+ enabled_pools = DEFAULT_LIST
+
+ #rac_logger.debug(f"Disabled Pools: {disabled_pools}")
+ #rac_logger.debug(f"Restricted Pools: {restricted_pools}")
+ #rac_logger.debug(f"Useful Pools: {useful_pools}")
+ #rac_logger.debug(f"Enabled Pools: {enabled_pools}")
+
+ if not self.options.shuffle_gold_bolts.value:
+ self.options.pack_size_gold_bolts.value = 1
+
+ #rac_logger.debug(
+ #f"Gold Bolt Pack Size: {self.options.pack_size_gold_bolts.value}, Bolt pack size: "
+ #f"{self.options.pack_size_bolts.value}")
+
+ #rac_logger.debug(f"Choose Progression Order")
+ self.orders = {
+ "progressive_suck_cannon_order": [Items.SUCK_CANNON.item_id, Items.GOLDEN_SUCK_CANNON.item_id],
+ "progressive_bomb_glove_order": [Items.BOMB_GLOVE.item_id, Items.GOLDEN_BOMB_GLOVE.item_id],
+ "progressive_devastator_order": [Items.DEVASTATOR.item_id, Items.GOLDEN_DEVASTATOR.item_id],
+ "progressive_blaster_order": [Items.BLASTER.item_id, Items.GOLDEN_BLASTER.item_id],
+ "progressive_pyrocitor_order": [Items.PYROCITOR.item_id, Items.GOLDEN_PYROCITOR.item_id],
+ "progressive_mine_glove_order": [Items.MINE_GLOVE.item_id, Items.GOLDEN_MINE_GLOVE.item_id],
+ "progressive_tesla_claw_order": [Items.TESLA_CLAW.item_id, Items.GOLDEN_TESLA_CLAW.item_id],
+ "progressive_glove_of_doom_order": [Items.GLOVE_OF_DOOM.item_id, Items.GOLDEN_GLOVE_OF_DOOM.item_id],
+ "progressive_morph_o_ray_order": [Items.MORPH_O_RAY.item_id, Items.GOLDEN_MORPH_O_RAY.item_id],
+ "progressive_decoy_glove_order": [Items.DECOY_GLOVE.item_id, Items.GOLDEN_DECOY_GLOVE.item_id],
+ "progressive_packs_order": [Items.HELI_PACK.item_id, Items.THRUSTER_PACK.item_id, Items.HYDRO_PACK.item_id],
+ "progressive_helmets_order": [Items.O2_MASK.item_id, Items.SONIC_SUMMONER.item_id,
+ Items.PILOTS_HELMET.item_id],
+ "progressive_boots_order": [Items.GRINDBOOTS.item_id, Items.MAGNEBOOTS.item_id],
+ "progressive_hoverboard_order": [Items.HOVERBOARD.item_id, Items.ZOOMERATOR.item_id],
+ "progressive_raritanium_order": [Items.RARITANIUM.item_id, Items.PERSUADER.item_id],
+ "progressive_nanotech_order": [Items.PREMIUM_NANOTECH.item_id, Items.ULTRA_NANOTECH.item_id],
+ }
+ progression_rules(self)
+ #rac_logger.debug(f"Progression Order: {self.orders}")
+ #rac_logger.debug(f"Creating Regions")
+ create_regions(self)
+
+ #rac_logger.debug(f"___Generate Item Pool___")
+ option_list = Items.get_pool(self.options)
+ #rac_logger.debug(f"length of option_list: {len(option_list)}")
+ for item in option_list:
+ #rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}")
+ item_list = self.item_pool.get(item.name) or []
+ item_list.append(self.create_item(item.name))
+ self.item_pool[item.name] = item_list
+
+ #rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}")
+ if (self.options.shuffle_infobots == ShuffleInfobots.option_vanilla or
+ self.options.starting_location == StartingLocation.option_false):
+ starting_planet = self.item_pool[Items.NOVALIS_INFOBOT.name].pop(0)
+ else:
+ starting_planet = [planet for planet in Items.get_starting_planets(self.options)]
+ self.random.shuffle(starting_planet)
+ self.starting_planet = starting_planet[0].name
+ starting_planet = self.item_pool[starting_planet[0].name].pop(0)
+ #rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}")
+
+ if (self.options.shuffle_weapons == ShuffleWeapons.option_vanilla or
+ self.options.starting_item == StartingItem.option_vanilla):
+ starting_item = self.item_pool[check_progressive_item(self.options, Items.BOMB_GLOVE.name)].pop(0)
+ else:
+ starting_item = []
+ item_list = [item.name for item in Items.STARTING_WEAPONS]
+ if (self.options.starting_item == StartingItem.option_random_item and
+ self.options.shuffle_gadgets > ShuffleGadgets.option_random_same):
+ item_list += [item.name for item in Items.GADGETS]
+ if self.options.progressive_weapons.value is Options.GoldenWeaponProgression.option_normal:
+ item_list += [item.name for item in Items.GOLDEN_WEAPONS]
+ for name, item in self.item_pool.items():
+ if name in item_list:
+ starting_item.extend(item)
+ self.random.shuffle(starting_item)
+ starting_item = self.item_pool[starting_item[0].name].pop(0)
+
+ self.preplaced_items = [starting_item, starting_planet]
+ self.multiworld.push_precollected(starting_item)
+ self.multiworld.push_precollected(starting_planet)
+ for name, count in self.options.start_inventory:
+ #if count > len(self.item_pool[name]):
+ #rac_logger.warning(f"Too many copies of {name} in yaml start inventory! Giving only "
+ #f"{len(self.item_pool[name])} of {count} copies")
+ for _ in range(count):
+ if self.item_pool[name]:
+ self.preplaced_items += [self.item_pool[name].pop(0)]
+ else:
+ break
+
+ #rac_logger.debug(f"Starting items: {self.preplaced_items}")
+
+ #rac_logger.debug(f"___Vanilla Locations___")
+ self.preplaced_items += self.fill_pool(disabled_pools, 0)
+ #rac_logger.debug(f"___Internal Shuffled Pools___")
+ self.preplaced_items += self.fill_pool(restricted_pools, 1)
+ #rac_logger.debug(f"___Group Shuffled Pools___")
+ self.preplaced_items += self.fill_pool(useful_pools, 2)
+ #rac_logger.debug(f"Pre-placed Items placed: {self.preplaced_items}")
+ #rac_logger.debug(f"Pre-filled Locations removed: {[loc.name for loc in self.get_locations() if loc.item]}")
+ #rac_logger.debug(f"_________END EARLY GENERATION____________")
+
+ def fill_pool(self, pools, scope) -> (list, list):
+ multiworld = self.multiworld
+ placed_items = self.preplaced_items
+ # for name in self.item_pool:
+ # if Items.from_name(name).pool == POOL_SKILLPOINT:
+ # placed_items += self.item_pool[name]
+ #rac_logger.debug(f"placed_items: {placed_items}")
+ unplaced_items: list[Item] = []
+ for name, items in self.item_pool.items():
+ #rac_logger.debug(f"Checking if {name} is unplaced")
+ if items:
+ if items[0].name.endswith("Gold Bolts"):
+ placed_items += self.item_pool[name]
+ continue
+ elif items[0].name.endswith("Skill Point"):
+ continue
+ elif items[0].name.endswith("Gold Bolt"):
+ placed_items += self.item_pool[name]
+ continue
+ else:
+ #rac_logger.debug(f"Add to unplaced: {name}")
+ unplaced_items += items
+ add_items: list[Item] = []
+ match scope:
+ case 0:
+ for pool in pools:
+ #rac_logger.debug(f"Disable Pool: {pool}")
+ for loc in ALL_LOCATIONS:
+ if pool in loc.pools and loc.vanilla_item is not None:
+ if self.get_location(loc.name).item is not None:
+ raise FillError(f"Slot {self.player_name} selected vanilla {pool}, but Location:"
+ f" {loc.name} was already filled")
+ elif pool == Items.GOLD_BOLT.pool:
+ item = self.item_pool[Items.GOLD_BOLT_1.name].pop(0)
+ elif self.item_pool[loc.vanilla_item]:
+ item = self.item_pool[loc.vanilla_item].pop(0)
+ else:
+ #rac_logger.warning(f"vanilla item {loc.vanilla_item} can't be placed at {loc.name}, "
+ #f"filler bolt pack placed instead")
+ item = self.create_item(get_bolt_pack(self.options))
+ self.get_location(loc.name).place_locked_item(item)
+ add_items += [item]
+ #rac_logger.debug(f"vanilla: {loc.name}, item: {item}")
+ case 1:
+ for pool in pools:
+ if pool == POOL_GOLDEN_WEAPON and POOL_WEAPON in pools:
+ continue
+ base_state = CollectionState(multiworld)
+ item_sweep = placed_items
+ #rac_logger.debug(f"unplaced items: {unplaced_items}")
+ for item in unplaced_items:
+ #rac_logger.debug(f"check {pool} pool: {item}")
+ item_pool = Items.from_name(item.name).pool
+ if item_pool != pool:
+ #if pool == POOL_WEAPON and POOL_GOLDEN_WEAPON in pools and item_pool == POOL_GOLDEN_WEAPON:
+ #rac_logger.debug(f"Gold Weapon skipped: {item}")
+ #else:
+ #rac_logger.debug(f"add to assumed: {item}")
+ item_sweep += [item]
+ #else:
+ #rac_logger.debug(f"{item} is in pool {pool}")
+ #rac_logger.debug(f"Assumed collected: {item_sweep}")
+ base_state = sweep_from_pool(base_state, item_sweep)
+ #rac_logger.debug(f"Restricted Pool: {pool}")
+ loc_temp = []
+ item_temp = []
+ for loc in ALL_LOCATIONS:
+ if pool in loc.pools and loc.vanilla_item is not None:
+ loc_temp += [self.get_location(loc.name)]
+ if self.item_pool[loc.vanilla_item]:
+ item_temp += [self.item_pool[loc.vanilla_item].pop(0)]
+ elif (self.starting_planet != Items.NOVALIS_INFOBOT.name and pool in
+ Items.NOVALIS_INFOBOT.pool):
+ item_temp += [self.item_pool[Items.NOVALIS_INFOBOT.name].pop(0)]
+ else:
+ #rac_logger.warning(f"vanilla item {loc.vanilla_item} can't be shuffled into pool {pool}"
+ #f", filler bolt pack added instead")
+ item_temp += [self.create_item(get_bolt_pack(self.options))]
+ if pool == POOL_WEAPON and POOL_GOLDEN_WEAPON in pools:
+ if POOL_GOLDEN_WEAPON in loc.pools and loc.vanilla_item is not None:
+ loc_temp += [self.get_location(loc.name)]
+ if self.item_pool[loc.vanilla_item]:
+ item_temp += [self.item_pool[loc.vanilla_item].pop(0)]
+ else:
+ #rac_logger.warning(f"vanilla item {loc.vanilla_item} can't be shuffled into pool"
+ #f" {pool}, filler bolt pack added instead")
+ item_temp += [self.create_item(get_bolt_pack(self.options))]
+ #rac_logger.debug(f"Randomize Locations: {loc_temp}")
+ add_items += item_temp
+ self.random.shuffle(item_temp)
+ #rac_logger.debug(f"Shuffled items: {item_temp}")
+ #rac_logger.debug(f"Reachability before Shuffle: {base_state.reachable_regions}")
+ #rac_logger.debug(f"Locations already checked: {base_state.locations_checked}")
+ reachable = [loc for loc in multiworld.get_reachable_locations(base_state, self.player)
+ if loc in loc_temp]
+ #rac_logger.debug(f"Reachable Locations: {reachable}")
+ fill_restrictive(multiworld, base_state, loc_temp, item_temp, single_player_placement=True,
+ lock=False, swap=True, allow_partial=False,
+ name=f"RAC1 Restricted Item Fill: {pool}")
+ # for item in item_temp:
+ # add_items.remove(item)
+ # if item_temp:
+ # for loc in placed_locations:
+ # rac_logger.debug(f"same group: {loc.name}, item: {loc.item}")
+ # raise FillError(f"Slot {self.player_name} selected shuffle {pool} only among themselves, "
+ # f"but Items: {item_temp} could not get placed at Locations: {loc_temp}")
+ case 2:
+ loc_temp = []
+ item_temp = []
+ item_sweep = placed_items
+ base_state = CollectionState(multiworld)
+ for pool in pools:
+ #rac_logger.debug(f"add Pool: {pool}")
+ for loc in ALL_LOCATIONS:
+ if pool in loc.pools and loc.vanilla_item is not None:
+ loc_temp += [self.get_location(loc.name)]
+ if loc_temp:
+ base_state = sweep_from_pool(base_state, item_sweep)
+ #rac_logger.debug(f"Randomizing Useful Locations: {loc_temp}")
+ self.random.shuffle(unplaced_items)
+ for i in range(len(loc_temp)):
+ item_temp += [self.item_pool[unplaced_items[i].name].pop(0)]
+ #rac_logger.debug(f"Shuffled items: {item_temp}")
+ add_items += item_temp
+ #rac_logger.debug(f"Reachability before Shuffle: {base_state.reachable_regions}")
+ reachable = [loc for loc in multiworld.get_reachable_locations(base_state, self.player)
+ if loc in loc_temp]
+ #rac_logger.debug(f"Reachable Locations: {reachable}")
+
+ fill_restrictive(multiworld, base_state, loc_temp, item_temp, single_player_placement=True,
+ lock=False, swap=True, allow_partial=True, name="RAC1 Useful Item Fill")
+ for item in item_temp:
+ add_items.remove(item)
+ item_list = self.item_pool.get(item.name) or []
+ item_list.append(self.create_item(item.name))
+ self.item_pool[item.name] = item_list
+ # if loc_temp:
+ # for loc in placed_locations:
+ # rac_logger.debug(f"any item: {loc.name}, item: {loc.item}")
+ # if item_temp:
+ # raise FillError(f"Slot {self.player_name} has locations requiring useful items that are "
+ # f"unfilled, Items: {item_temp} could not get placed at Locations: {loc_temp}")
+ # else:
+ # raise FillError(f"Slot {self.player_name} has locations requiring useful items that are "
+ # f"unfilled, No items left to get placed at Locations: {loc_temp}")
+ return add_items
+
+ def create_item(self, name: str, override: Optional[ItemClassification] = None) -> "Item":
+ new_name = Items.check_progressive_item(self.options, name)
+ #if new_name is not name:
+ #rac_logger.warning(f"Item {name} was not initially set to its progressive item: {new_name}")
+ #if name == Items.GOLD_BOLT or name == Items.BOLT_PACK:
+ #rac_logger.warning(f"{name} should not be in the item pool!!! Please report")
+ if override:
+ return RacItem(new_name, override, self.item_name_to_id[new_name], self.player)
+ item_data = Items.from_name(new_name)
+ return RacItem(new_name, ItemPool.get_classification(item_data), self.item_name_to_id[new_name], self.player)
+
+ def create_event(self, name: str) -> "Item":
+ return RacItem(name, ItemClassification.progression, None, self.player)
+
+ def get_pre_fill_items(self) -> list["Item"]:
+ #rac_logger.debug(f"fetching preplaced_items")
+ items = self.preplaced_items
+ return items
+
+ def create_items(self) -> None:
+ #rac_logger.debug(f"_________START ITEM CREATION__________")
+ #rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}")
+ items_to_add: list[Item] = []
+ for items in self.item_pool.values():
+ items_to_add.extend(items)
+
+ # add bolt packs in whatever slots we have left
+ unfilled = [i for i in self.multiworld.get_unfilled_locations(self.player) if not i.is_event]
+ #rac_logger.debug(f"Items:{len(items_to_add)}, Locations:{len(unfilled)}")
+ remain = len(unfilled) - len(items_to_add)
+ if remain < 0:
+ #rac_logger.debug(f"Items unplaced: {items_to_add}")
+ #rac_logger.debug(f"Locations unfilled: {self.multiworld.get_unfilled_locations(self.player)}")
+ raise FillError(f"Item Count: {len(items_to_add)} exceeds Location count: "
+ f"{len(self.multiworld.get_unfilled_locations(self.player))}")
+ elif remain == 0:
+ pass
+ else:
+ #rac_logger.debug(f"Not enough items to fill all locations. Adding {remain} filler items to the item pool")
+ for _ in range(remain):
+ items_to_add.append(self.create_item(get_bolt_pack(self.options)))
+ #rac_logger.debug(f"Add item pool to multiworld: {items_to_add}")
+ self.multiworld.itempool.extend(items_to_add)
+ #rac_logger.debug(f"_________END ITEM CREATION__________")
+
+ def set_rules(self) -> None:
+ boss_location = self.multiworld.get_location(Locations.VELDIN_DREK.name, self.player)
+ boss_location.place_locked_item(self.create_event("Victory"))
+ self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
+ # def generate_output(self, output_directory: str) -> None:
+ # aprac2 = Rac2ProcedurePatch(player=self.player, player_name=self.multiworld.get_player_name(self.player))
+ # generate_patch(self, aprac2)
+ # rom_path = os.path.join(output_directory,
+ # f"{self.multiworld.get_out_file_name_base(self.player)}{
+ # aprac2.patch_file_ending}")
+ # aprac2.write(rom_path)
+
+ def fill_slot_data(self) -> Mapping[str, Any]:
+ slot_data: dict[str, Any] = {}
+ slot_data |= Options.get_options_as_dict(self.options)
+ slot_data["starting_planet"] = self.item_name_to_id[self.starting_planet]
+ for item, value in self.orders.items():
+ slot_data[item] = value
+ return slot_data
+
+ # def post_fill(self) -> None:
+ # from Utils import visualize_regions
+ # visualize_regions(self.multiworld.get_region("Menu", self.player), f"{self.player_name}_world.puml",
+ # regions_to_highlight=self.multiworld.get_all_state(False).reachable_regions[
+ # self.player])
diff --git a/worlds/RAC1/archipelago.json b/worlds/RAC1/archipelago.json
new file mode 100644
index 000000000000..9741fca42b2b
--- /dev/null
+++ b/worlds/RAC1/archipelago.json
@@ -0,0 +1 @@
+{"compatible_version": 7, "version": 7, "game": "Ratchet & Clank"}
\ No newline at end of file
diff --git a/worlds/RAC1/data/Items.py b/worlds/RAC1/data/Items.py
new file mode 100644
index 000000000000..8f6cfa6c2002
--- /dev/null
+++ b/worlds/RAC1/data/Items.py
@@ -0,0 +1,1056 @@
+from dataclasses import dataclass
+from typing import Mapping, Sequence
+
+from BaseClasses import Item
+from worlds.RAC1 import Options
+
+
+@dataclass
+class ItemData(Item):
+ item_id: int
+ name: str
+ pool: str
+ quantity: int = 1
+
+
+HELI_PACK = ItemData(2, "Heli Pack", "Packs")
+THRUSTER_PACK = ItemData(3, "Thruster Pack", "Packs")
+HYDRO_PACK = ItemData(4, "Hydro Pack", "Packs")
+SONIC_SUMMONER = ItemData(5, "Sonic Summoner", "Helmets")
+O2_MASK = ItemData(6, "O2 Mask", "Helmets")
+PILOTS_HELMET = ItemData(7, "Pilots Helmet", "Helmets")
+# WRENCH = ItemData(8, "Wrench", "?")
+SUCK_CANNON = ItemData(9, "Suck cannon", "Weapons")
+BOMB_GLOVE = ItemData(10, "Bomb glove", "Weapons")
+DEVASTATOR = ItemData(11, "Devastator", "Weapons")
+SWINGSHOT = ItemData(12, "Swingshot", "Gadgets")
+VISIBOMB = ItemData(13, "Visibomb", "Weapons")
+TAUNTER = ItemData(14, "Taunter", "Weapons")
+BLASTER = ItemData(15, "Blaster", "Weapons")
+PYROCITOR = ItemData(16, "Pyrocitor", "Weapons")
+MINE_GLOVE = ItemData(17, "Mine glove", "Weapons")
+WALLOPER = ItemData(18, "Walloper", "Weapons")
+TESLA_CLAW = ItemData(19, "Tesla claw", "Weapons")
+GLOVE_OF_DOOM = ItemData(20, "Glove of doom", "Weapons")
+MORPH_O_RAY = ItemData(21, "Morph-o-ray", "Weapons")
+HYDRODISPLACER = ItemData(22, "Hydrodisplacer", "Gadgets")
+RYNO = ItemData(23, "RYNO", "Weapons")
+DRONE_DEVICE = ItemData(24, "Drone device", "Weapons")
+DECOY_GLOVE = ItemData(25, "Decoy glove", "Weapons")
+TRESPASSER = ItemData(26, "Trespasser", "Gadgets")
+METAL_DETECTOR = ItemData(27, "Metal Detector", "Gadgets")
+MAGNEBOOTS = ItemData(28, "Magneboots", "Boots")
+GRINDBOOTS = ItemData(29, "Grindboots", "Boots")
+HOVERBOARD = ItemData(30, "Hoverboard", "ExtraItems")
+HOLOGUISE = ItemData(31, "Hologuise", "Gadgets")
+PDA = ItemData(32, "PDA", "Gadgets")
+MAP_O_MATIC = ItemData(33, "Map-o-Matic", "ExtraItems")
+BOLT_GRABBER = ItemData(34, "Bolt Grabber", "ExtraItems")
+PERSUADER = ItemData(35, "Persuader", "ExtraItems")
+
+ZOOMERATOR = ItemData(48, "Zoomerator", "ExtraItems")
+RARITANIUM = ItemData(49, "Raritanium", "ExtraItems")
+CODEBOT = ItemData(50, "Codebot", "ExtraItems")
+PREMIUM_NANOTECH = ItemData(52, "Premium nanotech", "ExtraItems")
+ULTRA_NANOTECH = ItemData(53, "Ultra nanotech", "ExtraItems")
+
+GOLDEN_SUCK_CANNON = ItemData(309, "Golden Suck Cannon", "GoldenWeapons")
+GOLDEN_BOMB_GLOVE = ItemData(310, "Golden Bomb glove", "GoldenWeapons")
+GOLDEN_DEVASTATOR = ItemData(311, "Golden Devastator", "GoldenWeapons")
+GOLDEN_BLASTER = ItemData(315, "Golden Blaster", "GoldenWeapons")
+GOLDEN_PYROCITOR = ItemData(316, "Golden Pyrocitor", "GoldenWeapons")
+GOLDEN_MINE_GLOVE = ItemData(317, "Golden Mine glove", "GoldenWeapons")
+GOLDEN_TESLA_CLAW = ItemData(319, "Golden Tesla claw", "GoldenWeapons")
+GOLDEN_GLOVE_OF_DOOM = ItemData(320, "Golden Glove of doom", "GoldenWeapons")
+GOLDEN_MORPH_O_RAY = ItemData(321, "Golden Morph-o-ray", "GoldenWeapons")
+GOLDEN_DECOY_GLOVE = ItemData(325, "Golden Decoy glove", "GoldenWeapons")
+
+PROGRESSIVE_PACK = ItemData(80, "Progressive Pack", "Packs")
+PROGRESSIVE_HELMET = ItemData(81, "Progressive Helmet", "Helmets")
+PROGRESSIVE_SUCK = ItemData(82, "Progressive Suck Cannon", "Weapons")
+PROGRESSIVE_BOMB = ItemData(83, "Progressive Bomb glove", "Weapons")
+PROGRESSIVE_DEVASTATOR = ItemData(84, "Progressive Devastator", "Weapons")
+PROGRESSIVE_BLASTER = ItemData(85, "Progressive Blaster", "Weapons")
+PROGRESSIVE_PYROCITOR = ItemData(86, "Progressive Pyrocitor", "Weapons")
+PROGRESSIVE_MINE = ItemData(87, "Progressive Mine glove", "Weapons")
+PROGRESSIVE_TESLA = ItemData(88, "Progressive Tesla claw", "Weapons")
+PROGRESSIVE_DOOM = ItemData(89, "Progressive Glove of doom", "Weapons")
+PROGRESSIVE_MORPH = ItemData(90, "Progressive Morph-o-ray", "Weapons")
+PROGRESSIVE_DECOY = ItemData(91, "Progressive Decoy glove", "Weapons")
+PROGRESSIVE_BOOT = ItemData(92, "Progressive Boots", "Boots")
+PROGRESSIVE_HOVERBOARD = ItemData(93, "Progressive Hoverboard", "ExtraItems")
+PROGRESSIVE_TRADE = ItemData(94, "Progressive Raritanium", "ExtraItems")
+PROGRESSIVE_NANOTECH = ItemData(95, "Progressive Nanotech", "ExtraItems")
+
+NOVALIS_INFOBOT = ItemData(101, "Novalis", "Infobots")
+ARIDIA_INFOBOT = ItemData(102, "Aridia", "Infobots")
+KERWAN_INFOBOT = ItemData(103, "Kerwan", "Infobots")
+EUDORA_INFOBOT = ItemData(104, "Eudora", "Infobots")
+RILGAR_INFOBOT = ItemData(105, "Rilgar", "Infobots")
+BLARG_INFOBOT = ItemData(106, "Blarg", "Infobots")
+UMBRIS_INFOBOT = ItemData(107, "Umbris", "Infobots")
+BATALIA_INFOBOT = ItemData(108, "Batalia", "Infobots")
+GASPAR_INFOBOT = ItemData(109, "Gaspar", "Infobots")
+ORXON_INFOBOT = ItemData(110, "Orxon", "Infobots")
+POKITARU_INFOBOT = ItemData(111, "Pokitaru", "Infobots")
+HOVEN_INFOBOT = ItemData(112, "Hoven", "Infobots")
+GEMLIK_INFOBOT = ItemData(113, "Gemlik", "Infobots")
+OLTANIS_INFOBOT = ItemData(114, "Oltanis", "Infobots")
+QUARTU_INFOBOT = ItemData(115, "Quartu", "Infobots")
+KALEBO_INFOBOT = ItemData(116, "Kalebo III", "Infobots")
+FLEET_INFOBOT = ItemData(117, "Drek's Fleet", "Infobots")
+VELDIN_INFOBOT = ItemData(118, "Veldin", "Infobots")
+
+TAKE_AIM = ItemData(200, "Take Aim: Skill Point", "Skillpoint")
+SWING_IT = ItemData(201, "Swing it!: Skill Point", "Skillpoint")
+TRANSPORTED = ItemData(202, "Transported: Skill Point", "Skillpoint")
+STRIKE_A_POSE = ItemData(203, "Strike a pose: Skill Point", "Skillpoint")
+BLIMPY = ItemData(204, "Blimpy: Skill Point", "Skillpoint")
+QWARKTASTIC = ItemData(205, "Qwarktastic: Skill Point", "Skillpoint")
+ANY_TEN = ItemData(206, "Any Ten: Skill Point", "Skillpoint")
+TRICKY = ItemData(207, "Tricky: Skill Point", "Skillpoint")
+CLUCK_CLUCK = ItemData(208, "Cluck, Cluck: Skill Point", "Skillpoint")
+SPEEDY = ItemData(209, "Speedy: Skill Point", "Skillpoint")
+GIRL_TROUBLE = ItemData(210, "Girl Trouble: Skill Point", "Skillpoint")
+JUMPER = ItemData(211, "Jumper: Skill Point", "Skillpoint")
+ACCURACY_COUNTS = ItemData(212, "Accuracy Counts: Skill Point", "Skillpoint")
+EAT_LEAD = ItemData(213, "Eat Lead: Skill Point", "Skillpoint")
+DESTROYED = ItemData(214, "Destroyed: Skill Point", "Skillpoint")
+GUNNER = ItemData(215, "Gunner: Skill Point", "Skillpoint")
+SNIPER = ItemData(216, "Sniper: Skill Point", "Skillpoint")
+HEY_OVER_HERE = ItemData(217, "Hey, Over Here!: Skill Point", "Skillpoint")
+ALIEN_INVASION = ItemData(218, "Alien Invasion: Skill Point", "Skillpoint")
+BURIED_TREASURE = ItemData(219, "Buried Treasure: Skill Point", "Skillpoint")
+PEST_CONTROL = ItemData(220, "Pest Control: Skill Point", "Skillpoint")
+WHIRLYBIRDS = ItemData(221, "Whirlybirds: Skill Point", "Skillpoint")
+SITTING_DUCKS = ItemData(222, "Sitting Ducks: Skill Point", "Skillpoint")
+SHATTERED_GLASS = ItemData(223, "Shattered Glass: Skill Point", "Skillpoint")
+BLAST_EM = ItemData(224, "Blast Em!: Skill Point", "Skillpoint")
+HEAVY_TRAFFIC = ItemData(225, "Heavy Traffic: Skill Point", "Skillpoint")
+MAGICIAN = ItemData(226, "Magician: Skill Point", "Skillpoint")
+SNEAKY = ItemData(227, "Sneaky: Skill Point", "Skillpoint")
+CAREFUL_CRUISE = ItemData(228, "Careful Cruise: Skill Point", "Skillpoint")
+GOING_COMMANDO = ItemData(229, "Going Commando: Skill Point", "Skillpoint")
+
+
+@dataclass
+class CollectableData(ItemData):
+ max_capacity: int = 0x7F
+
+
+# Collectables
+GOLD_BOLT = ItemData(261, "Generic Gold Bolt", "GoldBolts")
+GOLD_BOLT_1 = ItemData(262, "1 Gold Bolt", "GoldBolts", 1)
+GOLD_BOLT_2 = ItemData(263, "2 Gold Bolts", "GoldBolts", 2)
+GOLD_BOLT_3 = ItemData(264, "3 Gold Bolts", "GoldBolts", 3)
+GOLD_BOLT_4 = ItemData(265, "4 Gold Bolts", "GoldBolts", 4)
+GOLD_BOLT_5 = ItemData(266, "5 Gold Bolts", "GoldBolts", 5)
+GOLD_BOLT_6 = ItemData(267, "6 Gold Bolts", "GoldBolts", 6)
+GOLD_BOLT_7 = ItemData(268, "7 Gold Bolts", "GoldBolts", 7)
+GOLD_BOLT_8 = ItemData(269, "8 Gold Bolts", "GoldBolts", 8)
+GOLD_BOLT_9 = ItemData(270, "9 Gold Bolts", "GoldBolts", 9)
+GOLD_BOLT_10 = ItemData(271, "10 Gold Bolts", "GoldBolts", 10)
+GOLD_BOLT_11 = ItemData(272, "11 Gold Bolts", "GoldBolts", 11)
+GOLD_BOLT_12 = ItemData(273, "12 Gold Bolts", "GoldBolts", 12)
+GOLD_BOLT_13 = ItemData(274, "13 Gold Bolts", "GoldBolts", 13)
+GOLD_BOLT_14 = ItemData(275, "14 Gold Bolts", "GoldBolts", 14)
+GOLD_BOLT_15 = ItemData(276, "15 Gold Bolts", "GoldBolts", 15)
+GOLD_BOLT_16 = ItemData(277, "16 Gold Bolts", "GoldBolts", 16)
+GOLD_BOLT_17 = ItemData(278, "17 Gold Bolts", "GoldBolts", 17)
+GOLD_BOLT_18 = ItemData(279, "18 Gold Bolts", "GoldBolts", 18)
+GOLD_BOLT_19 = ItemData(280, "19 Gold Bolts", "GoldBolts", 19)
+GOLD_BOLT_20 = ItemData(281, "20 Gold Bolts", "GoldBolts", 20)
+GOLD_BOLT_21 = ItemData(282, "21 Gold Bolts", "GoldBolts", 21)
+GOLD_BOLT_22 = ItemData(283, "22 Gold Bolts", "GoldBolts", 22)
+GOLD_BOLT_23 = ItemData(284, "23 Gold Bolts", "GoldBolts", 23)
+GOLD_BOLT_24 = ItemData(285, "24 Gold Bolts", "GoldBolts", 24)
+GOLD_BOLT_25 = ItemData(286, "25 Gold Bolts", "GoldBolts", 25)
+GOLD_BOLT_26 = ItemData(287, "26 Gold Bolts", "GoldBolts", 26)
+GOLD_BOLT_27 = ItemData(288, "27 Gold Bolts", "GoldBolts", 27)
+GOLD_BOLT_28 = ItemData(289, "28 Gold Bolts", "GoldBolts", 28)
+GOLD_BOLT_29 = ItemData(290, "29 Gold Bolts", "GoldBolts", 29)
+GOLD_BOLT_30 = ItemData(291, "30 Gold Bolts", "GoldBolts", 30)
+GOLD_BOLT_31 = ItemData(292, "31 Gold Bolts", "GoldBolts", 31)
+GOLD_BOLT_32 = ItemData(293, "32 Gold Bolts", "GoldBolts", 32)
+GOLD_BOLT_33 = ItemData(294, "33 Gold Bolts", "GoldBolts", 33)
+GOLD_BOLT_34 = ItemData(295, "34 Gold Bolts", "GoldBolts", 34)
+GOLD_BOLT_35 = ItemData(296, "35 Gold Bolts", "GoldBolts", 35)
+GOLD_BOLT_36 = ItemData(297, "36 Gold Bolts", "GoldBolts", 36)
+GOLD_BOLT_37 = ItemData(298, "37 Gold Bolts", "GoldBolts", 37)
+GOLD_BOLT_38 = ItemData(299, "38 Gold Bolts", "GoldBolts", 38)
+GOLD_BOLT_39 = ItemData(300, "39 Gold Bolts", "GoldBolts", 39)
+GOLD_BOLT_40 = ItemData(301, "40 Gold Bolts", "GoldBolts", 40)
+
+BOLT_PACK = ItemData(302, "Generic Bolt Pack", "Filler")
+BOLT_PACK_0 = ItemData(400, "Nothing", "Filler", 0)
+BOLT_PACK_1 = ItemData(401, "A single bolt", "Filler", 1)
+BOLT_PACK_2 = ItemData(402, "10 bolts", "Filler", 10)
+BOLT_PACK_3 = ItemData(403, "100 bolts", "Filler", 100)
+BOLT_PACK_4 = ItemData(404, "250 bolts", "Filler", 250)
+BOLT_PACK_5 = ItemData(405, "500 bolts", "Filler", 500)
+BOLT_PACK_6 = ItemData(406, "750 bolts", "Filler", 750)
+BOLT_PACK_7 = ItemData(407, "1,000 bolts", "Filler", 1000)
+BOLT_PACK_8 = ItemData(408, "2,000 bolts", "Filler", 2000)
+BOLT_PACK_9 = ItemData(409, "3,000 bolts", "Filler", 3000)
+BOLT_PACK_10 = ItemData(410, "4,000 bolts", "Filler", 4000)
+BOLT_PACK_11 = ItemData(411, "5,000 bolts", "Filler", 5000)
+BOLT_PACK_12 = ItemData(412, "6,000 bolts", "Filler", 6000)
+BOLT_PACK_13 = ItemData(413, "7,000 bolts", "Filler", 7000)
+BOLT_PACK_14 = ItemData(414, "8,000 bolts", "Filler", 8000)
+BOLT_PACK_15 = ItemData(415, "9,000 bolts", "Filler", 9000)
+BOLT_PACK_16 = ItemData(416, "10,000 bolts", "Filler", 10000)
+BOLT_PACK_17 = ItemData(417, "12,500 bolts", "Filler", 12500)
+BOLT_PACK_18 = ItemData(418, "15,000 bolts", "Filler", 15000)
+BOLT_PACK_19 = ItemData(419, "17,500 bolts", "Filler", 17500)
+BOLT_PACK_20 = ItemData(420, "20,000 bolts", "Filler", 20000)
+BOLT_PACK_21 = ItemData(421, "25,000 bolts", "Filler", 25000)
+BOLT_PACK_22 = ItemData(422, "30,000 bolts", "Filler", 30000)
+BOLT_PACK_23 = ItemData(423, "40,000 bolts", "Filler", 40000)
+BOLT_PACK_24 = ItemData(424, "50,000 bolts", "Filler", 50000)
+BOLT_PACK_25 = ItemData(425, "75,000 bolts", "Filler", 75000)
+BOLT_PACK_26 = ItemData(426, "100,000 bolts", "Filler", 100000)
+
+WEAPONS: Sequence[ItemData] = [
+ TAUNTER,
+ VISIBOMB,
+ WALLOPER,
+ RYNO,
+ DRONE_DEVICE,
+]
+
+NON_PROGRESSIVE_WEAPONS: Sequence[ItemData] = [
+ SUCK_CANNON,
+ BOMB_GLOVE,
+ DEVASTATOR,
+ BLASTER,
+ PYROCITOR,
+ MINE_GLOVE,
+ TESLA_CLAW,
+ GLOVE_OF_DOOM,
+ MORPH_O_RAY,
+ DECOY_GLOVE,
+]
+
+PROGRESSIVE_WEAPONS: Sequence[ItemData] = [
+ PROGRESSIVE_SUCK,
+ PROGRESSIVE_BOMB,
+ PROGRESSIVE_DEVASTATOR,
+ PROGRESSIVE_BLASTER,
+ PROGRESSIVE_PYROCITOR,
+ PROGRESSIVE_MINE,
+ PROGRESSIVE_TESLA,
+ PROGRESSIVE_DOOM,
+ PROGRESSIVE_MORPH,
+ PROGRESSIVE_DECOY,
+]
+
+GOLDEN_WEAPONS: Sequence[ItemData] = [
+ GOLDEN_SUCK_CANNON,
+ GOLDEN_BOMB_GLOVE,
+ GOLDEN_DEVASTATOR,
+ GOLDEN_BLASTER,
+ GOLDEN_PYROCITOR,
+ GOLDEN_MINE_GLOVE,
+ GOLDEN_TESLA_CLAW,
+ GOLDEN_GLOVE_OF_DOOM,
+ GOLDEN_MORPH_O_RAY,
+ GOLDEN_DECOY_GLOVE,
+]
+
+PROGRESSIVE_GOLDEN_WEAPONS: Sequence[ItemData] = [
+ GOLDEN_SUCK_CANNON,
+ GOLDEN_BOMB_GLOVE,
+ GOLDEN_DEVASTATOR,
+ GOLDEN_BLASTER,
+ GOLDEN_PYROCITOR,
+ GOLDEN_MINE_GLOVE,
+ GOLDEN_TESLA_CLAW,
+ GOLDEN_GLOVE_OF_DOOM,
+ GOLDEN_MORPH_O_RAY,
+ GOLDEN_DECOY_GLOVE,
+]
+
+GADGETS: Sequence[ItemData] = [
+ HYDRODISPLACER,
+ TRESPASSER,
+ METAL_DETECTOR,
+ HOLOGUISE,
+ PDA,
+ SWINGSHOT,
+]
+
+PACKS: Sequence[ItemData] = [
+ HELI_PACK,
+ THRUSTER_PACK,
+ HYDRO_PACK,
+]
+
+PROGRESSIVE_PACKS: Sequence[ItemData] = [
+ *[PROGRESSIVE_PACK] * 3,
+]
+
+HELMETS: Sequence[ItemData] = [
+ SONIC_SUMMONER,
+ O2_MASK,
+ PILOTS_HELMET,
+]
+
+PROGRESSIVE_HELMETS: Sequence[ItemData] = [
+ *[PROGRESSIVE_HELMET] * 3,
+]
+
+BOOTS: Sequence[ItemData] = [
+ MAGNEBOOTS,
+ GRINDBOOTS,
+]
+
+PROGRESSIVE_BOOTS: Sequence[ItemData] = [
+ *[PROGRESSIVE_BOOT] * 2,
+]
+
+EXTRA_ITEMS: Sequence[ItemData] = [
+ MAP_O_MATIC,
+ BOLT_GRABBER,
+ CODEBOT,
+]
+
+NON_PROGRESSIVE_HOVERBOARDS: Sequence[ItemData] = [
+ HOVERBOARD,
+ ZOOMERATOR,
+]
+
+PROGRESSIVE_HOVERBOARDS: Sequence[ItemData] = [
+ *[PROGRESSIVE_HOVERBOARD] * 2,
+]
+
+NON_PROGRESSIVE_TRADES: Sequence[ItemData] = [
+ PERSUADER,
+ RARITANIUM,
+]
+
+PROGRESSIVE_TRADES: Sequence[ItemData] = [
+ *[PROGRESSIVE_TRADE] * 2,
+]
+
+NON_PROGRESSIVE_NANOTECHS: Sequence[ItemData] = [
+ PREMIUM_NANOTECH,
+ ULTRA_NANOTECH,
+]
+
+PROGRESSIVE_NANOTECHS: Sequence[ItemData] = [
+ *[PROGRESSIVE_NANOTECH] * 2,
+]
+
+GOLD_BOLTS: Sequence[ItemData] = [
+ GOLD_BOLT,
+ GOLD_BOLT_1,
+ GOLD_BOLT_2,
+ GOLD_BOLT_3,
+ GOLD_BOLT_4,
+ GOLD_BOLT_5,
+ GOLD_BOLT_6,
+ GOLD_BOLT_7,
+ GOLD_BOLT_8,
+ GOLD_BOLT_9,
+ GOLD_BOLT_10,
+ GOLD_BOLT_11,
+ GOLD_BOLT_12,
+ GOLD_BOLT_13,
+ GOLD_BOLT_14,
+ GOLD_BOLT_15,
+ GOLD_BOLT_16,
+ GOLD_BOLT_17,
+ GOLD_BOLT_18,
+ GOLD_BOLT_19,
+ GOLD_BOLT_20,
+ GOLD_BOLT_21,
+ GOLD_BOLT_22,
+ GOLD_BOLT_23,
+ GOLD_BOLT_24,
+ GOLD_BOLT_25,
+ GOLD_BOLT_26,
+ GOLD_BOLT_27,
+ GOLD_BOLT_28,
+ GOLD_BOLT_29,
+ GOLD_BOLT_30,
+ GOLD_BOLT_31,
+ GOLD_BOLT_32,
+ GOLD_BOLT_33,
+ GOLD_BOLT_34,
+ GOLD_BOLT_35,
+ GOLD_BOLT_36,
+ GOLD_BOLT_37,
+ GOLD_BOLT_38,
+ GOLD_BOLT_39,
+ GOLD_BOLT_40,
+]
+
+BOLT_PACKS: Sequence[ItemData] = [
+ BOLT_PACK,
+ BOLT_PACK_0,
+ BOLT_PACK_1,
+ BOLT_PACK_2,
+ BOLT_PACK_3,
+ BOLT_PACK_4,
+ BOLT_PACK_5,
+ BOLT_PACK_6,
+ BOLT_PACK_7,
+ BOLT_PACK_8,
+ BOLT_PACK_9,
+ BOLT_PACK_10,
+ BOLT_PACK_11,
+ BOLT_PACK_12,
+ BOLT_PACK_13,
+ BOLT_PACK_14,
+ BOLT_PACK_15,
+ BOLT_PACK_16,
+ BOLT_PACK_17,
+ BOLT_PACK_18,
+ BOLT_PACK_19,
+ BOLT_PACK_20,
+ BOLT_PACK_21,
+ BOLT_PACK_22,
+ BOLT_PACK_23,
+ BOLT_PACK_24,
+ BOLT_PACK_25,
+ BOLT_PACK_26,
+]
+
+PLANETS: Sequence[ItemData] = [
+ NOVALIS_INFOBOT,
+ ARIDIA_INFOBOT,
+ KERWAN_INFOBOT,
+ EUDORA_INFOBOT,
+ RILGAR_INFOBOT,
+ BLARG_INFOBOT,
+ UMBRIS_INFOBOT,
+ BATALIA_INFOBOT,
+ GASPAR_INFOBOT,
+ ORXON_INFOBOT,
+ POKITARU_INFOBOT,
+ HOVEN_INFOBOT,
+ GEMLIK_INFOBOT,
+ OLTANIS_INFOBOT,
+ QUARTU_INFOBOT,
+ KALEBO_INFOBOT,
+ FLEET_INFOBOT,
+ VELDIN_INFOBOT,
+]
+
+STARTING_PLANETS: Sequence[ItemData] = [
+ NOVALIS_INFOBOT,
+ KERWAN_INFOBOT,
+ BLARG_INFOBOT,
+ BATALIA_INFOBOT,
+ ORXON_INFOBOT,
+]
+
+SKILLPOINTS: Sequence[ItemData] = [
+ TAKE_AIM,
+ SWING_IT,
+ TRANSPORTED,
+ STRIKE_A_POSE,
+ BLIMPY,
+ QWARKTASTIC,
+ ANY_TEN,
+ TRICKY,
+ CLUCK_CLUCK,
+ SPEEDY,
+ GIRL_TROUBLE,
+ JUMPER,
+ ACCURACY_COUNTS,
+ EAT_LEAD,
+ DESTROYED,
+ GUNNER,
+ SNIPER,
+ HEY_OVER_HERE,
+ ALIEN_INVASION,
+ BURIED_TREASURE,
+ PEST_CONTROL,
+ WHIRLYBIRDS,
+ SITTING_DUCKS,
+ SHATTERED_GLASS,
+ BLAST_EM,
+ HEAVY_TRAFFIC,
+ MAGICIAN,
+ SNEAKY,
+ CAREFUL_CRUISE,
+ GOING_COMMANDO,
+]
+
+ALL: Sequence[ItemData] = [*WEAPONS, *NON_PROGRESSIVE_WEAPONS, *PROGRESSIVE_WEAPONS, *GOLDEN_WEAPONS,
+ *GADGETS, *PACKS, *PROGRESSIVE_PACKS, *HELMETS, *PROGRESSIVE_HELMETS, *BOOTS,
+ *PROGRESSIVE_BOOTS, *EXTRA_ITEMS, *NON_PROGRESSIVE_HOVERBOARDS, *PROGRESSIVE_HOVERBOARDS,
+ *NON_PROGRESSIVE_TRADES, *PROGRESSIVE_TRADES, *NON_PROGRESSIVE_NANOTECHS,
+ *PROGRESSIVE_NANOTECHS, *GOLD_BOLTS, *PLANETS, *SKILLPOINTS, *BOLT_PACKS]
+
+ITEM_POOL: Sequence[ItemData] = [*PLANETS, *WEAPONS, *GADGETS, *EXTRA_ITEMS] # *SKILLPOINTS
+
+STARTING_WEAPONS: Sequence[ItemData] = [*WEAPONS, *NON_PROGRESSIVE_WEAPONS, *PROGRESSIVE_WEAPONS,
+ *PROGRESSIVE_GOLDEN_WEAPONS]
+ALL_WEAPONS: Sequence[ItemData] = [*STARTING_WEAPONS, *GOLDEN_WEAPONS]
+ALL_PACKS: Sequence[ItemData] = [*PACKS, *PROGRESSIVE_PACKS]
+ALL_HELMETS: Sequence[ItemData] = [*HELMETS, *PROGRESSIVE_HELMETS]
+ALL_BOOTS: Sequence[ItemData] = [*BOOTS, *PROGRESSIVE_BOOTS]
+ALL_EXTRA_ITEMS: Sequence[ItemData] = [*EXTRA_ITEMS, *NON_PROGRESSIVE_HOVERBOARDS, *PROGRESSIVE_HOVERBOARDS,
+ *NON_PROGRESSIVE_TRADES, *PROGRESSIVE_TRADES, *NON_PROGRESSIVE_NANOTECHS,
+ *PROGRESSIVE_NANOTECHS]
+ALL_HOVERBOARD: Sequence[ItemData] = [*NON_PROGRESSIVE_HOVERBOARDS, *PROGRESSIVE_HOVERBOARDS]
+ALL_TRADE: Sequence[ItemData] = [*NON_PROGRESSIVE_TRADES, *PROGRESSIVE_TRADES]
+ALL_NANOTECH: Sequence[ItemData] = [*NON_PROGRESSIVE_NANOTECHS, *PROGRESSIVE_NANOTECHS]
+ALL_STARTING: Sequence[ItemData] = [*STARTING_WEAPONS, *GADGETS]
+
+SUCK_GROUP: Sequence[ItemData] = [SUCK_CANNON, GOLDEN_SUCK_CANNON, PROGRESSIVE_SUCK]
+BOMB_GROUP: Sequence[ItemData] = [BOMB_GLOVE, GOLDEN_BOMB_GLOVE, PROGRESSIVE_BOMB]
+DEVASTATOR_GROUP: Sequence[ItemData] = [DEVASTATOR, GOLDEN_DEVASTATOR, PROGRESSIVE_DEVASTATOR]
+BLASTER_GROUP: Sequence[ItemData] = [BLASTER, GOLDEN_BLASTER, PROGRESSIVE_BLASTER]
+PYROCITOR_GROUP: Sequence[ItemData] = [PYROCITOR, GOLDEN_PYROCITOR, PROGRESSIVE_PYROCITOR]
+MINE_GROUP: Sequence[ItemData] = [MINE_GLOVE, GOLDEN_MINE_GLOVE, PROGRESSIVE_MINE]
+TESLA_GROUP: Sequence[ItemData] = [TESLA_CLAW, GOLDEN_TESLA_CLAW, PROGRESSIVE_TESLA]
+DOOM_GROUP: Sequence[ItemData] = [GLOVE_OF_DOOM, GOLDEN_GLOVE_OF_DOOM, PROGRESSIVE_DOOM]
+MORPH_GROUP: Sequence[ItemData] = [MORPH_O_RAY, GOLDEN_MORPH_O_RAY, PROGRESSIVE_MORPH]
+DECOY_GROUP: Sequence[ItemData] = [DECOY_GLOVE, GOLDEN_DECOY_GLOVE, PROGRESSIVE_DECOY]
+
+PROG: dict[str, Mapping[str, int]] = {
+ HELI_PACK.name: {HELI_PACK.name: 1},
+ THRUSTER_PACK.name: {THRUSTER_PACK.name: 1},
+ HYDRO_PACK.name: {HYDRO_PACK.name: 1},
+ SONIC_SUMMONER.name: {SONIC_SUMMONER.name: 1, PROGRESSIVE_HELMET.name: 2},
+ O2_MASK.name: {O2_MASK.name: 1, PROGRESSIVE_HELMET.name: 1},
+ PILOTS_HELMET.name: {PILOTS_HELMET.name: 1, PROGRESSIVE_HELMET.name: 3},
+ SUCK_CANNON.name: {SUCK_CANNON.name: 1},
+ GOLDEN_SUCK_CANNON.name: {SUCK_CANNON.name: 1, GOLDEN_SUCK_CANNON.name: 1},
+ BOMB_GLOVE.name: {BOMB_GLOVE.name: 1},
+ GOLDEN_BOMB_GLOVE.name: {BOMB_GLOVE.name: 1, GOLDEN_BOMB_GLOVE.name: 1},
+ DEVASTATOR.name: {DEVASTATOR.name: 1},
+ GOLDEN_DEVASTATOR.name: {DEVASTATOR.name: 1, GOLDEN_DEVASTATOR.name: 1},
+ BLASTER.name: {BLASTER.name: 1},
+ GOLDEN_BLASTER.name: {BLASTER.name: 1, GOLDEN_BLASTER.name: 1},
+ PYROCITOR.name: {PYROCITOR.name: 1},
+ GOLDEN_PYROCITOR.name: {PYROCITOR.name: 1, GOLDEN_PYROCITOR.name: 1},
+ MINE_GLOVE.name: {MINE_GLOVE.name: 1},
+ GOLDEN_MINE_GLOVE.name: {MINE_GLOVE.name: 1, GOLDEN_MINE_GLOVE.name: 1},
+ TESLA_CLAW.name: {TESLA_CLAW.name: 1},
+ GOLDEN_TESLA_CLAW.name: {TESLA_CLAW.name: 1, GOLDEN_TESLA_CLAW.name: 1},
+ GLOVE_OF_DOOM.name: {GLOVE_OF_DOOM.name: 1},
+ GOLDEN_GLOVE_OF_DOOM.name: {GLOVE_OF_DOOM.name: 1, GOLDEN_GLOVE_OF_DOOM.name: 1},
+ MORPH_O_RAY.name: {MORPH_O_RAY.name: 1},
+ GOLDEN_MORPH_O_RAY.name: {MORPH_O_RAY.name: 1, GOLDEN_MORPH_O_RAY.name: 1},
+ DECOY_GLOVE.name: {DECOY_GLOVE.name: 1},
+ GOLDEN_DECOY_GLOVE.name: {DECOY_GLOVE.name: 1, GOLDEN_DECOY_GLOVE.name: 1},
+ MAGNEBOOTS.name: {MAGNEBOOTS.name: 1, PROGRESSIVE_BOOT.name: 2},
+ GRINDBOOTS.name: {GRINDBOOTS.name: 1, PROGRESSIVE_BOOT.name: 1},
+ HOVERBOARD.name: {HOVERBOARD.name: 1, PROGRESSIVE_HOVERBOARD.name: 1},
+ ZOOMERATOR.name: {ZOOMERATOR.name: 1, PROGRESSIVE_HOVERBOARD.name: 2},
+ PERSUADER.name: {PERSUADER.name: 1, PROGRESSIVE_TRADE.name: 1},
+ RARITANIUM.name: {RARITANIUM.name: 1, PROGRESSIVE_TRADE.name: 2},
+ PREMIUM_NANOTECH.name: {PREMIUM_NANOTECH.name: 1, PROGRESSIVE_NANOTECH.name: 1},
+ ULTRA_NANOTECH.name: {ULTRA_NANOTECH.name: 1, PROGRESSIVE_NANOTECH.name: 2},
+}
+
+
+def get_bolt_pack(options: Options) -> str:
+ lookup: dict[int, str] = {
+ BOLT_PACK_0.quantity: BOLT_PACK_0.name,
+ BOLT_PACK_1.quantity: BOLT_PACK_1.name,
+ BOLT_PACK_2.quantity: BOLT_PACK_2.name,
+ BOLT_PACK_3.quantity: BOLT_PACK_3.name,
+ BOLT_PACK_4.quantity: BOLT_PACK_4.name,
+ BOLT_PACK_5.quantity: BOLT_PACK_5.name,
+ BOLT_PACK_6.quantity: BOLT_PACK_6.name,
+ BOLT_PACK_7.quantity: BOLT_PACK_7.name,
+ BOLT_PACK_8.quantity: BOLT_PACK_8.name,
+ BOLT_PACK_9.quantity: BOLT_PACK_9.name,
+ BOLT_PACK_10.quantity: BOLT_PACK_10.name,
+ BOLT_PACK_11.quantity: BOLT_PACK_11.name,
+ BOLT_PACK_12.quantity: BOLT_PACK_12.name,
+ BOLT_PACK_13.quantity: BOLT_PACK_13.name,
+ BOLT_PACK_14.quantity: BOLT_PACK_14.name,
+ BOLT_PACK_15.quantity: BOLT_PACK_15.name,
+ BOLT_PACK_16.quantity: BOLT_PACK_16.name,
+ BOLT_PACK_17.quantity: BOLT_PACK_17.name,
+ BOLT_PACK_18.quantity: BOLT_PACK_18.name,
+ BOLT_PACK_19.quantity: BOLT_PACK_19.name,
+ BOLT_PACK_20.quantity: BOLT_PACK_20.name,
+ BOLT_PACK_21.quantity: BOLT_PACK_21.name,
+ BOLT_PACK_22.quantity: BOLT_PACK_22.name,
+ BOLT_PACK_23.quantity: BOLT_PACK_23.name,
+ BOLT_PACK_24.quantity: BOLT_PACK_24.name,
+ BOLT_PACK_25.quantity: BOLT_PACK_25.name,
+ BOLT_PACK_26.quantity: BOLT_PACK_26.name,
+ }
+ return lookup[options.pack_size_bolts.value]
+
+
+def get_gold_bolts(options: Options) -> str:
+ lookup: dict[int, str] = {}
+ for gold_bolt in GOLD_BOLTS:
+ if gold_bolt.name.startswith("Generic"):
+ continue
+ lookup.update({gold_bolt.quantity: gold_bolt.name})
+ return lookup[options.pack_size_gold_bolts.value]
+
+
+def progression_rules(world):
+ match world.options.progressive_weapons.value:
+ case Options.GoldenWeaponProgression.option_normal:
+ PROG[SUCK_CANNON.name] = {SUCK_CANNON.name: 1, GOLDEN_SUCK_CANNON.name: 1}
+ PROG[GOLDEN_SUCK_CANNON.name] = {GOLDEN_SUCK_CANNON.name: 1}
+ PROG[BOMB_GLOVE.name] = {BOMB_GLOVE.name: 1, GOLDEN_BOMB_GLOVE.name: 1}
+ PROG[GOLDEN_BOMB_GLOVE.name] = {GOLDEN_BOMB_GLOVE.name: 1}
+ PROG[DEVASTATOR.name] = {DEVASTATOR.name: 1, GOLDEN_DEVASTATOR.name: 1}
+ PROG[GOLDEN_DEVASTATOR.name] = {GOLDEN_DEVASTATOR.name: 1}
+ PROG[BLASTER.name] = {BLASTER.name: 1, GOLDEN_BLASTER.name: 1}
+ PROG[GOLDEN_BLASTER.name] = {GOLDEN_BLASTER.name: 1}
+ PROG[PYROCITOR.name] = {PYROCITOR.name: 1, GOLDEN_PYROCITOR.name: 1}
+ PROG[GOLDEN_PYROCITOR.name] = {GOLDEN_PYROCITOR.name: 1}
+ PROG[MINE_GLOVE.name] = {MINE_GLOVE.name: 1, GOLDEN_MINE_GLOVE.name: 1}
+ PROG[GOLDEN_MINE_GLOVE.name] = {GOLDEN_MINE_GLOVE.name: 1}
+ PROG[TESLA_CLAW.name] = {TESLA_CLAW.name: 1, GOLDEN_TESLA_CLAW.name: 1}
+ PROG[GOLDEN_TESLA_CLAW.name] = {GOLDEN_TESLA_CLAW.name: 1}
+ PROG[GLOVE_OF_DOOM.name] = {GLOVE_OF_DOOM.name: 1, GOLDEN_GLOVE_OF_DOOM.name: 1}
+ PROG[GOLDEN_GLOVE_OF_DOOM.name] = {GOLDEN_GLOVE_OF_DOOM.name: 1}
+ PROG[MORPH_O_RAY.name] = {MORPH_O_RAY.name: 1, GOLDEN_MORPH_O_RAY.name: 1}
+ PROG[GOLDEN_MORPH_O_RAY.name] = {GOLDEN_MORPH_O_RAY.name: 1}
+ PROG[DECOY_GLOVE.name] = {DECOY_GLOVE.name: 1, GOLDEN_DECOY_GLOVE.name: 1}
+ PROG[GOLDEN_DECOY_GLOVE.name] = {GOLDEN_DECOY_GLOVE.name: 1}
+ case Options.GoldenWeaponProgression.option_progressive:
+ PROG[SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1}
+ PROG[GOLDEN_SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 2}
+ PROG[BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1}
+ PROG[GOLDEN_BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 2}
+ PROG[DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1}
+ PROG[GOLDEN_DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 2}
+ PROG[BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1}
+ PROG[GOLDEN_BLASTER.name] = {PROGRESSIVE_BLASTER.name: 2}
+ PROG[PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1}
+ PROG[GOLDEN_PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 2}
+ PROG[MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1}
+ PROG[GOLDEN_MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 2}
+ PROG[TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1}
+ PROG[GOLDEN_TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 2}
+ PROG[GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1}
+ PROG[GOLDEN_GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 2}
+ PROG[MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1}
+ PROG[GOLDEN_MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 2}
+ PROG[DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1}
+ PROG[GOLDEN_DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 2}
+ case Options.GoldenWeaponProgression.option_progressive_reversed:
+ world.orders["progressive_suck_cannon_order"].reverse()
+ PROG[SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1}
+ PROG[GOLDEN_SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1}
+ world.orders["progressive_bomb_glove_order"].reverse()
+ PROG[BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1}
+ PROG[GOLDEN_BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1}
+ world.orders["progressive_devastator_order"].reverse()
+ PROG[DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1}
+ PROG[GOLDEN_DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1}
+ world.orders["progressive_blaster_order"].reverse()
+ PROG[BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1}
+ PROG[GOLDEN_BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1}
+ world.orders["progressive_pyrocitor_order"].reverse()
+ PROG[PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1}
+ PROG[GOLDEN_PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1}
+ world.orders["progressive_mine_glove_order"].reverse()
+ PROG[MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1}
+ PROG[GOLDEN_MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1}
+ world.orders["progressive_tesla_claw_order"].reverse()
+ PROG[TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1}
+ PROG[GOLDEN_TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1}
+ world.orders["progressive_glove_of_doom_order"].reverse()
+ PROG[GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1}
+ PROG[GOLDEN_GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1}
+ world.orders["progressive_morph_o_ray_order"].reverse()
+ PROG[MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1}
+ PROG[GOLDEN_MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1}
+ world.orders["progressive_decoy_glove_order"].reverse()
+ PROG[DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1}
+ PROG[GOLDEN_DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1}
+ case Options.GoldenWeaponProgression.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_suck_cannon_order"])
+ PROG[SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1}
+ PROG[GOLDEN_SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1 + world.orders[
+ "progressive_suck_cannon_order"].index(GOLDEN_SUCK_CANNON.item_id)}
+ world.random.shuffle(world.orders["progressive_bomb_glove_order"])
+ PROG[BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1}
+ PROG[GOLDEN_BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1 + world.orders[
+ "progressive_bomb_glove_order"].index(GOLDEN_BOMB_GLOVE.item_id)}
+ world.random.shuffle(world.orders["progressive_devastator_order"])
+ PROG[DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1}
+ PROG[GOLDEN_DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1 + world.orders[
+ "progressive_devastator_order"].index(GOLDEN_DEVASTATOR.item_id)}
+ world.random.shuffle(world.orders["progressive_blaster_order"])
+ PROG[BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1}
+ PROG[GOLDEN_BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1 + world.orders[
+ "progressive_blaster_order"].index(GOLDEN_BLASTER.item_id)}
+ world.random.shuffle(world.orders["progressive_pyrocitor_order"])
+ PROG[PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1}
+ PROG[GOLDEN_PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1 + world.orders[
+ "progressive_pyrocitor_order"].index(GOLDEN_PYROCITOR.item_id)}
+ world.random.shuffle(world.orders["progressive_mine_glove_order"])
+ PROG[MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1}
+ PROG[GOLDEN_MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1 + world.orders[
+ "progressive_mine_glove_order"].index(GOLDEN_MINE_GLOVE.item_id)}
+ world.random.shuffle(world.orders["progressive_tesla_claw_order"])
+ PROG[TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1}
+ PROG[GOLDEN_TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1 + world.orders[
+ "progressive_tesla_claw_order"].index(TESLA_CLAW.item_id)}
+ world.random.shuffle(world.orders["progressive_glove_of_doom_order"])
+ PROG[GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1}
+ PROG[GOLDEN_GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1 + world.orders[
+ "progressive_glove_of_doom_order"].index(GOLDEN_GLOVE_OF_DOOM.item_id)}
+ world.random.shuffle(world.orders["progressive_morph_o_ray_order"])
+ PROG[MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1}
+ PROG[GOLDEN_MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1 + world.orders[
+ "progressive_morph_o_ray_order"].index(GOLDEN_MORPH_O_RAY.item_id)}
+ world.random.shuffle(world.orders["progressive_decoy_glove_order"])
+ PROG[DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1}
+ PROG[GOLDEN_DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1 + world.orders[
+ "progressive_decoy_glove_order"].index(GOLDEN_DECOY_GLOVE.item_id)}
+ case _:
+ pass
+
+ match world.options.progressive_packs.value:
+ case Options.ProgressiveOptions.option_progressive:
+ PROG[HELI_PACK.name] = {PROGRESSIVE_PACK.name: 1}
+ PROG[THRUSTER_PACK.name] = {PROGRESSIVE_PACK.name: 2}
+ PROG[HYDRO_PACK.name] = {PROGRESSIVE_PACK.name: 3}
+ case Options.ProgressiveOptions.option_progressive_reversed:
+ world.orders["progressive_packs_order"].reverse()
+ PROG[HELI_PACK.name] = {PROGRESSIVE_PACK.name: 3}
+ PROG[THRUSTER_PACK.name] = {PROGRESSIVE_PACK.name: 2}
+ PROG[HYDRO_PACK.name] = {PROGRESSIVE_PACK.name: 1}
+ case Options.ProgressiveOptions.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_packs_order"])
+ PROG[HELI_PACK.name] = {
+ PROGRESSIVE_PACK.name: 1 + world.orders["progressive_packs_order"].index(HELI_PACK.item_id)}
+ PROG[THRUSTER_PACK.name] = {
+ PROGRESSIVE_PACK.name: 1 + world.orders["progressive_packs_order"].index(THRUSTER_PACK.item_id)}
+ PROG[HYDRO_PACK.name] = {
+ PROGRESSIVE_PACK.name: 1 + world.orders["progressive_packs_order"].index(HYDRO_PACK.item_id)}
+ case _:
+ pass
+
+ match world.options.progressive_helmets.value:
+ case Options.ProgressiveOptions.option_progressive:
+ PROG[O2_MASK.name] = {PROGRESSIVE_HELMET.name: 1}
+ if world.options.shuffle_helmets.value <= Options.ItemOptions.option_random_same:
+ PROG[SONIC_SUMMONER.name] = {PROGRESSIVE_HELMET.name: 3}
+ PROG[PILOTS_HELMET.name] = {PROGRESSIVE_HELMET.name: 2}
+ world.orders["progressive_helmets_order"] = [O2_MASK.item_id, PILOTS_HELMET.item_id,
+ SONIC_SUMMONER.item_id]
+ else:
+ PROG[SONIC_SUMMONER.name] = {PROGRESSIVE_HELMET.name: 2}
+ PROG[PILOTS_HELMET.name] = {PROGRESSIVE_HELMET.name: 3}
+ case Options.ProgressiveOptions.option_progressive_reversed:
+ world.orders["progressive_helmets_order"].reverse()
+ PROG[O2_MASK.name] = {PROGRESSIVE_HELMET.name: 3}
+ PROG[SONIC_SUMMONER.name] = {PROGRESSIVE_HELMET.name: 2}
+ PROG[PILOTS_HELMET.name] = {PROGRESSIVE_HELMET.name: 1}
+ case Options.ProgressiveOptions.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_helmets_order"])
+ PROG[O2_MASK.name] = {
+ PROGRESSIVE_HELMET.name: 1 + world.orders["progressive_helmets_order"].index(O2_MASK.item_id)}
+ PROG[SONIC_SUMMONER.name] = {
+ PROGRESSIVE_HELMET.name: 1 + world.orders["progressive_helmets_order"].index(SONIC_SUMMONER.item_id)}
+ PROG[PILOTS_HELMET.name] = {
+ PROGRESSIVE_HELMET.name: 1 + world.orders["progressive_helmets_order"].index(PILOTS_HELMET.item_id)}
+ if (world.options.shuffle_helmets.value <= Options.ItemOptions.option_random_same and
+ PROG[PILOTS_HELMET.name].values() == 3):
+ temp = PROG[PILOTS_HELMET.name]
+ PROG[PILOTS_HELMET.name] = PROG[SONIC_SUMMONER.name]
+ PROG[SONIC_SUMMONER.name] = temp
+ if world.orders["progressive_helmets_order"].index(O2_MASK.item_id) == 0:
+ world.orders["progressive_helmets_order"] = [O2_MASK.item_id, PILOTS_HELMET.item_id,
+ SONIC_SUMMONER.item_id]
+ else:
+ world.orders["progressive_helmets_order"] = [PILOTS_HELMET.item_id, O2_MASK.item_id,
+ SONIC_SUMMONER.item_id]
+
+ case _:
+ pass
+
+ match world.options.progressive_boots.value:
+ case Options.ProgressiveOptions.option_progressive:
+ PROG[GRINDBOOTS.name] = {PROGRESSIVE_BOOT.name: 1}
+ PROG[MAGNEBOOTS.name] = {PROGRESSIVE_BOOT.name: 2}
+ case Options.ProgressiveOptions.option_progressive_reversed:
+ world.orders["progressive_boots_order"].reverse()
+ PROG[GRINDBOOTS.name] = {PROGRESSIVE_BOOT.name: 2}
+ PROG[MAGNEBOOTS.name] = {PROGRESSIVE_BOOT.name: 1}
+ case Options.ProgressiveOptions.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_boots_order"])
+ PROG[GRINDBOOTS.name] = {
+ PROGRESSIVE_BOOT.name: 1 + world.orders["progressive_boots_order"].index(GRINDBOOTS.item_id)}
+ PROG[MAGNEBOOTS.name] = {
+ PROGRESSIVE_BOOT.name: 1 + world.orders["progressive_boots_order"].index(MAGNEBOOTS.item_id)}
+ case _:
+ pass
+
+ if world.options.shuffle_extra_items.value == Options.ItemOptions.option_vanilla:
+ PROG[HOVERBOARD.name] = {HOVERBOARD.name: 1, PROGRESSIVE_HOVERBOARD.name: 1}
+ PROG[ZOOMERATOR.name] = {ZOOMERATOR.name: 1, PROGRESSIVE_HOVERBOARD.name: 2}
+ else:
+ match world.options.progressive_hoverboard.value:
+ case Options.ProgressiveOptions.option_progressive:
+ PROG[HOVERBOARD.name] = {PROGRESSIVE_HOVERBOARD.name: 1}
+ PROG[ZOOMERATOR.name] = {PROGRESSIVE_HOVERBOARD.name: 2}
+ case Options.ProgressiveOptions.option_progressive_reversed:
+ world.orders["progressive_hoverboard_order"].reverse()
+ PROG[HOVERBOARD.name] = {PROGRESSIVE_HOVERBOARD.name: 2}
+ PROG[ZOOMERATOR.name] = {PROGRESSIVE_HOVERBOARD.name: 1}
+ case Options.ProgressiveOptions.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_hoverboard_order"])
+ PROG[HOVERBOARD.name] = {
+ PROGRESSIVE_HOVERBOARD.name: 1 + world.orders["progressive_hoverboard_order"].index(HOVERBOARD.item_id)}
+ PROG[ZOOMERATOR.name] = {
+ PROGRESSIVE_HOVERBOARD.name: 1 + world.orders["progressive_hoverboard_order"].index(ZOOMERATOR.item_id)}
+ case _:
+ pass
+ if world.options.shuffle_extra_items.value == Options.ItemOptions.option_vanilla:
+ PROG[RARITANIUM.name] = {RARITANIUM.name: 1, PROGRESSIVE_TRADE.name: 1}
+ PROG[PERSUADER.name] = {PERSUADER.name: 1, PROGRESSIVE_TRADE.name: 2}
+ else:
+ match world.options.progressive_raritanium.value:
+ case Options.ProgressiveOptions.option_progressive:
+ PROG[RARITANIUM.name] = {PROGRESSIVE_TRADE.name: 1}
+ PROG[PERSUADER.name] = {PROGRESSIVE_TRADE.name: 2}
+ case Options.ProgressiveOptions.option_progressive_reversed:
+ world.orders["progressive_raritanium_order"].reverse()
+ PROG[RARITANIUM.name] = {PROGRESSIVE_TRADE.name: 2}
+ PROG[PERSUADER.name] = {PROGRESSIVE_TRADE.name: 1}
+ case Options.ProgressiveOptions.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_raritanium_order"])
+ PROG[RARITANIUM.name] = {
+ PROGRESSIVE_TRADE.name: 1 + world.orders["progressive_raritanium_order"].index(RARITANIUM.item_id)}
+ PROG[PERSUADER.name] = {
+ PROGRESSIVE_TRADE.name: 1 + world.orders["progressive_raritanium_order"].index(PERSUADER.item_id)}
+ case _:
+ pass
+
+ match world.options.progressive_nanotech.value:
+ case Options.ProgressiveOptions.option_progressive:
+ PROG[PREMIUM_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1}
+ PROG[ULTRA_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 2}
+ case Options.ProgressiveOptions.option_progressive_reversed:
+ world.orders["progressive_nanotech_order"].reverse()
+ PROG[PREMIUM_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 2}
+ PROG[ULTRA_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1}
+ case Options.ProgressiveOptions.option_progressive_random:
+ world.random.shuffle(world.orders["progressive_nanotech_order"])
+ PROG[PREMIUM_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1 + world.orders[
+ "progressive_nanotech_order"].index(PREMIUM_NANOTECH.item_id)}
+ PROG[ULTRA_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1 + world.orders[
+ "progressive_nanotech_order"].index(ULTRA_NANOTECH.item_id)}
+ case _:
+ pass
+ return
+
+
+def get_pool(options) -> Sequence[ItemData]:
+ pool = []
+ for item in ITEM_POOL:
+ pool += [item]
+ if options.progressive_weapons.value > Options.GoldenWeaponProgression.option_normal:
+ for item in PROGRESSIVE_WEAPONS:
+ pool += [item, item]
+ else:
+ for item in NON_PROGRESSIVE_WEAPONS:
+ pool += [item]
+ for item in GOLDEN_WEAPONS:
+ pool += [item]
+ if options.progressive_packs.value > Options.ProgressiveOptions.option_vanilla:
+ for item in PROGRESSIVE_PACKS:
+ pool += [item]
+ else:
+ for item in PACKS:
+ pool += [item]
+ if options.progressive_helmets.value > Options.ProgressiveOptions.option_vanilla:
+ for item in PROGRESSIVE_HELMETS:
+ pool += [item]
+ else:
+ for item in HELMETS:
+ pool += [item]
+ if options.progressive_boots.value > Options.ProgressiveOptions.option_vanilla:
+ for item in PROGRESSIVE_BOOTS:
+ pool += [item]
+ else:
+ for item in BOOTS:
+ pool += [item]
+ if options.progressive_hoverboard.value > Options.ProgressiveOptions.option_vanilla:
+ for item in PROGRESSIVE_HOVERBOARDS:
+ pool += [item]
+ else:
+ for item in NON_PROGRESSIVE_HOVERBOARDS:
+ pool += [item]
+ if options.progressive_raritanium.value > Options.ProgressiveOptions.option_vanilla:
+ for item in PROGRESSIVE_TRADES:
+ pool += [item]
+ else:
+ for item in NON_PROGRESSIVE_TRADES:
+ pool += [item]
+ if options.progressive_nanotech.value > Options.ProgressiveOptions.option_vanilla:
+ for item in PROGRESSIVE_NANOTECHS:
+ pool += [item]
+ else:
+ for item in NON_PROGRESSIVE_NANOTECHS:
+ pool += [item]
+ lookup: dict[int, tuple[ItemData, int]] = {
+ 1: (GOLD_BOLT_1, 40),
+ 2: (GOLD_BOLT_2, 30),
+ 3: (GOLD_BOLT_3, 20),
+ 4: (GOLD_BOLT_4, 15),
+ 5: (GOLD_BOLT_5, 12),
+ 6: (GOLD_BOLT_6, 10),
+ 7: (GOLD_BOLT_7, 9),
+ 8: (GOLD_BOLT_8, 8),
+ 9: (GOLD_BOLT_9, 7),
+ 10: (GOLD_BOLT_10, 6),
+ 11: (GOLD_BOLT_11, 5),
+ 12: (GOLD_BOLT_12, 5),
+ 13: (GOLD_BOLT_13, 5),
+ 14: (GOLD_BOLT_14, 4),
+ 15: (GOLD_BOLT_15, 4),
+ 16: (GOLD_BOLT_16, 4),
+ 17: (GOLD_BOLT_17, 4),
+ 18: (GOLD_BOLT_18, 4),
+ 19: (GOLD_BOLT_19, 4),
+ 20: (GOLD_BOLT_20, 3),
+ 21: (GOLD_BOLT_21, 3),
+ 22: (GOLD_BOLT_22, 3),
+ 23: (GOLD_BOLT_23, 3),
+ 24: (GOLD_BOLT_24, 3),
+ 25: (GOLD_BOLT_25, 3),
+ 26: (GOLD_BOLT_26, 3),
+ 27: (GOLD_BOLT_27, 3),
+ 28: (GOLD_BOLT_28, 3),
+ 29: (GOLD_BOLT_29, 3),
+ 30: (GOLD_BOLT_30, 2),
+ 31: (GOLD_BOLT_31, 2),
+ 32: (GOLD_BOLT_32, 2),
+ 33: (GOLD_BOLT_33, 2),
+ 34: (GOLD_BOLT_34, 2),
+ 35: (GOLD_BOLT_35, 2),
+ 36: (GOLD_BOLT_36, 2),
+ 37: (GOLD_BOLT_37, 2),
+ 38: (GOLD_BOLT_38, 2),
+ 39: (GOLD_BOLT_39, 2),
+ 40: (GOLD_BOLT_40, 1),
+ }
+ for _ in range(lookup[options.pack_size_gold_bolts.value][1]):
+ pool += [lookup[options.pack_size_gold_bolts.value][0]]
+ return pool
+
+
+def get_starting_planets(options) -> Sequence[ItemData]:
+ planets: Sequence[ItemData] = []
+ for item in STARTING_PLANETS:
+ planets += [item]
+ if options.shuffle_infobots.value >= Options.ShuffleInfobots.option_unrestricted:
+ planets += [ARIDIA_INFOBOT]
+ if options.shuffle_helmets.value >= Options.ShuffleHelmets.option_unrestricted:
+ planets += [GASPAR_INFOBOT]
+ if options.shuffle_gold_bolts.value:
+ planets += [HOVEN_INFOBOT]
+ return planets
+
+
+def from_id(item_id: int) -> ItemData:
+ matching = [item for item in ALL if item.item_id == item_id]
+ if len(matching) == 0:
+ raise ValueError(f"No item data for item id '{item_id}'")
+ assert len(matching) < 2, f"{len(matching)} item data found with id '{item_id}'. Items are: {matching}"
+ return matching[0]
+
+
+def from_name(item_name: str) -> ItemData:
+ matching = [item for item in ALL if item.name == item_name]
+ if len(matching) == 0:
+ raise ValueError(f"No item data for '{item_name}'")
+ # if item_name != GOLD_BOLT.name:
+ # assert len(matching) < 2, f"Multiple item data with name '{item_name}'. Please report."
+ return matching[0]
+
+
+def get_item_groups() -> dict[str, set[str]]:
+ groups: dict[str, set[str]] = {
+ "Weapons": {w.name for w in ALL_WEAPONS},
+ "Gadgets": {g.name for g in GADGETS},
+ "Packs": {p.name for p in ALL_PACKS},
+ "Helmets": {h.name for h in ALL_HELMETS},
+ "Boots": {b.name for b in ALL_BOOTS},
+ "ExtraItems": {e.name for e in ALL_EXTRA_ITEMS},
+ "GoldBolts": {c.name for c in GOLD_BOLTS},
+ "Infobots": {i.name for i in PLANETS},
+ "Skillpoints": {s.name for s in SKILLPOINTS},
+ }
+ return groups
+
+
+def check_progressive_item(options, item) -> str:
+ new_item = item
+ match from_name(item).pool:
+ case SUCK_CANNON.pool | GOLDEN_SUCK_CANNON.pool:
+ if options.progressive_weapons.value > Options.GoldenWeaponProgression.option_normal:
+ match item:
+ case SUCK_CANNON.name:
+ new_item = PROGRESSIVE_SUCK.name
+ case GOLDEN_SUCK_CANNON.name:
+ new_item = PROGRESSIVE_SUCK.name
+ case BOMB_GLOVE.name:
+ new_item = PROGRESSIVE_BOMB.name
+ case GOLDEN_BOMB_GLOVE.name:
+ new_item = PROGRESSIVE_BOMB.name
+ case DEVASTATOR.name:
+ new_item = PROGRESSIVE_DEVASTATOR.name
+ case GOLDEN_DEVASTATOR.name:
+ new_item = PROGRESSIVE_DEVASTATOR.name
+ case BLASTER.name:
+ new_item = PROGRESSIVE_BLASTER.name
+ case GOLDEN_BLASTER.name:
+ new_item = PROGRESSIVE_BLASTER.name
+ case PYROCITOR.name:
+ new_item = PROGRESSIVE_PYROCITOR.name
+ case GOLDEN_PYROCITOR.name:
+ new_item = PROGRESSIVE_PYROCITOR.name
+ case MINE_GLOVE.name:
+ new_item = PROGRESSIVE_MINE.name
+ case GOLDEN_MINE_GLOVE.name:
+ new_item = PROGRESSIVE_MINE.name
+ case TESLA_CLAW.name:
+ new_item = PROGRESSIVE_TESLA.name
+ case GOLDEN_TESLA_CLAW.name:
+ new_item = PROGRESSIVE_TESLA.name
+ case GLOVE_OF_DOOM.name:
+ new_item = PROGRESSIVE_DOOM.name
+ case GOLDEN_GLOVE_OF_DOOM.name:
+ new_item = PROGRESSIVE_DOOM.name
+ case MORPH_O_RAY.name:
+ new_item = PROGRESSIVE_MORPH.name
+ case GOLDEN_MORPH_O_RAY.name:
+ new_item = PROGRESSIVE_MORPH.name
+ case DECOY_GLOVE.name:
+ new_item = PROGRESSIVE_DECOY.name
+ case GOLDEN_DECOY_GLOVE.name:
+ new_item = PROGRESSIVE_DECOY.name
+ case HELI_PACK.pool:
+ if options.progressive_packs.value:
+ new_item = PROGRESSIVE_PACK.name
+ case O2_MASK.pool:
+ if options.progressive_helmets.value:
+ new_item = PROGRESSIVE_HELMET.name
+ case GRINDBOOTS.pool:
+ if options.progressive_boots.value:
+ new_item = PROGRESSIVE_BOOT.name
+ case HOVERBOARD.pool:
+ match item:
+ case HOVERBOARD.name | ZOOMERATOR.name:
+ if options.progressive_hoverboard.value:
+ new_item = PROGRESSIVE_HOVERBOARD.name
+ case RARITANIUM.name | PERSUADER.name:
+ if options.progressive_raritanium.value:
+ new_item = PROGRESSIVE_TRADE.name
+ case PREMIUM_NANOTECH.name | ULTRA_NANOTECH.name:
+ if options.progressive_nanotech.value:
+ new_item = PROGRESSIVE_NANOTECH.name
+ return new_item
diff --git a/worlds/RAC1/data/Locations.py b/worlds/RAC1/data/Locations.py
new file mode 100644
index 000000000000..623a1d75d96f
--- /dev/null
+++ b/worlds/RAC1/data/Locations.py
@@ -0,0 +1,277 @@
+from dataclasses import dataclass
+from typing import Callable, Optional
+
+from ..Logic import *
+
+POOL_START_PLANET: str = "StartPlanet"
+POOL_START_ITEM: str = "StartItem"
+POOL_WEAPON: str = "Weapons"
+POOL_GOLDEN_WEAPON: str = "GoldenWeapons"
+POOL_GADGET: str = "Gadgets"
+POOL_PACK: str = "Packs"
+POOL_HELMET: str = "Helmets"
+POOL_BOOT: str = "Boots"
+POOL_EXTRA_ITEM: str = "ExtraItems"
+POOL_GOLD_BOLT: str = "GoldBolts"
+POOL_INFOBOT: str = "Infobots"
+POOL_SKILLPOINT: str = "Skillpoint"
+
+DEFAULT_LIST = list([POOL_WEAPON, POOL_GADGET, POOL_PACK, POOL_HELMET, POOL_BOOT, POOL_EXTRA_ITEM, POOL_GOLD_BOLT,
+ POOL_INFOBOT])
+ALL_POOLS = list([POOL_START_PLANET, POOL_START_ITEM, POOL_WEAPON, POOL_GOLDEN_WEAPON, POOL_GADGET, POOL_PACK,
+ POOL_HELMET, POOL_BOOT, POOL_EXTRA_ITEM, POOL_GOLD_BOLT, POOL_INFOBOT, POOL_SKILLPOINT])
+
+
+# noinspection PyCompatibility
+@dataclass
+class LocationData:
+ location_id: Optional[int]
+ planet: str
+ name: str
+ vanilla_item: Optional[str]
+ pools: set[str] = frozenset()
+ """All of these must be enabled for this spot to be randomized"""
+ access_rule: Optional[Callable[[CollectionState, int], bool]] = None
+
+
+# Novalis
+NOVALIS_PLUMBER = LocationData(1, "Novalis", "Novalis: Plumber", Items.ARIDIA_INFOBOT.name, {POOL_INFOBOT})
+NOVALIS_MAYOR = LocationData(2, "Novalis", "Novalis: Chairman", Items.KERWAN_INFOBOT.name, {POOL_INFOBOT})
+NOVALIS_VENDOR_PYROCITOR = LocationData(3, "Novalis", "Novalis: Vendor - 2,500", Items.PYROCITOR.name, {POOL_WEAPON})
+NOVALIS_SEWER_GOLD_BOLT = LocationData(
+ 4, "Novalis", "Novalis: Gold Bolt: Waterworks", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT})
+NOVALIS_CAVES_GOLD_BOLT = LocationData(
+ 5, "Novalis", "Novalis: Gold Bolt: Caves", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_explosive_weapon)
+NOVALIS_UNDERWATER_CAVES_GOLD_BOLT = LocationData(6, "Novalis", "Novalis: Gold Bolt: Amoeboid Caves",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, novalis_underwater_caves_rule)
+# Golden Weapon Locations
+NOVALIS_GOLD_WEAPON_1 = LocationData(100, "Novalis", "Novalis: Golden Weapon 1 - 60,000",
+ Items.GOLDEN_TESLA_CLAW.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_2 = LocationData(95, "Novalis", "Novalis: Golden Weapon 2 - 20,000",
+ Items.GOLDEN_BOMB_GLOVE.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_3 = LocationData(101, "Novalis", "Novalis: Golden Weapon 3 - 60,000",
+ Items.GOLDEN_DEVASTATOR.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_4 = LocationData(96, "Novalis", "Novalis: Golden Weapon 4 - 30,000",
+ Items.GOLDEN_PYROCITOR.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_5 = LocationData(102, "Novalis", "Novalis: Golden Weapon 5 - 10,000",
+ Items.GOLDEN_MINE_GLOVE.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_6 = LocationData(97, "Novalis", "Novalis: Golden Weapon 6 - 20,000",
+ Items.GOLDEN_BLASTER.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_7 = LocationData(103, "Novalis", "Novalis: Golden Weapon 7 - 20,000",
+ Items.GOLDEN_MORPH_O_RAY.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_8 = LocationData(98, "Novalis", "Novalis: Golden Weapon 8 - 10,000",
+ Items.GOLDEN_GLOVE_OF_DOOM.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_9 = LocationData(104, "Novalis", "Novalis: Golden Weapon 9 - 10,000",
+ Items.GOLDEN_DECOY_GLOVE.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+NOVALIS_GOLD_WEAPON_10 = LocationData(99, "Novalis", "Novalis: Golden Weapon 10 - 10,000",
+ Items.GOLDEN_SUCK_CANNON.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule)
+# TODO: Skillpoint Locations
+# Skill Point Locations
+NOVALIS_SKILLPOINT = LocationData(
+ 105, "Novalis", "Novalis: Skillpoint: Take Aim", Items.TAKE_AIM.name, {POOL_SKILLPOINT}, novalis_skillpoint_rule)
+
+# Aridia
+ARIDIA_HOVERBOARD = LocationData(7, "Aridia", "Aridia: Kill the Sand Sharks", Items.HOVERBOARD.name, {POOL_EXTRA_ITEM})
+ARIDIA_TRESPASSER = LocationData(
+ 8, "Aridia", "Aridia: Construction Zone", Items.TRESPASSER.name, {POOL_GADGET}, can_swingshot)
+ARIDIA_SONIC_SUMMONER = LocationData(9, "Aridia", "Aridia: Bring Zoomerator to Agent",
+ Items.SONIC_SUMMONER.name, {POOL_HELMET}, has_zoomerator)
+ARIDIA_TRESPASSER_GOLD_BOLT = LocationData(
+ 10, "Aridia", "Aridia: Gold Bolt: Construction Zone", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_swingshot)
+ARIDIA_ISLAND_GOLD_BOLT = LocationData(
+ 11, "Aridia", "Aridia: Gold bolt: Island", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT})
+ARIDIA_MAGNEBOOTS_GOLD_BOLT = LocationData(
+ 12, "Aridia", "Aridia: Gold Bolt: Laser", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_magneboots)
+ARIDIA_SANDSHARK_GOLD_BOLT = LocationData(13, "Aridia", "Aridia: Gold bolt: Sandshark Cave",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_explosive_weapon)
+
+# Kerwan
+KERWAN_SWINGSHOT = LocationData(
+ 14, "Kerwan", "Kerwan: Fitness Course", Items.SWINGSHOT.name, {POOL_GADGET})
+KERWAN_HELIPACK = LocationData(15, "Kerwan", "Kerwan: Al's Roboshack", Items.HELI_PACK.name, {POOL_PACK})
+KERWAN_TRAIN_INFOBOT = LocationData(16, "Kerwan", "Kerwan: Ride the Robot Train",
+ Items.EUDORA_INFOBOT.name, {POOL_INFOBOT}, can_improved_jump)
+KERWAN_VENDOR_BLASTER = LocationData(
+ 17, "Kerwan", "Kerwan: Vendor - 2,500", Items.BLASTER.name, {POOL_WEAPON})
+KERWAN_BELOW_SHIP_GOLD_BOLT = LocationData(
+ 18, "Kerwan", "Kerwan: Gold Bolt: Underpass", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT})
+KERWAN_TRAIN_STATION_GOLD_BOLT = LocationData(19, "Kerwan", "Kerwan: Gold Bolt: Train Station",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_improved_jump)
+KERWAN_LONE_TOWER_GOLD_BOLT = LocationData(
+ 20, "Kerwan", "Kerwan: Gold Bolt: Fitness Course Tower", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_glide)
+
+# Eudora
+EUDORA_HENCHMAN = LocationData(
+ 21, "Eudora", "Eudora: Robot Lieutenant", Items.BLARG_INFOBOT.name, {POOL_INFOBOT}, eudora_henchman_rule)
+EUDORA_SUCK_CANNON = LocationData(
+ 22, "Eudora", "Eudora: Explore the Mills", Items.SUCK_CANNON.name, {POOL_WEAPON}, eudora_suck_cannon_rule)
+EUDORA_VENDOR_GLOVE_OF_DOOM = LocationData(
+ 23, "Eudora", "Eudora: Vendor - 7,500", Items.GLOVE_OF_DOOM.name, {POOL_WEAPON}, has_metal_detector)
+EUDORA_GOLD_BOLT = LocationData(
+ 24, "Eudora", "Eudora: Gold Bolt", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_heli_high_jump)
+
+# Rilgar
+RILGAR_QUARK_INFOBOT = LocationData(
+ 25, "Rilgar", "Rilgar: Locate Captain Quark", Items.UMBRIS_INFOBOT.name, {POOL_INFOBOT}, rilgar_bouncer_rule)
+RILGAR_PLATINUM_ZOOMERATOR = LocationData(
+ 26, "Rilgar", "Rilgar: Win the hoverboard race", Items.ZOOMERATOR.name, {POOL_EXTRA_ITEM}, rilgar_hoverboard_rule)
+RILGAR_MINE_GLOVE = LocationData(
+ 27, "Rilgar", "Rilgar: Vendor - 7,500", Items.MINE_GLOVE.name, {POOL_WEAPON}, has_metal_detector)
+RILGAR_RYNO = LocationData(
+ 28, "Rilgar", "Rilgar: Shady Salesman - 150,000", Items.RYNO.name, {POOL_WEAPON}, rilgar_ryno_rule)
+RILGAR_MAZE_GOLD_BOLT = LocationData(
+ 29, "Rilgar", "Rilgar: Gold Bolt: Maze", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_improved_jump)
+RILGAR_WATERWORKS_GOLD_BOLT = LocationData(
+ 30, "Rilgar", "Rilgar: Gold Bolt: Sewer Cave", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, rilgar_underwater_bolt_rule)
+
+# Blarg
+BLARG_HYDRODISPLACER = LocationData(
+ 31, "Blarg", "Blarg: Outside as Clank", Items.HYDRODISPLACER.name, {POOL_GADGET}, has_trespasser)
+BLARG_EXPLOSION_INFOBOT = LocationData(
+ 32, "Blarg", "Blarg: Destroy the Warship", Items.RILGAR_INFOBOT.name, {POOL_INFOBOT})
+BLARG_GRINDBOOTS = LocationData(
+ 33, "Blarg", "Blarg: Explore the Space Station", Items.GRINDBOOTS.name, {POOL_BOOT}, can_swingshot)
+BLARG_VENDOR_TAUNTER = LocationData(34, "Blarg", "Blarg: Vendor - 2,500", Items.TAUNTER.name, {POOL_WEAPON})
+BLARG_OUTSIDE_GOLD_BOLT = LocationData(
+ 35, "Blarg", "Blarg: Gold Bolt: Outside", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, blarg_outside_gold_bolt_rule)
+BLARG_SWARMER_GOLD_BOLT = LocationData(
+ 36, "Blarg", "Blarg: Gold Bolt: Cages", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_swingshot)
+
+# Umbris
+UMBRIS_SNAGGLEBEAST_INFOBOT = LocationData(
+ 37, "Umbris", "Umbris: Defeat the Snagglebeast", Items.BATALIA_INFOBOT.name, {POOL_INFOBOT},
+ umbris_snagglebeast_rule)
+UMBRIS_PRESSURE_PUZZLE_GOLD_BOLT = LocationData(38, "Umbris", "Umbris: Gold Bolt: Lighthouse puzzle",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, umbris_pressure_bolt_rule)
+UMBRIS_JUMP_DOWN_GOLD_BOLT = LocationData(
+ 39, "Umbris", "Umbris: Gold bolt: Jumping Down", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, umbris_jump_bolt_rule)
+
+# Batalia
+BATALIA_VENDOR_DEVASTATOR = LocationData(
+ 40, "Batalia", "Batalia: Vendor - 10,000", Items.DEVASTATOR.name, {POOL_WEAPON}, has_metal_detector)
+BATALIA_GRINDRAIL_INFOBOT = LocationData(
+ 41, "Batalia", "Batalia: Ride the grindrail", Items.GASPAR_INFOBOT.name, {POOL_INFOBOT}, can_grind)
+BATALIA_COMMANDER_INFOBOT = LocationData(42, "Batalia", "Batalia: Commando", Items.ORXON_INFOBOT.name, {POOL_INFOBOT})
+BATALIA_METAL_DETECTOR = LocationData(
+ 43, "Batalia", "Batalia: Shoot down the Bombers", Items.METAL_DETECTOR.name, {POOL_GADGET}, has_magneboots)
+BATALIA_CLIFFSIDE_GOLD_BOLT = LocationData(
+ 44, "Batalia", "Batalia: Gold Bolt: Cliffside", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_improved_jump)
+BATALIA_TRESPASSER_GOLD_BOLT = LocationData(
+ 45, "Batalia", "Batalia: Gold Bolt: House Roof", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT})
+
+# Gaspar
+GASPAR_VENDOR_WALLOPER = LocationData(
+ 46, "Gaspar", "Gaspar: Vendor - 7,500", Items.WALLOPER.name, {POOL_WEAPON}, has_metal_detector)
+GASPAR_PILOT_HELMET = LocationData(
+ 47, "Gaspar", "Gaspar: Get the pilot helmet", Items.PILOTS_HELMET.name, {POOL_HELMET})
+GASPAR_SWINGSHOT_GOLD_BOLT = (LocationData(
+ 48, "Gaspar", "Gaspar: Gold Bolt: Destroy the Bombers", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_swingshot))
+GASPAR_VOLCANO_GOLD_BOLT = LocationData(
+ 49, "Gaspar", "Gaspar: Gold Bolt: Volcano", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_improved_jump)
+
+# Orxon
+ORXON_VENDOR_VISIBOMB = LocationData(
+ 50, "Orxon", "Orxon: Vendor - 15,000", Items.VISIBOMB.name, {POOL_WEAPON}, orxon_visibomb_rule)
+ORXON_CLANK_INFOBOT = LocationData(
+ 51, "Orxon", "Orxon: Clank: Traverse the Wilderness", Items.POKITARU_INFOBOT.name, {POOL_INFOBOT})
+ORXON_RATCHET_INFOBOT = LocationData(
+ 52, "Orxon", "Orxon: Chase the Infobot", Items.HOVEN_INFOBOT.name, {POOL_INFOBOT}, orxon_ratchet_infobot_rule)
+ORXON_CLANK_MAGNEBOOTS = LocationData(53, "Orxon", "Orxon: Clank: Search the Labs", Items.MAGNEBOOTS.name, {POOL_BOOT})
+ORXON_PREMIUM_NANOTECH = LocationData(
+ 54, "Orxon", "Orxon: Buy the premium nanotech", Items.PREMIUM_NANOTECH.name, {POOL_EXTRA_ITEM}, orxon_nanotech_rule)
+ORXON_ULTRA_NANOTECH = LocationData(55, "Orxon", "Orxon: Buy the ultra nanotech",
+ Items.ULTRA_NANOTECH.name, {POOL_EXTRA_ITEM}, orxon_ultra_nanotech_rule)
+ORXON_CLANK_GOLD_BOLT = LocationData(
+ 56, "Orxon", "Orxon: Gold Bolt: Return to the Clank section", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_o2_mask)
+ORXON_VISIBOMB_GOLD_BOLT = LocationData(
+ 57, "Orxon", "Orxon: Gold Bolt: Long Tunnel", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, orxon_visibomb_bolt_rule)
+
+# Pokitaru
+POKITARU_VENDOR_DECOY_GLOVE = LocationData(
+ 58, "Pokitaru", "Pokitaru: Vendor - 7,500", Items.DECOY_GLOVE.name, {POOL_WEAPON}, has_metal_detector)
+POKITARU_O2_MASK = LocationData(
+ 59, "Pokitaru", "Pokitaru: Pilot the Ship", Items.O2_MASK.name, {POOL_HELMET}, pokitaru_ship_rule)
+POKITARU_SEWER_PERSUADER = LocationData(
+ 60, "Pokitaru", "Pokitaru: Trade Raritanium", Items.PERSUADER.name, {POOL_EXTRA_ITEM}, pokitaru_persuader_rule)
+POKITARU_THRUSTER_PACK = LocationData(61, "Pokitaru", "Pokitaru: Bob's Shop", Items.THRUSTER_PACK.name, {POOL_PACK})
+POKITARU_GOLD_BOLT = LocationData(
+ 62, "Pokitaru", "Pokitaru: Gold Bolt: Waterfalls", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, pokitaru_gold_bolt_rule)
+
+# Hoven
+HOVEN_VENDOR_DRONE_DEVICE = LocationData(
+ 63, "Hoven", "Hoven: Vendor - 7,500", Items.DRONE_DEVICE.name, {POOL_WEAPON}, has_metal_detector)
+HOVEN_TURRET_INFOBOT = LocationData(
+ 64, "Hoven", "Hoven: Destroy the Planetbuster", Items.GEMLIK_INFOBOT.name, {POOL_INFOBOT}, hoven_infobot_rule)
+HOVEN_HYDRO_PACK = LocationData(
+ 65, "Hoven", "Hoven: Edwina's Shop", Items.HYDRO_PACK.name, {POOL_PACK}, has_hydrodisplacer)
+HOVEN_RARITANIUM = LocationData(
+ 66, "Hoven", "Hoven: Talk to the Miner", Items.RARITANIUM.name, {POOL_EXTRA_ITEM}, hoven_raritanium_rule)
+HOVEN_WATER_GOLD_BOLT = LocationData(
+ 67, "Hoven", "Hoven: Gold Bolt: in the Water Cave", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_hydrodisplacer)
+HOVEN_WALLJUMP_GOLD_BOLT = LocationData(
+ 68, "Hoven", "Hoven: Gold Bolt: Moving wall jump", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT})
+
+# Gemlik
+GEMLIK_QUARK_FIGHT = LocationData(
+ 69, "Gemlik", "Gemlik: Defeat Captain Quark", Items.OLTANIS_INFOBOT.name, {POOL_INFOBOT}, gemlik_quark_rule)
+GEMLIK_GOLD_BOLT = LocationData(
+ 70, "Gemlik", "Gemlik: Gold Bolt: Visibomb Hidden Tower", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, gemlik_bolt_rule)
+
+# Oltanis
+OLTANIS_VENDOR_TESLA_CLAW = LocationData(
+ 71, "Oltanis", "Oltanis: Vendor - 40,000", Items.TESLA_CLAW.name, {POOL_WEAPON}, has_metal_detector)
+OLTANIS_INFOBOT = LocationData(
+ 72, "Oltanis", "Oltanis: Grindrail path: Scrap Merchant", Items.QUARTU_INFOBOT.name, {POOL_INFOBOT}, can_grind)
+OLTANIS_PDA = LocationData(
+ 73, "Oltanis", "Oltanis: Magneboot path: Buy the PDA from Steve", Items.PDA.name, {POOL_GADGET}, has_magneboots)
+OLTANIS_MORPH_O_RAY = LocationData(
+ 74, "Oltanis", "Oltanis: Swingshot path: Search the city", Items.MORPH_O_RAY.name, {POOL_WEAPON}, can_swingshot)
+OLTANIS_MAIN_GOLD_BOLT = LocationData(75, "Oltanis", "Oltanis: Gold Bolt: Grindrail path: Swingshot Upper Ledge",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, oltanis_main_bolt_rule)
+OLTANIS_MAGNET_GOLD_BOLT_1 = LocationData(76, "Oltanis", "Oltanis: Gold Bolt: Magneboot path: Ledge near Bomber",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_magneboots)
+OLTANIS_MAGNET_GOLD_BOLT_2 = LocationData(77, "Oltanis", "Oltanis: Gold Bolt: Magneboot path: Ledge hang",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, has_magneboots)
+OLTANIS_FINAL_GOLD_BOLT = LocationData(78, "Oltanis", "Oltanis: Gold Bolt: All Objectives",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, oltanis_final_bolt_rule)
+
+# Quartu
+QUARTU_GIANT_CLANK_INFOBOT = LocationData(
+ 79, "Quartu", "Quartu: Giant Clank Fight", Items.KALEBO_INFOBOT.name, {POOL_INFOBOT}, can_swingshot)
+QUARTU_BOLT_GRABBER = LocationData(
+ 80, "Quartu", "Quartu: Water Path", Items.BOLT_GRABBER.name, {POOL_EXTRA_ITEM}, quartu_bolt_grabber_rule)
+QUARTU_INFILTRATE_INFOBOT = LocationData(
+ 81, "Quartu", "Quartu: Clank's mother", Items.FLEET_INFOBOT.name, {POOL_INFOBOT}, quartu_infiltrate_rule)
+QUARTU_MOM_GOLD_BOLT = LocationData(82, "Quartu", "Quartu: Gold Bolt: Behind Clank's mother",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, quartu_infiltrate_rule)
+QUARTU_CODEBOT_GOLD_BOLT = LocationData(
+ 83, "Quartu", "Quartu: Gold Bolt: Codebot door", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, quartu_codebot_rule)
+
+# Kalebo III
+KALEBO_HOLOGUISE = LocationData(
+ 84, "Kalebo III", "Kalebo III: Win the hoverboard race", Items.HOLOGUISE.name, {POOL_GADGET}, kalebo_hologuise_rule)
+KALEBO_MAP_O_MATIC = LocationData(
+ 85, "Kalebo III", "Kalebo III: Grindrail: Helpdesk", Items.MAP_O_MATIC.name, {POOL_EXTRA_ITEM}, can_grind)
+KALEBO_GRIND_GOLD_BOLT = LocationData(
+ 86, "Kalebo III", "Kalebo III: Gold Bolt: On the Grindrail", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_grind)
+KALEBO_BREAK_ROOM_GOLD_BOLT = LocationData(
+ 87, "Kalebo III", "Kalebo III: Gold Bolt: Employee break room", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, can_grind)
+
+# Drek's Fleet
+FLEET_INFOBOT = LocationData(
+ 88, "Drek's Fleet", "Drek's Fleet: Flagship", Items.VELDIN_INFOBOT.name, {POOL_INFOBOT}, fleet_infobot_rule)
+FLEET_CODEBOT = LocationData(
+ 89, "Drek's Fleet", "Drek's Fleet: Water section", Items.CODEBOT.name, {POOL_EXTRA_ITEM}, fleet_water_rule)
+FLEET_WATER_GOLD_BOLT = LocationData(90, "Drek's Fleet", "Drek's Fleet: Gold Bolt: Water section",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, fleet_water_rule)
+FLEET_ROBOT_GOLD_BOLT = (LocationData(91, "Drek's Fleet", "Drek's Fleet: Gold Bolt: Sidepath with robot guards",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, fleet_second_bolt_rule))
+
+# Veldin
+VELDIN_TAUNTER_GOLD_BOLT = LocationData(92, "Veldin", "Veldin: Gold Bolt: Taunter the horny toad",
+ Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, veldin_taunter_bolt_rule)
+VELDIN_HALFWAY_GOLD_BOLT = LocationData(
+ 93, "Veldin", "Veldin: Gold Bolt: Platforms", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, veldin_halfway_bolt_rule)
+VELDIN_GRIND_GOLD_BOLT = LocationData(
+ 94, "Veldin", "Veldin: Gold Bolt: Grindrail", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, veldin_grind_bolt_rule)
+VELDIN_DREK = LocationData(None, "Veldin", "Veldin: Defeat Chairman Drek", None, access_rule=veldin_defeat_drek_rule)
diff --git a/worlds/RAC1/data/Planets.py b/worlds/RAC1/data/Planets.py
new file mode 100644
index 000000000000..273c0df554ae
--- /dev/null
+++ b/worlds/RAC1/data/Planets.py
@@ -0,0 +1,231 @@
+from typing import NamedTuple, Sequence
+
+from .Locations import *
+
+
+class PlanetData(NamedTuple):
+ name: str
+ number: int
+ locations: Sequence[LocationData] = []
+
+
+NOVALIS = PlanetData("Novalis", 1, [
+ NOVALIS_PLUMBER,
+ NOVALIS_MAYOR,
+ NOVALIS_VENDOR_PYROCITOR,
+ NOVALIS_SEWER_GOLD_BOLT,
+ NOVALIS_CAVES_GOLD_BOLT,
+ NOVALIS_UNDERWATER_CAVES_GOLD_BOLT,
+ NOVALIS_GOLD_WEAPON_1,
+ NOVALIS_GOLD_WEAPON_2,
+ NOVALIS_GOLD_WEAPON_3,
+ NOVALIS_GOLD_WEAPON_4,
+ NOVALIS_GOLD_WEAPON_5,
+ NOVALIS_GOLD_WEAPON_6,
+ NOVALIS_GOLD_WEAPON_7,
+ NOVALIS_GOLD_WEAPON_8,
+ NOVALIS_GOLD_WEAPON_9,
+ NOVALIS_GOLD_WEAPON_10,
+])
+
+ARIDIA = PlanetData("Aridia", 2, [
+ ARIDIA_HOVERBOARD,
+ ARIDIA_TRESPASSER,
+ ARIDIA_SONIC_SUMMONER,
+ ARIDIA_TRESPASSER_GOLD_BOLT,
+ ARIDIA_ISLAND_GOLD_BOLT,
+ ARIDIA_MAGNEBOOTS_GOLD_BOLT,
+ ARIDIA_SANDSHARK_GOLD_BOLT,
+])
+
+KERWAN = PlanetData("Kerwan", 3, [
+ KERWAN_SWINGSHOT,
+ KERWAN_HELIPACK,
+ KERWAN_TRAIN_INFOBOT,
+ KERWAN_VENDOR_BLASTER,
+ KERWAN_BELOW_SHIP_GOLD_BOLT,
+ KERWAN_TRAIN_STATION_GOLD_BOLT,
+ KERWAN_LONE_TOWER_GOLD_BOLT,
+])
+
+EUDORA = PlanetData("Eudora", 4, [
+ EUDORA_HENCHMAN,
+ EUDORA_SUCK_CANNON,
+ EUDORA_VENDOR_GLOVE_OF_DOOM,
+ EUDORA_GOLD_BOLT,
+])
+
+RILGAR = PlanetData("Rilgar", 5, [
+ RILGAR_QUARK_INFOBOT,
+ RILGAR_PLATINUM_ZOOMERATOR,
+ RILGAR_MINE_GLOVE,
+ RILGAR_RYNO,
+ RILGAR_MAZE_GOLD_BOLT,
+ RILGAR_WATERWORKS_GOLD_BOLT,
+])
+
+BLARG = PlanetData("Blarg", 6, [
+ BLARG_HYDRODISPLACER,
+ BLARG_EXPLOSION_INFOBOT,
+ BLARG_GRINDBOOTS,
+ BLARG_VENDOR_TAUNTER,
+ BLARG_OUTSIDE_GOLD_BOLT,
+ BLARG_SWARMER_GOLD_BOLT,
+])
+
+UMBRIS = PlanetData("Umbris", 7, [
+ UMBRIS_SNAGGLEBEAST_INFOBOT,
+ UMBRIS_PRESSURE_PUZZLE_GOLD_BOLT,
+ UMBRIS_JUMP_DOWN_GOLD_BOLT,
+])
+
+BATALIA = PlanetData("Batalia", 8, [
+ BATALIA_VENDOR_DEVASTATOR,
+ BATALIA_GRINDRAIL_INFOBOT,
+ BATALIA_COMMANDER_INFOBOT,
+ BATALIA_METAL_DETECTOR,
+ BATALIA_CLIFFSIDE_GOLD_BOLT,
+ BATALIA_TRESPASSER_GOLD_BOLT,
+])
+
+GASPAR = PlanetData("Gaspar", 9, [
+ GASPAR_VENDOR_WALLOPER,
+ GASPAR_PILOT_HELMET,
+ GASPAR_SWINGSHOT_GOLD_BOLT,
+ GASPAR_VOLCANO_GOLD_BOLT,
+])
+
+ORXON = PlanetData("Orxon", 10, [
+ ORXON_VENDOR_VISIBOMB,
+ ORXON_CLANK_INFOBOT,
+ ORXON_RATCHET_INFOBOT,
+ ORXON_CLANK_MAGNEBOOTS,
+ ORXON_PREMIUM_NANOTECH,
+ ORXON_ULTRA_NANOTECH,
+ ORXON_CLANK_GOLD_BOLT,
+ ORXON_VISIBOMB_GOLD_BOLT,
+])
+
+POKITARU = PlanetData("Pokitaru", 11, [
+ POKITARU_VENDOR_DECOY_GLOVE,
+ POKITARU_O2_MASK,
+ POKITARU_SEWER_PERSUADER,
+ POKITARU_THRUSTER_PACK,
+ POKITARU_GOLD_BOLT,
+])
+
+HOVEN = PlanetData("Hoven", 12, [
+ HOVEN_VENDOR_DRONE_DEVICE,
+ HOVEN_TURRET_INFOBOT,
+ HOVEN_HYDRO_PACK,
+ HOVEN_RARITANIUM,
+ HOVEN_WATER_GOLD_BOLT,
+ HOVEN_WALLJUMP_GOLD_BOLT,
+])
+
+GEMLIK = PlanetData("Gemlik", 13, [
+ GEMLIK_QUARK_FIGHT,
+ GEMLIK_GOLD_BOLT,
+])
+
+OLTANIS = PlanetData("Oltanis", 14, [
+ OLTANIS_VENDOR_TESLA_CLAW,
+ OLTANIS_INFOBOT,
+ OLTANIS_PDA,
+ OLTANIS_MORPH_O_RAY,
+ OLTANIS_MAIN_GOLD_BOLT,
+ OLTANIS_MAGNET_GOLD_BOLT_1,
+ OLTANIS_MAGNET_GOLD_BOLT_2,
+ OLTANIS_FINAL_GOLD_BOLT,
+])
+
+QUARTU = PlanetData("Quartu", 15, [
+ QUARTU_GIANT_CLANK_INFOBOT,
+ QUARTU_BOLT_GRABBER,
+ QUARTU_INFILTRATE_INFOBOT,
+ QUARTU_MOM_GOLD_BOLT,
+ QUARTU_CODEBOT_GOLD_BOLT,
+])
+
+KALEBO = PlanetData("Kalebo III", 16, [
+ KALEBO_HOLOGUISE,
+ KALEBO_MAP_O_MATIC,
+ KALEBO_GRIND_GOLD_BOLT,
+ KALEBO_BREAK_ROOM_GOLD_BOLT,
+])
+
+FLEET = PlanetData("Drek's Fleet", 17, [
+ FLEET_INFOBOT,
+ FLEET_CODEBOT,
+ FLEET_WATER_GOLD_BOLT,
+ FLEET_ROBOT_GOLD_BOLT,
+])
+
+VELDIN = PlanetData("Veldin", 18, [
+ VELDIN_TAUNTER_GOLD_BOLT,
+ VELDIN_HALFWAY_GOLD_BOLT,
+ VELDIN_GRIND_GOLD_BOLT,
+ VELDIN_DREK,
+])
+
+LOGIC_PLANETS: Sequence[PlanetData] = [
+ NOVALIS,
+ ARIDIA,
+ KERWAN,
+ EUDORA,
+ RILGAR,
+ BLARG,
+ UMBRIS,
+ BATALIA,
+ GASPAR,
+ ORXON,
+ POKITARU,
+ HOVEN,
+ GEMLIK,
+ OLTANIS,
+ QUARTU,
+ KALEBO,
+ FLEET,
+ VELDIN,
+]
+
+ALL_LOCATIONS: Sequence[LocationData] = [
+ location
+ for locations in [planet.locations for planet in LOGIC_PLANETS]
+ for location in locations
+]
+
+location_table_by_name: dict[str, LocationData] = {location.name: location for location in ALL_LOCATIONS}
+location_groups: dict[str, set[str]] = {
+ "Novalis": set(loc.name for loc in ALL_LOCATIONS if loc.planet in NOVALIS),
+ "Aridia": set(loc.name for loc in ALL_LOCATIONS if loc.planet in ARIDIA),
+ "Kerwan": set(loc.name for loc in ALL_LOCATIONS if loc.planet in KERWAN),
+ "Eudora": set(loc.name for loc in ALL_LOCATIONS if loc.planet in EUDORA),
+ "Rilgar": set(loc.name for loc in ALL_LOCATIONS if loc.planet in RILGAR),
+ "Blarg": set(loc.name for loc in ALL_LOCATIONS if loc.planet in BLARG),
+ "Umbris": set(loc.name for loc in ALL_LOCATIONS if loc.planet in UMBRIS),
+ "Batalia": set(loc.name for loc in ALL_LOCATIONS if loc.planet in BATALIA),
+ "Gaspar": set(loc.name for loc in ALL_LOCATIONS if loc.planet in GASPAR),
+ "Orxon": set(loc.name for loc in ALL_LOCATIONS if loc.planet in ORXON),
+ "Pokitaru": set(loc.name for loc in ALL_LOCATIONS if loc.planet in POKITARU),
+ "Hoven": set(loc.name for loc in ALL_LOCATIONS if loc.planet in HOVEN),
+ "Gemlik": set(loc.name for loc in ALL_LOCATIONS if loc.planet in GEMLIK),
+ "Oltanis": set(loc.name for loc in ALL_LOCATIONS if loc.planet in OLTANIS),
+ "Quartu": set(loc.name for loc in ALL_LOCATIONS if loc.planet in QUARTU),
+ "Kalebo": set(loc.name for loc in ALL_LOCATIONS if loc.planet in KALEBO),
+ "Fleet": set(loc.name for loc in ALL_LOCATIONS if loc.planet in FLEET),
+ "Veldin": set(loc.name for loc in ALL_LOCATIONS if loc.planet in VELDIN),
+ "Weapons": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_WEAPON) and len(loc.pools)),
+ "GoldenWeapons": set(
+ loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_GOLDEN_WEAPON) and len(loc.pools)),
+ "Gadgets": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_GADGET) and len(loc.pools)),
+ "Packs": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_PACK) and len(loc.pools)),
+ "Helmets": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_HELMET) and len(loc.pools)),
+ "Boots": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_BOOT) and len(loc.pools)),
+ "ExtraItems": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_EXTRA_ITEM) and len(loc.pools)),
+ "GoldBolt": set(
+ loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_GOLD_BOLT) and len(loc.pools)),
+ "Infobots": set(loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_INFOBOT) and len(loc.pools)),
+ "Skillpoint": set(
+ loc.name for loc in ALL_LOCATIONS if loc.pools.issubset(POOL_SKILLPOINT) and len(loc.pools)),
+}
diff --git a/worlds/RAC1/data/__init__.py b/worlds/RAC1/data/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/worlds/RAC1/docs/en_Ratchet & Clank.md b/worlds/RAC1/docs/en_Ratchet & Clank.md
new file mode 100644
index 000000000000..83037e9ccedd
--- /dev/null
+++ b/worlds/RAC1/docs/en_Ratchet & Clank.md
@@ -0,0 +1,49 @@
+# Ratchet & Clank Archipelago
+An Archipelago implementation for Ratchet & Clank
+
+
+## Setup Guide
+To get started or for troubleshooting, see [the Setup Guide](https://github.com/Panda291/Archipelago/blob/main/worlds/RAC1/docs/setup_en.md).
+
+
+## What does randomization do to this game?
+In Ratchet & Clank 1, all Gadgets, Gold Bolts and Planet Infobots are shuffled into the multiworld, giving the game a greater variety in routing to complete the end goal.
+
+
+## What is the goal of Ratchet & Clank when randomized?
+The end goal of the randomizer game is to defeat Ultimate Supreme Executive Chairman Drek in planet Veldin.
+
+
+## Which items can be in another player's world?
+All Gadgets/Items, Gold Bolts and Planet Infobots can be shuffled in other players' worlds.
+
+
+## What does another world's item look like in Ratchet & Clank?
+There is no model replacement support yet so all items will their vanilla appearance.
+
+
+## What versions of the Ratchet & Clank are supported?
+Only the PS3 version of the game is supported: NPEA00385
+
+
+## When the player receives an item, what happens?
+The player will immediately have their inventory updated and receive a notification in the Client and a HUD message in-game.
+
+### Aside from item locations being shuffled, how does this differ from the vanilla game?
+Some of the changes include:
+ - The Metal Detector has a 50x multiplier for found bolts.
+ - If you turn on cheat mode in the lobby, you can enable ghost ratchet for 1 second by pressing R1 while paused. (ghost ratchet means you can walk through walls, among other things speedrunners use)
+ - You can also play in coop in the same world as your friend. One person sets up a lobby and fills in the archipelago details, the others can join for jolly cooperation.
+
+## Are there any other things I should know?
+- It has come to my attention some people have been using save/load to get back to their ship if they get stuck. This is not a scenario I had anticipated and as such is untested. For now avoid using it, instead use the quit game option and create a new lobby (or reconnect if another player is still in)
+- Any location that costs more than 7500 bolts has the metal detector listed as a requirement
+
+# Credit
+The following wonderful people helped on this project:
+
+[Bordplate](https://github.com/bordplate) - Client
+[evilwb](https://github.com/evilwb/APRac2) - APWorld
+[Myth197](https://github.com/Myth197) - APWorld
+[SharloBun](https://www.twitch.tv/sharlobun) - Playtesting
+Amondo - Playtesting
\ No newline at end of file
diff --git a/worlds/RAC1/docs/setup_en.md b/worlds/RAC1/docs/setup_en.md
new file mode 100644
index 000000000000..2c6f6d9d4f30
--- /dev/null
+++ b/worlds/RAC1/docs/setup_en.md
@@ -0,0 +1,95 @@
+# Setup Guide for Ratchet & Clank Archipelago
+
+This guide is meant to help you get up and running with Ratchet & Clank in your Archipelago run.
+
+## Requirements
+
+The following are required in order to play Ratchet & Clank in Archipelago
+
+- Installed [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) v0.5.0 or higher.\
+ **Make sure to install the Generator if you intend to generate multiworlds.**
+- The latest version of the [Ratchet & Clank apworld](https://github.com/Panda291/Archipelago/releases).
+- A device to play on:
+ - [RPCS3 Emulator](https://rpcs3.net/download).
+ - A Homebrew enabled PS3 (easiest is PS3 HEN).
+- A Ratchet & Clank PAL ISO (`NPEA00385`)
+- The latest version of the [Ratchet & Clank Multiplayer Client](https://github.com/bordplate/rac1-multiplayer/releases).
+- (optional) The latest version of the [Ratchet & Clank Multiplayer Server](https://github.com/bordplate/Lawrence/releases).
+
+## AP World Installation
+
+1. Unzip the downloaded Ratchet & Clank apworld zip file
+2. Double-click the `rac1.apworld` to install it to your local Archipelago instance
+
+## RPCS3 Settings
+- make sure you enable networking:
+ - Configuration -> System -> Network (in the top bar) -> Network Status to 'Connected'
+
+## Setting Up a YAML
+
+All players playing Ratchet & Clank must provide the room host with a YAML file containing the settings for their world.
+A sample YAML file for Ratchet & Clank is supplied in the Ratchet & Clank apworld download. Refer to the comments in that file for details about what each setting does.
+
+Once complete, provide the room host with your YAML file.
+
+## Generating a Multiworld
+
+If you're generating a multiworld game that includes Ratchet & Clank, you'll need to run it locally since the online
+generator does not yet support it. Follow these steps to generate a multiworld:
+
+1. Gather all player's YAMLs. Place these YAMLs into the `Players` folder of your Archipelago installation. If the
+ folder does not exist, then it must be created manually. The files here should not be compressed.
+
+2. Modify any local host settings for generation, as desired.
+
+3. Run `ArchipelagoGenerate.exe` (without `.exe` on Linux) or click `Generate` in the launcher. The generation output
+ is placed in the `output` folder (usually named something like `AP_XXXXX.zip`). \* Please note that if any player in the game you want to generate plays a game that needs a ROM file to generate,
+ you will need the corresponding ROM files.
+
+## Hosting a Room
+
+If you're generating the multiworld, follow the instructions in the previous section.
+Once you have the zip file corresponding to your multiworld, follow [these steps](https://archipelago.gg/tutorial/Archipelago/setup/en#hosting-an-archipelago-server) to host a room.
+Follow the instructions for hosting on the website from a locally generated game or on a local machine.
+
+## Installing the Ratchet & Clank Multiplayer Mod
+To install the Ratchet & Clank Multiplayer Mod, it would be best to follow its [installation guide](https://github.com/bordplate/rac1-multiplayer/blob/main/README.md).
+A short TL;DR:
+- Have NPEA00385 installed to either your emulator or PS3
+- Get the latest release of the [mod](https://github.com/bordplate/rac1-multiplayer/releases)
+- Install the mod to your emulator or PS3 (this requires a modded PS3, I will not be going into the details here. Look up PS3 HEN if you don't know where to start)
+
+## Connecting to a Room
+
+- If you host the multiworld on the website, you can use the public multiplayer server to join it.
+ - Simply start the Ratchet & Clank Multiplayer Client and select the 'Randomizer' Server from the public servers.
+ - Here you can either press Circle to create your own lobby, or join one made by another player who will be playing on the same multiworld slot
+ - The host of the lobby must fill in the multiworld room details to connect
+
+- If you host the multiworld on your own machine, you must also host a Ratchet & Clank Multiplayer Server in order to join it.
+ - Hosting the server locally is as simple as running "Lawrence.exe" and selecting to host "rando" in the configurator.
+ - The IP does not matter if you are not planning to port forward your server
+ - Connecting to the local server can be done from the main menu of the Client by pressing Circle to 'Direct connect' to your machine, the IP for localhost is '127.0.0.1'
+ - When you make a lobby, make sure to set the address to '127.0.0.1' or 'localhost' for your local multiworld room.
+
+## Connection Troubleshooting
+
+- Use the latest Ratchet & Clank Archipelago release
+
+ - Ratchet & Clank Archipelago: [Releases](https://github.com/Panda291/Archipelago/releases)
+
+- Use the latest RPCS3
+
+ - [RPCS3 Emulator](https://rpcs3.net/download)
+
+- Use the Correct Version of the Game
+
+ - Ensure your ISO of Ratchet & Clank is the supported version:
+ - Platform: `PlayStation 3`
+ - Serial: `NPEA00385`
+
+
+- Enable Networking in RPCS3
+ - In RPCS3, Under Configuration -> System -> Network (in the top bar) -> Network Status to 'Connected'
+
+- For any problems, do not hesitate to check out the [Official Ratchet & Clank Multiplayer website](Configuration -> System -> Network (in the top bar) -> Network Status to 'Connected').
\ No newline at end of file
diff --git a/worlds/RAC1/test/TestGoal.py b/worlds/RAC1/test/TestGoal.py
new file mode 100644
index 000000000000..c61fe0961a0f
--- /dev/null
+++ b/worlds/RAC1/test/TestGoal.py
@@ -0,0 +1,24 @@
+from worlds.RAC1.data.Locations import VELDIN_HALFWAY_GOLD_BOLT
+from worlds.RAC1.test import RACTestBase
+
+
+class TestDrek(RACTestBase):
+ options = {}
+
+ def test_goal(self) -> None:
+ """Test various states to verify goal is beatable with correct items"""
+ self.collect_by_name("Veldin")
+ self.assertEqual(self.can_reach_region("Veldin"), True, msg="Veldin region not reached with the infobot")
+ self.collect_by_name(["Trespasser", "Hydrodisplacer", "Magneboots", "Thruster Pack"])
+ self.assertAccessDependency([VELDIN_HALFWAY_GOLD_BOLT.name],
+ [["Trespasser", "Hydrodisplacer", "Magneboots", "Thruster Pack", "Veldin"]],
+ True)
+ self.assertBeatable(False)
+ self.remove_by_name("Veldin")
+ self.assertBeatable(False)
+ self.collect_by_name("Swingshot")
+ self.assertBeatable(False)
+ self.collect_all_but(["Veldin", "Victory"])
+ self.assertBeatable(False)
+ self.collect_by_name("Veldin")
+ self.assertBeatable(True)
diff --git a/worlds/RAC1/test/TestOptions.py b/worlds/RAC1/test/TestOptions.py
new file mode 100644
index 000000000000..6665681a6b7f
--- /dev/null
+++ b/worlds/RAC1/test/TestOptions.py
@@ -0,0 +1,96 @@
+from worlds.RAC1.Options import *
+from worlds.RAC1.test import RACTestBase
+
+
+class TestVanillaWeapons(RACTestBase):
+ """Test Weapons unshuffled to verify beatable"""
+ options = {"shuffle_weapons": ShuffleWeapons.option_vanilla}
+
+
+class TestRandomWeapons(RACTestBase):
+ """Test Weapons local shuffle to verify beatable"""
+ options = {"shuffle_weapons": ShuffleWeapons.option_random_same}
+
+
+class TestVanillaGadgets(RACTestBase):
+ """Test Gadgets unshuffled to verify beatable"""
+ options = {"shuffle_gadgets": ShuffleGadgets.option_vanilla}
+
+
+class TestRandomGadgets(RACTestBase):
+ """Test Gadgets local shuffle to verify beatable"""
+ options = {"shuffle_gadgets": ShuffleGadgets.option_random_same}
+
+
+class TestVanillaPacks(RACTestBase):
+ """Test Packs unshuffled to verify beatable"""
+ options = {"shuffle_packs": ShufflePacks.option_vanilla}
+
+
+class TestRandomPacks(RACTestBase):
+ """Test Packs local shuffle to verify beatable"""
+ options = {"shuffle_packs": ShufflePacks.option_random_same}
+
+
+class TestVanillaHelmets(RACTestBase):
+ """Test Helmets unshuffled to verify beatable"""
+ options = {"shuffle_helmets": ShuffleHelmets.option_vanilla}
+
+
+class TestRandomHelmets(RACTestBase):
+ """Test Helmets local shuffle to verify beatable"""
+ options = {"shuffle_helmets": ShuffleHelmets.option_random_same}
+
+
+class TestVanillaBoots(RACTestBase):
+ """Test Boots unshuffled to verify beatable"""
+ options = {"shuffle_boots": ShuffleBoots.option_vanilla}
+
+
+class TestRandomBoots(RACTestBase):
+ """Test Boots local shuffle to verify beatable"""
+ options = {"shuffle_boots": ShuffleBoots.option_random_same}
+
+
+class TestVanillaExtraItems(RACTestBase):
+ """Test ExtraItems unshuffled to verify beatable"""
+ options = {"shuffle_extra_items": ShuffleExtraItems.option_vanilla}
+
+
+class TestRandomExtraItems(RACTestBase):
+ """Test ExtraItems local shuffle to verify beatable"""
+ options = {"shuffle_extra_items": ShuffleExtraItems.option_random_same}
+
+
+class TestVanillaGoldBolts(RACTestBase):
+ """Test Gold Bolts off to verify beatable"""
+ options = {"shuffle_gold_bolts": ShuffleGoldBolts.option_false}
+
+
+class TestRandomGoldBolts(RACTestBase):
+ """Test Gold Bolts with a random pack size"""
+ options = {"pack_size_gold_bolts": GoldBoltPackSize.weighted_range("random-low")}
+
+
+class TestVanillaInfobots(RACTestBase):
+ """Test Infobots unshuffled to verify beatable"""
+ options = {"shuffle_infobots": ShuffleInfobots.option_vanilla}
+
+
+class TestRandomInfobots(RACTestBase):
+ """Test Infobots local shuffle to verify beatable"""
+ options = {"shuffle_infobots": ShuffleInfobots.option_random_same}
+
+
+class TestUsefuls(RACTestBase):
+ """Test Useful items local shuffle to verify beatable"""
+ options = {
+ "shuffle_weapons": ShuffleWeapons.option_random_item,
+ "shuffle_gold_weapons": ShuffleGoldWeapons.option_random_item,
+ "shuffle_gadgets": ShuffleGadgets.option_random_item,
+ "shuffle_packs": ShufflePacks.option_random_item,
+ "shuffle_helmets": ShuffleHelmets.option_random_item,
+ "shuffle_boots": ShuffleBoots.option_random_item,
+ "shuffle_extra_items": ShuffleExtraItems.option_random_item,
+ "shuffle_infobots": ShuffleInfobots.option_random_item
+ }
diff --git a/worlds/RAC1/test/__init__.py b/worlds/RAC1/test/__init__.py
new file mode 100644
index 000000000000..c5cf80b580d2
--- /dev/null
+++ b/worlds/RAC1/test/__init__.py
@@ -0,0 +1,5 @@
+from test.bases import WorldTestBase
+
+
+class RACTestBase(WorldTestBase):
+ game = "Ratchet & Clank"
diff --git a/worlds/_sc2common/bot/bot_ai_internal.py b/worlds/_sc2common/bot/bot_ai_internal.py
index 583c491dc2f6..1cdd3bda0682 100644
--- a/worlds/_sc2common/bot/bot_ai_internal.py
+++ b/worlds/_sc2common/bot/bot_ai_internal.py
@@ -9,7 +9,7 @@
from typing import TYPE_CHECKING, Any
from typing import Dict, Generator, Iterable, List, Set, Tuple, Union, final
-from s2clientprotocol import sc2api_pb2 as sc_pb
+from .proto import sc2api_pb2 as sc_pb
from .constants import (
IS_PLACEHOLDER,
diff --git a/worlds/_sc2common/bot/client.py b/worlds/_sc2common/bot/client.py
index a902c99d5594..bb4bc4c86659 100644
--- a/worlds/_sc2common/bot/client.py
+++ b/worlds/_sc2common/bot/client.py
@@ -4,11 +4,11 @@
from worlds._sc2common.bot import logger
-from s2clientprotocol import debug_pb2 as debug_pb
-from s2clientprotocol import query_pb2 as query_pb
-from s2clientprotocol import raw_pb2 as raw_pb
-from s2clientprotocol import sc2api_pb2 as sc_pb
-from s2clientprotocol import spatial_pb2 as spatial_pb
+from .proto import debug_pb2 as debug_pb
+from .proto import query_pb2 as query_pb
+from .proto import raw_pb2 as raw_pb
+from .proto import sc2api_pb2 as sc_pb
+from .proto import spatial_pb2 as spatial_pb
from .data import ActionResult, ChatChannel, Race, Result, Status
from .game_data import AbilityData, GameData
diff --git a/worlds/_sc2common/bot/controller.py b/worlds/_sc2common/bot/controller.py
index abb26ef8a9e5..8ddb6131e2d2 100644
--- a/worlds/_sc2common/bot/controller.py
+++ b/worlds/_sc2common/bot/controller.py
@@ -2,7 +2,7 @@
from pathlib import Path
from worlds._sc2common.bot import logger
-from s2clientprotocol import sc2api_pb2 as sc_pb
+from .proto import sc2api_pb2 as sc_pb
from .player import Computer
from .protocol import Protocol
diff --git a/worlds/_sc2common/bot/data.py b/worlds/_sc2common/bot/data.py
index 4c9b3b94a046..36681a5b87f8 100644
--- a/worlds/_sc2common/bot/data.py
+++ b/worlds/_sc2common/bot/data.py
@@ -7,11 +7,11 @@
"""
import enum
-from s2clientprotocol import common_pb2 as common_pb
-from s2clientprotocol import data_pb2 as data_pb
-from s2clientprotocol import error_pb2 as error_pb
-from s2clientprotocol import raw_pb2 as raw_pb
-from s2clientprotocol import sc2api_pb2 as sc_pb
+from .proto import common_pb2 as common_pb
+from .proto import data_pb2 as data_pb
+from .proto import error_pb2 as error_pb
+from .proto import raw_pb2 as raw_pb
+from .proto import sc2api_pb2 as sc_pb
CreateGameError = enum.Enum("CreateGameError", sc_pb.ResponseCreateGame.Error.items())
diff --git a/worlds/_sc2common/bot/main.py b/worlds/_sc2common/bot/main.py
index f18c56836166..5d40269ed30b 100644
--- a/worlds/_sc2common/bot/main.py
+++ b/worlds/_sc2common/bot/main.py
@@ -15,7 +15,7 @@
import portpicker
from aiohttp import ClientSession, ClientWebSocketResponse
from worlds._sc2common.bot import logger
-from s2clientprotocol import sc2api_pb2 as sc_pb
+from .proto import sc2api_pb2 as sc_pb
from .bot_ai import BotAI
from .client import Client
diff --git a/worlds/_sc2common/bot/position.py b/worlds/_sc2common/bot/position.py
index aca9a5105cbe..bde21b15c3f3 100644
--- a/worlds/_sc2common/bot/position.py
+++ b/worlds/_sc2common/bot/position.py
@@ -5,7 +5,7 @@
import random
from typing import TYPE_CHECKING, Iterable, List, Set, Tuple, Union
-from s2clientprotocol import common_pb2 as common_pb
+from .proto import common_pb2 as common_pb
if TYPE_CHECKING:
from .unit import Unit
diff --git a/worlds/_sc2common/bot/proto/__init__.py b/worlds/_sc2common/bot/proto/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/worlds/_sc2common/bot/proto/common_pb2.py b/worlds/_sc2common/bot/proto/common_pb2.py
new file mode 100644
index 000000000000..f84ad4d9ee85
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/common_pb2.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/common.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/common.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ds2clientprotocol/common.proto\x12\x0eSC2APIProtocol\">\n\x10\x41vailableAbility\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12\x16\n\x0erequires_point\x18\x02 \x01(\x08\"X\n\tImageData\x12\x16\n\x0e\x62its_per_pixel\x18\x01 \x01(\x05\x12%\n\x04size\x18\x02 \x01(\x0b\x32\x17.SC2APIProtocol.Size2DI\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"\x1e\n\x06PointI\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\"T\n\nRectangleI\x12\"\n\x02p0\x18\x01 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\x12\"\n\x02p1\x18\x02 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\"\x1f\n\x07Point2D\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"(\n\x05Point\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"\x1f\n\x07Size2DI\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05*A\n\x04Race\x12\n\n\x06NoRace\x10\x00\x12\n\n\x06Terran\x10\x01\x12\x08\n\x04Zerg\x10\x02\x12\x0b\n\x07Protoss\x10\x03\x12\n\n\x06Random\x10\x04')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.common_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_RACE']._serialized_start=429
+ _globals['_RACE']._serialized_end=494
+ _globals['_AVAILABLEABILITY']._serialized_start=49
+ _globals['_AVAILABLEABILITY']._serialized_end=111
+ _globals['_IMAGEDATA']._serialized_start=113
+ _globals['_IMAGEDATA']._serialized_end=201
+ _globals['_POINTI']._serialized_start=203
+ _globals['_POINTI']._serialized_end=233
+ _globals['_RECTANGLEI']._serialized_start=235
+ _globals['_RECTANGLEI']._serialized_end=319
+ _globals['_POINT2D']._serialized_start=321
+ _globals['_POINT2D']._serialized_end=352
+ _globals['_POINT']._serialized_start=354
+ _globals['_POINT']._serialized_end=394
+ _globals['_SIZE2DI']._serialized_start=396
+ _globals['_SIZE2DI']._serialized_end=427
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/data_pb2.py b/worlds/_sc2common/bot/proto/data_pb2.py
new file mode 100644
index 000000000000..6785ea3ed7dd
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/data_pb2.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/data.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/data.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import common_pb2 as s2clientprotocol_dot_common__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bs2clientprotocol/data.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\xc4\x03\n\x0b\x41\x62ilityData\x12\x12\n\nability_id\x18\x01 \x01(\r\x12\x11\n\tlink_name\x18\x02 \x01(\t\x12\x12\n\nlink_index\x18\x03 \x01(\r\x12\x13\n\x0b\x62utton_name\x18\x04 \x01(\t\x12\x15\n\rfriendly_name\x18\x05 \x01(\t\x12\x0e\n\x06hotkey\x18\x06 \x01(\t\x12\x1c\n\x14remaps_to_ability_id\x18\x07 \x01(\r\x12\x11\n\tavailable\x18\x08 \x01(\x08\x12\x32\n\x06target\x18\t \x01(\x0e\x32\".SC2APIProtocol.AbilityData.Target\x12\x15\n\rallow_minimap\x18\n \x01(\x08\x12\x16\n\x0e\x61llow_autocast\x18\x0b \x01(\x08\x12\x13\n\x0bis_building\x18\x0c \x01(\x08\x12\x18\n\x10\x66ootprint_radius\x18\r \x01(\x02\x12\x1c\n\x14is_instant_placement\x18\x0e \x01(\x08\x12\x12\n\ncast_range\x18\x0f \x01(\x02\"I\n\x06Target\x12\x08\n\x04None\x10\x01\x12\t\n\x05Point\x10\x02\x12\x08\n\x04Unit\x10\x03\x12\x0f\n\x0bPointOrUnit\x10\x04\x12\x0f\n\x0bPointOrNone\x10\x05\"J\n\x0b\x44\x61mageBonus\x12,\n\tattribute\x18\x01 \x01(\x0e\x32\x19.SC2APIProtocol.Attribute\x12\r\n\x05\x62onus\x18\x02 \x01(\x02\"\xd7\x01\n\x06Weapon\x12/\n\x04type\x18\x01 \x01(\x0e\x32!.SC2APIProtocol.Weapon.TargetType\x12\x0e\n\x06\x64\x61mage\x18\x02 \x01(\x02\x12\x31\n\x0c\x64\x61mage_bonus\x18\x03 \x03(\x0b\x32\x1b.SC2APIProtocol.DamageBonus\x12\x0f\n\x07\x61ttacks\x18\x04 \x01(\r\x12\r\n\x05range\x18\x05 \x01(\x02\x12\r\n\x05speed\x18\x06 \x01(\x02\"*\n\nTargetType\x12\n\n\x06Ground\x10\x01\x12\x07\n\x03\x41ir\x10\x02\x12\x07\n\x03\x41ny\x10\x03\"\x95\x04\n\x0cUnitTypeData\x12\x0f\n\x07unit_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\tavailable\x18\x03 \x01(\x08\x12\x12\n\ncargo_size\x18\x04 \x01(\r\x12\x14\n\x0cmineral_cost\x18\x0c \x01(\r\x12\x14\n\x0cvespene_cost\x18\r \x01(\r\x12\x15\n\rfood_required\x18\x0e \x01(\x02\x12\x15\n\rfood_provided\x18\x12 \x01(\x02\x12\x12\n\nability_id\x18\x0f \x01(\r\x12\"\n\x04race\x18\x10 \x01(\x0e\x32\x14.SC2APIProtocol.Race\x12\x12\n\nbuild_time\x18\x11 \x01(\x02\x12\x13\n\x0bhas_vespene\x18\x13 \x01(\x08\x12\x14\n\x0chas_minerals\x18\x14 \x01(\x08\x12\x13\n\x0bsight_range\x18\x19 \x01(\x02\x12\x12\n\ntech_alias\x18\x15 \x03(\r\x12\x12\n\nunit_alias\x18\x16 \x01(\r\x12\x18\n\x10tech_requirement\x18\x17 \x01(\r\x12\x18\n\x10require_attached\x18\x18 \x01(\x08\x12-\n\nattributes\x18\x08 \x03(\x0e\x32\x19.SC2APIProtocol.Attribute\x12\x16\n\x0emovement_speed\x18\t \x01(\x02\x12\r\n\x05\x61rmor\x18\n \x01(\x02\x12\'\n\x07weapons\x18\x0b \x03(\x0b\x32\x16.SC2APIProtocol.Weapon\"\x86\x01\n\x0bUpgradeData\x12\x12\n\nupgrade_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x14\n\x0cmineral_cost\x18\x03 \x01(\r\x12\x14\n\x0cvespene_cost\x18\x04 \x01(\r\x12\x15\n\rresearch_time\x18\x05 \x01(\x02\x12\x12\n\nability_id\x18\x06 \x01(\r\")\n\x08\x42uffData\x12\x0f\n\x07\x62uff_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\"T\n\nEffectData\x12\x11\n\teffect_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x15\n\rfriendly_name\x18\x03 \x01(\t\x12\x0e\n\x06radius\x18\x04 \x01(\x02*\x9e\x01\n\tAttribute\x12\t\n\x05Light\x10\x01\x12\x0b\n\x07\x41rmored\x10\x02\x12\x0e\n\nBiological\x10\x03\x12\x0e\n\nMechanical\x10\x04\x12\x0b\n\x07Robotic\x10\x05\x12\x0b\n\x07Psionic\x10\x06\x12\x0b\n\x07Massive\x10\x07\x12\r\n\tStructure\x10\x08\x12\t\n\x05Hover\x10\t\x12\n\n\x06Heroic\x10\n\x12\x0c\n\x08Summoned\x10\x0b')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.data_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_ATTRIBUTE']._serialized_start=1630
+ _globals['_ATTRIBUTE']._serialized_end=1788
+ _globals['_ABILITYDATA']._serialized_start=79
+ _globals['_ABILITYDATA']._serialized_end=531
+ _globals['_ABILITYDATA_TARGET']._serialized_start=458
+ _globals['_ABILITYDATA_TARGET']._serialized_end=531
+ _globals['_DAMAGEBONUS']._serialized_start=533
+ _globals['_DAMAGEBONUS']._serialized_end=607
+ _globals['_WEAPON']._serialized_start=610
+ _globals['_WEAPON']._serialized_end=825
+ _globals['_WEAPON_TARGETTYPE']._serialized_start=783
+ _globals['_WEAPON_TARGETTYPE']._serialized_end=825
+ _globals['_UNITTYPEDATA']._serialized_start=828
+ _globals['_UNITTYPEDATA']._serialized_end=1361
+ _globals['_UPGRADEDATA']._serialized_start=1364
+ _globals['_UPGRADEDATA']._serialized_end=1498
+ _globals['_BUFFDATA']._serialized_start=1500
+ _globals['_BUFFDATA']._serialized_end=1541
+ _globals['_EFFECTDATA']._serialized_start=1543
+ _globals['_EFFECTDATA']._serialized_end=1627
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/debug_pb2.py b/worlds/_sc2common/bot/proto/debug_pb2.py
new file mode 100644
index 000000000000..6715c8b0f23c
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/debug_pb2.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/debug.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/debug.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import common_pb2 as s2clientprotocol_dot_common__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/debug.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\xbb\x03\n\x0c\x44\x65\x62ugCommand\x12)\n\x04\x64raw\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.DebugDrawH\x00\x12\x34\n\ngame_state\x18\x02 \x01(\x0e\x32\x1e.SC2APIProtocol.DebugGameStateH\x00\x12\x36\n\x0b\x63reate_unit\x18\x03 \x01(\x0b\x32\x1f.SC2APIProtocol.DebugCreateUnitH\x00\x12\x32\n\tkill_unit\x18\x04 \x01(\x0b\x32\x1d.SC2APIProtocol.DebugKillUnitH\x00\x12\x38\n\x0ctest_process\x18\x05 \x01(\x0b\x32 .SC2APIProtocol.DebugTestProcessH\x00\x12.\n\x05score\x18\x06 \x01(\x0b\x32\x1d.SC2APIProtocol.DebugSetScoreH\x00\x12\x30\n\x08\x65nd_game\x18\x07 \x01(\x0b\x32\x1c.SC2APIProtocol.DebugEndGameH\x00\x12\x37\n\nunit_value\x18\x08 \x01(\x0b\x32!.SC2APIProtocol.DebugSetUnitValueH\x00\x42\t\n\x07\x63ommand\"\xb5\x01\n\tDebugDraw\x12\'\n\x04text\x18\x01 \x03(\x0b\x32\x19.SC2APIProtocol.DebugText\x12(\n\x05lines\x18\x02 \x03(\x0b\x32\x19.SC2APIProtocol.DebugLine\x12\'\n\x05\x62oxes\x18\x03 \x03(\x0b\x32\x18.SC2APIProtocol.DebugBox\x12,\n\x07spheres\x18\x04 \x03(\x0b\x32\x1b.SC2APIProtocol.DebugSphere\"L\n\x04Line\x12!\n\x02p0\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12!\n\x02p1\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\"(\n\x05\x43olor\x12\t\n\x01r\x18\x01 \x01(\r\x12\t\n\x01g\x18\x02 \x01(\r\x12\t\n\x01\x62\x18\x03 \x01(\r\"\xa3\x01\n\tDebugText\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12\x0c\n\x04text\x18\x02 \x01(\t\x12*\n\x0bvirtual_pos\x18\x03 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12(\n\tworld_pos\x18\x04 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x0c\n\x04size\x18\x05 \x01(\r\"U\n\tDebugLine\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12\"\n\x04line\x18\x02 \x01(\x0b\x32\x14.SC2APIProtocol.Line\"x\n\x08\x44\x65\x62ugBox\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12\"\n\x03min\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\"\n\x03max\x18\x03 \x01(\x0b\x32\x15.SC2APIProtocol.Point\"`\n\x0b\x44\x65\x62ugSphere\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12 \n\x01p\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\t\n\x01r\x18\x03 \x01(\x02\"k\n\x0f\x44\x65\x62ugCreateUnit\x12\x11\n\tunit_type\x18\x01 \x01(\r\x12\r\n\x05owner\x18\x02 \x01(\x05\x12$\n\x03pos\x18\x03 \x01(\x0b\x32\x17.SC2APIProtocol.Point2D\x12\x10\n\x08quantity\x18\x04 \x01(\r\"\x1c\n\rDebugKillUnit\x12\x0b\n\x03tag\x18\x01 \x03(\x04\"\x80\x01\n\x10\x44\x65\x62ugTestProcess\x12\x33\n\x04test\x18\x01 \x01(\x0e\x32%.SC2APIProtocol.DebugTestProcess.Test\x12\x10\n\x08\x64\x65lay_ms\x18\x02 \x01(\x05\"%\n\x04Test\x12\x08\n\x04hang\x10\x01\x12\t\n\x05\x63rash\x10\x02\x12\x08\n\x04\x65xit\x10\x03\"\x1e\n\rDebugSetScore\x12\r\n\x05score\x18\x01 \x01(\x02\"z\n\x0c\x44\x65\x62ugEndGame\x12:\n\nend_result\x18\x01 \x01(\x0e\x32&.SC2APIProtocol.DebugEndGame.EndResult\".\n\tEndResult\x12\r\n\tSurrender\x10\x01\x12\x12\n\x0e\x44\x65\x63lareVictory\x10\x02\"\xa5\x01\n\x11\x44\x65\x62ugSetUnitValue\x12?\n\nunit_value\x18\x01 \x01(\x0e\x32+.SC2APIProtocol.DebugSetUnitValue.UnitValue\x12\r\n\x05value\x18\x02 \x01(\x02\x12\x10\n\x08unit_tag\x18\x03 \x01(\x04\".\n\tUnitValue\x12\n\n\x06\x45nergy\x10\x01\x12\x08\n\x04Life\x10\x02\x12\x0b\n\x07Shields\x10\x03*\xb2\x01\n\x0e\x44\x65\x62ugGameState\x12\x0c\n\x08show_map\x10\x01\x12\x11\n\rcontrol_enemy\x10\x02\x12\x08\n\x04\x66ood\x10\x03\x12\x08\n\x04\x66ree\x10\x04\x12\x11\n\rall_resources\x10\x05\x12\x07\n\x03god\x10\x06\x12\x0c\n\x08minerals\x10\x07\x12\x07\n\x03gas\x10\x08\x12\x0c\n\x08\x63ooldown\x10\t\x12\r\n\ttech_tree\x10\n\x12\x0b\n\x07upgrade\x10\x0b\x12\x0e\n\nfast_build\x10\x0c')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.debug_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_DEBUGGAMESTATE']._serialized_start=1897
+ _globals['_DEBUGGAMESTATE']._serialized_end=2075
+ _globals['_DEBUGCOMMAND']._serialized_start=80
+ _globals['_DEBUGCOMMAND']._serialized_end=523
+ _globals['_DEBUGDRAW']._serialized_start=526
+ _globals['_DEBUGDRAW']._serialized_end=707
+ _globals['_LINE']._serialized_start=709
+ _globals['_LINE']._serialized_end=785
+ _globals['_COLOR']._serialized_start=787
+ _globals['_COLOR']._serialized_end=827
+ _globals['_DEBUGTEXT']._serialized_start=830
+ _globals['_DEBUGTEXT']._serialized_end=993
+ _globals['_DEBUGLINE']._serialized_start=995
+ _globals['_DEBUGLINE']._serialized_end=1080
+ _globals['_DEBUGBOX']._serialized_start=1082
+ _globals['_DEBUGBOX']._serialized_end=1202
+ _globals['_DEBUGSPHERE']._serialized_start=1204
+ _globals['_DEBUGSPHERE']._serialized_end=1300
+ _globals['_DEBUGCREATEUNIT']._serialized_start=1302
+ _globals['_DEBUGCREATEUNIT']._serialized_end=1409
+ _globals['_DEBUGKILLUNIT']._serialized_start=1411
+ _globals['_DEBUGKILLUNIT']._serialized_end=1439
+ _globals['_DEBUGTESTPROCESS']._serialized_start=1442
+ _globals['_DEBUGTESTPROCESS']._serialized_end=1570
+ _globals['_DEBUGTESTPROCESS_TEST']._serialized_start=1533
+ _globals['_DEBUGTESTPROCESS_TEST']._serialized_end=1570
+ _globals['_DEBUGSETSCORE']._serialized_start=1572
+ _globals['_DEBUGSETSCORE']._serialized_end=1602
+ _globals['_DEBUGENDGAME']._serialized_start=1604
+ _globals['_DEBUGENDGAME']._serialized_end=1726
+ _globals['_DEBUGENDGAME_ENDRESULT']._serialized_start=1680
+ _globals['_DEBUGENDGAME_ENDRESULT']._serialized_end=1726
+ _globals['_DEBUGSETUNITVALUE']._serialized_start=1729
+ _globals['_DEBUGSETUNITVALUE']._serialized_end=1894
+ _globals['_DEBUGSETUNITVALUE_UNITVALUE']._serialized_start=1848
+ _globals['_DEBUGSETUNITVALUE_UNITVALUE']._serialized_end=1894
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/error_pb2.py b/worlds/_sc2common/bot/proto/error_pb2.py
new file mode 100644
index 000000000000..d9c5709d74c1
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/error_pb2.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/error.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/error.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/error.proto\x12\x0eSC2APIProtocol*\xa8-\n\x0c\x41\x63tionResult\x12\x0b\n\x07Success\x10\x01\x12\x10\n\x0cNotSupported\x10\x02\x12\t\n\x05\x45rror\x10\x03\x12\x16\n\x12\x43\x61ntQueueThatOrder\x10\x04\x12\t\n\x05Retry\x10\x05\x12\x0c\n\x08\x43ooldown\x10\x06\x12\x0f\n\x0bQueueIsFull\x10\x07\x12\x14\n\x10RallyQueueIsFull\x10\x08\x12\x15\n\x11NotEnoughMinerals\x10\t\x12\x14\n\x10NotEnoughVespene\x10\n\x12\x16\n\x12NotEnoughTerrazine\x10\x0b\x12\x13\n\x0fNotEnoughCustom\x10\x0c\x12\x11\n\rNotEnoughFood\x10\r\x12\x17\n\x13\x46oodUsageImpossible\x10\x0e\x12\x11\n\rNotEnoughLife\x10\x0f\x12\x14\n\x10NotEnoughShields\x10\x10\x12\x13\n\x0fNotEnoughEnergy\x10\x11\x12\x12\n\x0eLifeSuppressed\x10\x12\x12\x15\n\x11ShieldsSuppressed\x10\x13\x12\x14\n\x10\x45nergySuppressed\x10\x14\x12\x14\n\x10NotEnoughCharges\x10\x15\x12\x16\n\x12\x43\x61ntAddMoreCharges\x10\x16\x12\x13\n\x0fTooMuchMinerals\x10\x17\x12\x12\n\x0eTooMuchVespene\x10\x18\x12\x14\n\x10TooMuchTerrazine\x10\x19\x12\x11\n\rTooMuchCustom\x10\x1a\x12\x0f\n\x0bTooMuchFood\x10\x1b\x12\x0f\n\x0bTooMuchLife\x10\x1c\x12\x12\n\x0eTooMuchShields\x10\x1d\x12\x11\n\rTooMuchEnergy\x10\x1e\x12\x1a\n\x16MustTargetUnitWithLife\x10\x1f\x12\x1d\n\x19MustTargetUnitWithShields\x10 \x12\x1c\n\x18MustTargetUnitWithEnergy\x10!\x12\r\n\tCantTrade\x10\"\x12\r\n\tCantSpend\x10#\x12\x16\n\x12\x43\x61ntTargetThatUnit\x10$\x12\x17\n\x13\x43ouldntAllocateUnit\x10%\x12\x10\n\x0cUnitCantMove\x10&\x12\x1e\n\x1aTransportIsHoldingPosition\x10\'\x12\x1f\n\x1b\x42uildTechRequirementsNotMet\x10(\x12\x1d\n\x19\x43\x61ntFindPlacementLocation\x10)\x12\x13\n\x0f\x43\x61ntBuildOnThat\x10*\x12\x1e\n\x1a\x43\x61ntBuildTooCloseToDropOff\x10+\x12\x1c\n\x18\x43\x61ntBuildLocationInvalid\x10,\x12\x18\n\x14\x43\x61ntSeeBuildLocation\x10-\x12\"\n\x1e\x43\x61ntBuildTooCloseToCreepSource\x10.\x12 \n\x1c\x43\x61ntBuildTooCloseToResources\x10/\x12\x1c\n\x18\x43\x61ntBuildTooFarFromWater\x10\x30\x12\"\n\x1e\x43\x61ntBuildTooFarFromCreepSource\x10\x31\x12\'\n#CantBuildTooFarFromBuildPowerSource\x10\x32\x12\x1b\n\x17\x43\x61ntBuildOnDenseTerrain\x10\x33\x12\'\n#CantTrainTooFarFromTrainPowerSource\x10\x34\x12\x1b\n\x17\x43\x61ntLandLocationInvalid\x10\x35\x12\x17\n\x13\x43\x61ntSeeLandLocation\x10\x36\x12!\n\x1d\x43\x61ntLandTooCloseToCreepSource\x10\x37\x12\x1f\n\x1b\x43\x61ntLandTooCloseToResources\x10\x38\x12\x1b\n\x17\x43\x61ntLandTooFarFromWater\x10\x39\x12!\n\x1d\x43\x61ntLandTooFarFromCreepSource\x10:\x12&\n\"CantLandTooFarFromBuildPowerSource\x10;\x12&\n\"CantLandTooFarFromTrainPowerSource\x10<\x12\x1a\n\x16\x43\x61ntLandOnDenseTerrain\x10=\x12\x1b\n\x17\x41\x64\x64OnTooFarFromBuilding\x10>\x12\x1a\n\x16MustBuildRefineryFirst\x10?\x12\x1f\n\x1b\x42uildingIsUnderConstruction\x10@\x12\x13\n\x0f\x43\x61ntFindDropOff\x10\x41\x12\x1d\n\x19\x43\x61ntLoadOtherPlayersUnits\x10\x42\x12\x1b\n\x17NotEnoughRoomToLoadUnit\x10\x43\x12\x18\n\x14\x43\x61ntUnloadUnitsThere\x10\x44\x12\x18\n\x14\x43\x61ntWarpInUnitsThere\x10\x45\x12\x19\n\x15\x43\x61ntLoadImmobileUnits\x10\x46\x12\x1d\n\x19\x43\x61ntRechargeImmobileUnits\x10G\x12&\n\"CantRechargeUnderConstructionUnits\x10H\x12\x14\n\x10\x43\x61ntLoadThatUnit\x10I\x12\x13\n\x0fNoCargoToUnload\x10J\x12\x19\n\x15LoadAllNoTargetsFound\x10K\x12\x14\n\x10NotWhileOccupied\x10L\x12\x19\n\x15\x43\x61ntAttackWithoutAmmo\x10M\x12\x17\n\x13\x43\x61ntHoldAnyMoreAmmo\x10N\x12\x1a\n\x16TechRequirementsNotMet\x10O\x12\x19\n\x15MustLockdownUnitFirst\x10P\x12\x12\n\x0eMustTargetUnit\x10Q\x12\x17\n\x13MustTargetInventory\x10R\x12\x19\n\x15MustTargetVisibleUnit\x10S\x12\x1d\n\x19MustTargetVisibleLocation\x10T\x12\x1e\n\x1aMustTargetWalkableLocation\x10U\x12\x1a\n\x16MustTargetPawnableUnit\x10V\x12\x1a\n\x16YouCantControlThatUnit\x10W\x12\"\n\x1eYouCantIssueCommandsToThatUnit\x10X\x12\x17\n\x13MustTargetResources\x10Y\x12\x16\n\x12RequiresHealTarget\x10Z\x12\x18\n\x14RequiresRepairTarget\x10[\x12\x11\n\rNoItemsToDrop\x10\\\x12\x18\n\x14\x43\x61ntHoldAnyMoreItems\x10]\x12\x10\n\x0c\x43\x61ntHoldThat\x10^\x12\x18\n\x14TargetHasNoInventory\x10_\x12\x14\n\x10\x43\x61ntDropThisItem\x10`\x12\x14\n\x10\x43\x61ntMoveThisItem\x10\x61\x12\x14\n\x10\x43\x61ntPawnThisUnit\x10\x62\x12\x14\n\x10MustTargetCaster\x10\x63\x12\x14\n\x10\x43\x61ntTargetCaster\x10\x64\x12\x13\n\x0fMustTargetOuter\x10\x65\x12\x13\n\x0f\x43\x61ntTargetOuter\x10\x66\x12\x1a\n\x16MustTargetYourOwnUnits\x10g\x12\x1a\n\x16\x43\x61ntTargetYourOwnUnits\x10h\x12\x1b\n\x17MustTargetFriendlyUnits\x10i\x12\x1b\n\x17\x43\x61ntTargetFriendlyUnits\x10j\x12\x1a\n\x16MustTargetNeutralUnits\x10k\x12\x1a\n\x16\x43\x61ntTargetNeutralUnits\x10l\x12\x18\n\x14MustTargetEnemyUnits\x10m\x12\x18\n\x14\x43\x61ntTargetEnemyUnits\x10n\x12\x16\n\x12MustTargetAirUnits\x10o\x12\x16\n\x12\x43\x61ntTargetAirUnits\x10p\x12\x19\n\x15MustTargetGroundUnits\x10q\x12\x19\n\x15\x43\x61ntTargetGroundUnits\x10r\x12\x18\n\x14MustTargetStructures\x10s\x12\x18\n\x14\x43\x61ntTargetStructures\x10t\x12\x18\n\x14MustTargetLightUnits\x10u\x12\x18\n\x14\x43\x61ntTargetLightUnits\x10v\x12\x1a\n\x16MustTargetArmoredUnits\x10w\x12\x1a\n\x16\x43\x61ntTargetArmoredUnits\x10x\x12\x1d\n\x19MustTargetBiologicalUnits\x10y\x12\x1d\n\x19\x43\x61ntTargetBiologicalUnits\x10z\x12\x19\n\x15MustTargetHeroicUnits\x10{\x12\x19\n\x15\x43\x61ntTargetHeroicUnits\x10|\x12\x1a\n\x16MustTargetRoboticUnits\x10}\x12\x1a\n\x16\x43\x61ntTargetRoboticUnits\x10~\x12\x1d\n\x19MustTargetMechanicalUnits\x10\x7f\x12\x1e\n\x19\x43\x61ntTargetMechanicalUnits\x10\x80\x01\x12\x1b\n\x16MustTargetPsionicUnits\x10\x81\x01\x12\x1b\n\x16\x43\x61ntTargetPsionicUnits\x10\x82\x01\x12\x1b\n\x16MustTargetMassiveUnits\x10\x83\x01\x12\x1b\n\x16\x43\x61ntTargetMassiveUnits\x10\x84\x01\x12\x16\n\x11MustTargetMissile\x10\x85\x01\x12\x16\n\x11\x43\x61ntTargetMissile\x10\x86\x01\x12\x1a\n\x15MustTargetWorkerUnits\x10\x87\x01\x12\x1a\n\x15\x43\x61ntTargetWorkerUnits\x10\x88\x01\x12!\n\x1cMustTargetEnergyCapableUnits\x10\x89\x01\x12!\n\x1c\x43\x61ntTargetEnergyCapableUnits\x10\x8a\x01\x12!\n\x1cMustTargetShieldCapableUnits\x10\x8b\x01\x12!\n\x1c\x43\x61ntTargetShieldCapableUnits\x10\x8c\x01\x12\x15\n\x10MustTargetFlyers\x10\x8d\x01\x12\x15\n\x10\x43\x61ntTargetFlyers\x10\x8e\x01\x12\x1a\n\x15MustTargetBuriedUnits\x10\x8f\x01\x12\x1a\n\x15\x43\x61ntTargetBuriedUnits\x10\x90\x01\x12\x1b\n\x16MustTargetCloakedUnits\x10\x91\x01\x12\x1b\n\x16\x43\x61ntTargetCloakedUnits\x10\x92\x01\x12\"\n\x1dMustTargetUnitsInAStasisField\x10\x93\x01\x12\"\n\x1d\x43\x61ntTargetUnitsInAStasisField\x10\x94\x01\x12%\n MustTargetUnderConstructionUnits\x10\x95\x01\x12%\n CantTargetUnderConstructionUnits\x10\x96\x01\x12\x18\n\x13MustTargetDeadUnits\x10\x97\x01\x12\x18\n\x13\x43\x61ntTargetDeadUnits\x10\x98\x01\x12\x1d\n\x18MustTargetRevivableUnits\x10\x99\x01\x12\x1d\n\x18\x43\x61ntTargetRevivableUnits\x10\x9a\x01\x12\x1a\n\x15MustTargetHiddenUnits\x10\x9b\x01\x12\x1a\n\x15\x43\x61ntTargetHiddenUnits\x10\x9c\x01\x12\"\n\x1d\x43\x61ntRechargeOtherPlayersUnits\x10\x9d\x01\x12\x1d\n\x18MustTargetHallucinations\x10\x9e\x01\x12\x1d\n\x18\x43\x61ntTargetHallucinations\x10\x9f\x01\x12 \n\x1bMustTargetInvulnerableUnits\x10\xa0\x01\x12 \n\x1b\x43\x61ntTargetInvulnerableUnits\x10\xa1\x01\x12\x1c\n\x17MustTargetDetectedUnits\x10\xa2\x01\x12\x1c\n\x17\x43\x61ntTargetDetectedUnits\x10\xa3\x01\x12\x1d\n\x18\x43\x61ntTargetUnitWithEnergy\x10\xa4\x01\x12\x1e\n\x19\x43\x61ntTargetUnitWithShields\x10\xa5\x01\x12!\n\x1cMustTargetUncommandableUnits\x10\xa6\x01\x12!\n\x1c\x43\x61ntTargetUncommandableUnits\x10\xa7\x01\x12!\n\x1cMustTargetPreventDefeatUnits\x10\xa8\x01\x12!\n\x1c\x43\x61ntTargetPreventDefeatUnits\x10\xa9\x01\x12!\n\x1cMustTargetPreventRevealUnits\x10\xaa\x01\x12!\n\x1c\x43\x61ntTargetPreventRevealUnits\x10\xab\x01\x12\x1b\n\x16MustTargetPassiveUnits\x10\xac\x01\x12\x1b\n\x16\x43\x61ntTargetPassiveUnits\x10\xad\x01\x12\x1b\n\x16MustTargetStunnedUnits\x10\xae\x01\x12\x1b\n\x16\x43\x61ntTargetStunnedUnits\x10\xaf\x01\x12\x1c\n\x17MustTargetSummonedUnits\x10\xb0\x01\x12\x1c\n\x17\x43\x61ntTargetSummonedUnits\x10\xb1\x01\x12\x14\n\x0fMustTargetUser1\x10\xb2\x01\x12\x14\n\x0f\x43\x61ntTargetUser1\x10\xb3\x01\x12\x1f\n\x1aMustTargetUnstoppableUnits\x10\xb4\x01\x12\x1f\n\x1a\x43\x61ntTargetUnstoppableUnits\x10\xb5\x01\x12\x1d\n\x18MustTargetResistantUnits\x10\xb6\x01\x12\x1d\n\x18\x43\x61ntTargetResistantUnits\x10\xb7\x01\x12\x19\n\x14MustTargetDazedUnits\x10\xb8\x01\x12\x19\n\x14\x43\x61ntTargetDazedUnits\x10\xb9\x01\x12\x11\n\x0c\x43\x61ntLockdown\x10\xba\x01\x12\x14\n\x0f\x43\x61ntMindControl\x10\xbb\x01\x12\x1c\n\x17MustTargetDestructibles\x10\xbc\x01\x12\x1c\n\x17\x43\x61ntTargetDestructibles\x10\xbd\x01\x12\x14\n\x0fMustTargetItems\x10\xbe\x01\x12\x14\n\x0f\x43\x61ntTargetItems\x10\xbf\x01\x12\x18\n\x13NoCalldownAvailable\x10\xc0\x01\x12\x15\n\x10WaypointListFull\x10\xc1\x01\x12\x13\n\x0eMustTargetRace\x10\xc2\x01\x12\x13\n\x0e\x43\x61ntTargetRace\x10\xc3\x01\x12\x1b\n\x16MustTargetSimilarUnits\x10\xc4\x01\x12\x1b\n\x16\x43\x61ntTargetSimilarUnits\x10\xc5\x01\x12\x1a\n\x15\x43\x61ntFindEnoughTargets\x10\xc6\x01\x12\x19\n\x14\x41lreadySpawningLarva\x10\xc7\x01\x12!\n\x1c\x43\x61ntTargetExhaustedResources\x10\xc8\x01\x12\x13\n\x0e\x43\x61ntUseMinimap\x10\xc9\x01\x12\x15\n\x10\x43\x61ntUseInfoPanel\x10\xca\x01\x12\x15\n\x10OrderQueueIsFull\x10\xcb\x01\x12\x1c\n\x17\x43\x61ntHarvestThatResource\x10\xcc\x01\x12\x1a\n\x15HarvestersNotRequired\x10\xcd\x01\x12\x14\n\x0f\x41lreadyTargeted\x10\xce\x01\x12\x1e\n\x19\x43\x61ntAttackWeaponsDisabled\x10\xcf\x01\x12\x17\n\x12\x43ouldntReachTarget\x10\xd0\x01\x12\x17\n\x12TargetIsOutOfRange\x10\xd1\x01\x12\x15\n\x10TargetIsTooClose\x10\xd2\x01\x12\x15\n\x10TargetIsOutOfArc\x10\xd3\x01\x12\x1d\n\x18\x43\x61ntFindTeleportLocation\x10\xd4\x01\x12\x15\n\x10InvalidItemClass\x10\xd5\x01\x12\x18\n\x13\x43\x61ntFindCancelOrder\x10\xd6\x01')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.error_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_ACTIONRESULT']._serialized_start=49
+ _globals['_ACTIONRESULT']._serialized_end=5849
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/query_pb2.py b/worlds/_sc2common/bot/proto/query_pb2.py
new file mode 100644
index 000000000000..40d09ecce156
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/query_pb2.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/query.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/query.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import common_pb2 as s2clientprotocol_dot_common__pb2
+from . import error_pb2 as s2clientprotocol_dot_error__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/query.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\x1a\x1cs2clientprotocol/error.proto\"\xf0\x01\n\x0cRequestQuery\x12\x34\n\x07pathing\x18\x01 \x03(\x0b\x32#.SC2APIProtocol.RequestQueryPathing\x12\x41\n\tabilities\x18\x02 \x03(\x0b\x32..SC2APIProtocol.RequestQueryAvailableAbilities\x12\x41\n\nplacements\x18\x03 \x03(\x0b\x32-.SC2APIProtocol.RequestQueryBuildingPlacement\x12$\n\x1cignore_resource_requirements\x18\x04 \x01(\x08\"\xce\x01\n\rResponseQuery\x12\x35\n\x07pathing\x18\x01 \x03(\x0b\x32$.SC2APIProtocol.ResponseQueryPathing\x12\x42\n\tabilities\x18\x02 \x03(\x0b\x32/.SC2APIProtocol.ResponseQueryAvailableAbilities\x12\x42\n\nplacements\x18\x03 \x03(\x0b\x32..SC2APIProtocol.ResponseQueryBuildingPlacement\"\x8a\x01\n\x13RequestQueryPathing\x12,\n\tstart_pos\x18\x01 \x01(\x0b\x32\x17.SC2APIProtocol.Point2DH\x00\x12\x12\n\x08unit_tag\x18\x02 \x01(\x04H\x00\x12(\n\x07\x65nd_pos\x18\x03 \x01(\x0b\x32\x17.SC2APIProtocol.Point2DB\x07\n\x05start\"(\n\x14ResponseQueryPathing\x12\x10\n\x08\x64istance\x18\x01 \x01(\x02\"2\n\x1eRequestQueryAvailableAbilities\x12\x10\n\x08unit_tag\x18\x01 \x01(\x04\"~\n\x1fResponseQueryAvailableAbilities\x12\x33\n\tabilities\x18\x01 \x03(\x0b\x32 .SC2APIProtocol.AvailableAbility\x12\x10\n\x08unit_tag\x18\x02 \x01(\x04\x12\x14\n\x0cunit_type_id\x18\x03 \x01(\r\"z\n\x1dRequestQueryBuildingPlacement\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12+\n\ntarget_pos\x18\x02 \x01(\x0b\x32\x17.SC2APIProtocol.Point2D\x12\x18\n\x10placing_unit_tag\x18\x03 \x01(\x04\"N\n\x1eResponseQueryBuildingPlacement\x12,\n\x06result\x18\x01 \x01(\x0e\x32\x1c.SC2APIProtocol.ActionResult')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.query_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_REQUESTQUERY']._serialized_start=110
+ _globals['_REQUESTQUERY']._serialized_end=350
+ _globals['_RESPONSEQUERY']._serialized_start=353
+ _globals['_RESPONSEQUERY']._serialized_end=559
+ _globals['_REQUESTQUERYPATHING']._serialized_start=562
+ _globals['_REQUESTQUERYPATHING']._serialized_end=700
+ _globals['_RESPONSEQUERYPATHING']._serialized_start=702
+ _globals['_RESPONSEQUERYPATHING']._serialized_end=742
+ _globals['_REQUESTQUERYAVAILABLEABILITIES']._serialized_start=744
+ _globals['_REQUESTQUERYAVAILABLEABILITIES']._serialized_end=794
+ _globals['_RESPONSEQUERYAVAILABLEABILITIES']._serialized_start=796
+ _globals['_RESPONSEQUERYAVAILABLEABILITIES']._serialized_end=922
+ _globals['_REQUESTQUERYBUILDINGPLACEMENT']._serialized_start=924
+ _globals['_REQUESTQUERYBUILDINGPLACEMENT']._serialized_end=1046
+ _globals['_RESPONSEQUERYBUILDINGPLACEMENT']._serialized_start=1048
+ _globals['_RESPONSEQUERYBUILDINGPLACEMENT']._serialized_end=1126
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/raw_pb2.py b/worlds/_sc2common/bot/proto/raw_pb2.py
new file mode 100644
index 000000000000..3eb9ea182c89
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/raw_pb2.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/raw.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/raw.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import common_pb2 as s2clientprotocol_dot_common__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1as2clientprotocol/raw.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\xb1\x02\n\x08StartRaw\x12)\n\x08map_size\x18\x01 \x01(\x0b\x32\x17.SC2APIProtocol.Size2DI\x12/\n\x0cpathing_grid\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0eterrain_height\x18\x03 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0eplacement_grid\x18\x04 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\rplayable_area\x18\x05 \x01(\x0b\x32\x1a.SC2APIProtocol.RectangleI\x12\x30\n\x0fstart_locations\x18\x06 \x03(\x0b\x32\x17.SC2APIProtocol.Point2D\"\x86\x02\n\x0eObservationRaw\x12)\n\x06player\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.PlayerRaw\x12#\n\x05units\x18\x02 \x03(\x0b\x32\x14.SC2APIProtocol.Unit\x12+\n\tmap_state\x18\x03 \x01(\x0b\x32\x18.SC2APIProtocol.MapState\x12$\n\x05\x65vent\x18\x04 \x01(\x0b\x32\x15.SC2APIProtocol.Event\x12\'\n\x07\x65\x66\x66\x65\x63ts\x18\x05 \x03(\x0b\x32\x16.SC2APIProtocol.Effect\x12(\n\x05radar\x18\x06 \x03(\x0b\x32\x19.SC2APIProtocol.RadarRing\"?\n\tRadarRing\x12\"\n\x03pos\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x0e\n\x06radius\x18\x02 \x01(\x02\"N\n\x0bPowerSource\x12\"\n\x03pos\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x0e\n\x06radius\x18\x02 \x01(\x02\x12\x0b\n\x03tag\x18\x03 \x01(\x04\"{\n\tPlayerRaw\x12\x32\n\rpower_sources\x18\x01 \x03(\x0b\x32\x1b.SC2APIProtocol.PowerSource\x12%\n\x06\x63\x61mera\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x13\n\x0bupgrade_ids\x18\x03 \x03(\r\"\x8f\x01\n\tUnitOrder\x12\x12\n\nability_id\x18\x01 \x01(\r\x12\x37\n\x16target_world_space_pos\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.PointH\x00\x12\x19\n\x0ftarget_unit_tag\x18\x03 \x01(\x04H\x00\x12\x10\n\x08progress\x18\x04 \x01(\x02\x42\x08\n\x06target\"\x9b\x01\n\rPassengerUnit\x12\x0b\n\x03tag\x18\x01 \x01(\x04\x12\x0e\n\x06health\x18\x02 \x01(\x02\x12\x12\n\nhealth_max\x18\x03 \x01(\x02\x12\x0e\n\x06shield\x18\x04 \x01(\x02\x12\x12\n\nshield_max\x18\x07 \x01(\x02\x12\x0e\n\x06\x65nergy\x18\x05 \x01(\x02\x12\x12\n\nenergy_max\x18\x08 \x01(\x02\x12\x11\n\tunit_type\x18\x06 \x01(\r\"@\n\x0bRallyTarget\x12$\n\x05point\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x0b\n\x03tag\x18\x02 \x01(\x04\"\xf5\x08\n\x04Unit\x12\x31\n\x0c\x64isplay_type\x18\x01 \x01(\x0e\x32\x1b.SC2APIProtocol.DisplayType\x12*\n\x08\x61lliance\x18\x02 \x01(\x0e\x32\x18.SC2APIProtocol.Alliance\x12\x0b\n\x03tag\x18\x03 \x01(\x04\x12\x11\n\tunit_type\x18\x04 \x01(\r\x12\r\n\x05owner\x18\x05 \x01(\x05\x12\"\n\x03pos\x18\x06 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x0e\n\x06\x66\x61\x63ing\x18\x07 \x01(\x02\x12\x0e\n\x06radius\x18\x08 \x01(\x02\x12\x16\n\x0e\x62uild_progress\x18\t \x01(\x02\x12)\n\x05\x63loak\x18\n \x01(\x0e\x32\x1a.SC2APIProtocol.CloakState\x12\x10\n\x08\x62uff_ids\x18\x1b \x03(\r\x12\x14\n\x0c\x64\x65tect_range\x18\x1f \x01(\x02\x12\x13\n\x0bradar_range\x18 \x01(\x02\x12\x13\n\x0bis_selected\x18\x0b \x01(\x08\x12\x14\n\x0cis_on_screen\x18\x0c \x01(\x08\x12\x0f\n\x07is_blip\x18\r \x01(\x08\x12\x12\n\nis_powered\x18# \x01(\x08\x12\x11\n\tis_active\x18\' \x01(\x08\x12\x1c\n\x14\x61ttack_upgrade_level\x18( \x01(\x05\x12\x1b\n\x13\x61rmor_upgrade_level\x18) \x01(\x05\x12\x1c\n\x14shield_upgrade_level\x18* \x01(\x05\x12\x0e\n\x06health\x18\x0e \x01(\x02\x12\x12\n\nhealth_max\x18\x0f \x01(\x02\x12\x0e\n\x06shield\x18\x10 \x01(\x02\x12\x12\n\nshield_max\x18$ \x01(\x02\x12\x0e\n\x06\x65nergy\x18\x11 \x01(\x02\x12\x12\n\nenergy_max\x18% \x01(\x02\x12\x18\n\x10mineral_contents\x18\x12 \x01(\x05\x12\x18\n\x10vespene_contents\x18\x13 \x01(\x05\x12\x11\n\tis_flying\x18\x14 \x01(\x08\x12\x13\n\x0bis_burrowed\x18\x15 \x01(\x08\x12\x18\n\x10is_hallucination\x18& \x01(\x08\x12)\n\x06orders\x18\x16 \x03(\x0b\x32\x19.SC2APIProtocol.UnitOrder\x12\x12\n\nadd_on_tag\x18\x17 \x01(\x04\x12\x31\n\npassengers\x18\x18 \x03(\x0b\x32\x1d.SC2APIProtocol.PassengerUnit\x12\x19\n\x11\x63\x61rgo_space_taken\x18\x19 \x01(\x05\x12\x17\n\x0f\x63\x61rgo_space_max\x18\x1a \x01(\x05\x12\x1b\n\x13\x61ssigned_harvesters\x18\x1c \x01(\x05\x12\x18\n\x10ideal_harvesters\x18\x1d \x01(\x05\x12\x17\n\x0fweapon_cooldown\x18\x1e \x01(\x02\x12\x1a\n\x12\x65ngaged_target_tag\x18\" \x01(\x04\x12\x1c\n\x14\x62uff_duration_remain\x18+ \x01(\x05\x12\x19\n\x11\x62uff_duration_max\x18, \x01(\x05\x12\x32\n\rrally_targets\x18- \x03(\x0b\x32\x1b.SC2APIProtocol.RallyTarget\"c\n\x08MapState\x12-\n\nvisibility\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x63reep\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"\x1b\n\x05\x45vent\x12\x12\n\ndead_units\x18\x01 \x03(\x04\"\x8c\x01\n\x06\x45\x66\x66\x65\x63t\x12\x11\n\teffect_id\x18\x01 \x01(\r\x12$\n\x03pos\x18\x02 \x03(\x0b\x32\x17.SC2APIProtocol.Point2D\x12*\n\x08\x61lliance\x18\x03 \x01(\x0e\x32\x18.SC2APIProtocol.Alliance\x12\r\n\x05owner\x18\x04 \x01(\x05\x12\x0e\n\x06radius\x18\x05 \x01(\x02\"\xd3\x01\n\tActionRaw\x12<\n\x0cunit_command\x18\x01 \x01(\x0b\x32$.SC2APIProtocol.ActionRawUnitCommandH\x00\x12:\n\x0b\x63\x61mera_move\x18\x02 \x01(\x0b\x32#.SC2APIProtocol.ActionRawCameraMoveH\x00\x12\x42\n\x0ftoggle_autocast\x18\x03 \x01(\x0b\x32\'.SC2APIProtocol.ActionRawToggleAutocastH\x00\x42\x08\n\x06\x61\x63tion\"\xb4\x01\n\x14\x41\x63tionRawUnitCommand\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12\x39\n\x16target_world_space_pos\x18\x02 \x01(\x0b\x32\x17.SC2APIProtocol.Point2DH\x00\x12\x19\n\x0ftarget_unit_tag\x18\x03 \x01(\x04H\x00\x12\x11\n\tunit_tags\x18\x04 \x03(\x04\x12\x15\n\rqueue_command\x18\x05 \x01(\x08\x42\x08\n\x06target\"H\n\x13\x41\x63tionRawCameraMove\x12\x31\n\x12\x63\x65nter_world_space\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Point\"@\n\x17\x41\x63tionRawToggleAutocast\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12\x11\n\tunit_tags\x18\x02 \x03(\x04*E\n\x0b\x44isplayType\x12\x0b\n\x07Visible\x10\x01\x12\x0c\n\x08Snapshot\x10\x02\x12\n\n\x06Hidden\x10\x03\x12\x0f\n\x0bPlaceholder\x10\x04*6\n\x08\x41lliance\x12\x08\n\x04Self\x10\x01\x12\x08\n\x04\x41lly\x10\x02\x12\x0b\n\x07Neutral\x10\x03\x12\t\n\x05\x45nemy\x10\x04*e\n\nCloakState\x12\x12\n\x0e\x43loakedUnknown\x10\x00\x12\x0b\n\x07\x43loaked\x10\x01\x12\x13\n\x0f\x43loakedDetected\x10\x02\x12\x0e\n\nNotCloaked\x10\x03\x12\x11\n\rCloakedAllied\x10\x04')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.raw_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_DISPLAYTYPE']._serialized_start=3244
+ _globals['_DISPLAYTYPE']._serialized_end=3313
+ _globals['_ALLIANCE']._serialized_start=3315
+ _globals['_ALLIANCE']._serialized_end=3369
+ _globals['_CLOAKSTATE']._serialized_start=3371
+ _globals['_CLOAKSTATE']._serialized_end=3472
+ _globals['_STARTRAW']._serialized_start=78
+ _globals['_STARTRAW']._serialized_end=383
+ _globals['_OBSERVATIONRAW']._serialized_start=386
+ _globals['_OBSERVATIONRAW']._serialized_end=648
+ _globals['_RADARRING']._serialized_start=650
+ _globals['_RADARRING']._serialized_end=713
+ _globals['_POWERSOURCE']._serialized_start=715
+ _globals['_POWERSOURCE']._serialized_end=793
+ _globals['_PLAYERRAW']._serialized_start=795
+ _globals['_PLAYERRAW']._serialized_end=918
+ _globals['_UNITORDER']._serialized_start=921
+ _globals['_UNITORDER']._serialized_end=1064
+ _globals['_PASSENGERUNIT']._serialized_start=1067
+ _globals['_PASSENGERUNIT']._serialized_end=1222
+ _globals['_RALLYTARGET']._serialized_start=1224
+ _globals['_RALLYTARGET']._serialized_end=1288
+ _globals['_UNIT']._serialized_start=1291
+ _globals['_UNIT']._serialized_end=2432
+ _globals['_MAPSTATE']._serialized_start=2434
+ _globals['_MAPSTATE']._serialized_end=2533
+ _globals['_EVENT']._serialized_start=2535
+ _globals['_EVENT']._serialized_end=2562
+ _globals['_EFFECT']._serialized_start=2565
+ _globals['_EFFECT']._serialized_end=2705
+ _globals['_ACTIONRAW']._serialized_start=2708
+ _globals['_ACTIONRAW']._serialized_end=2919
+ _globals['_ACTIONRAWUNITCOMMAND']._serialized_start=2922
+ _globals['_ACTIONRAWUNITCOMMAND']._serialized_end=3102
+ _globals['_ACTIONRAWCAMERAMOVE']._serialized_start=3104
+ _globals['_ACTIONRAWCAMERAMOVE']._serialized_end=3176
+ _globals['_ACTIONRAWTOGGLEAUTOCAST']._serialized_start=3178
+ _globals['_ACTIONRAWTOGGLEAUTOCAST']._serialized_end=3242
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/sc2api_pb2.py b/worlds/_sc2common/bot/proto/sc2api_pb2.py
new file mode 100644
index 000000000000..55a4d010083f
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/sc2api_pb2.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/sc2api.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/sc2api.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import common_pb2 as s2clientprotocol_dot_common__pb2
+from . import data_pb2 as s2clientprotocol_dot_data__pb2
+from . import debug_pb2 as s2clientprotocol_dot_debug__pb2
+from . import error_pb2 as s2clientprotocol_dot_error__pb2
+from . import query_pb2 as s2clientprotocol_dot_query__pb2
+from . import raw_pb2 as s2clientprotocol_dot_raw__pb2
+from . import score_pb2 as s2clientprotocol_dot_score__pb2
+from . import spatial_pb2 as s2clientprotocol_dot_spatial__pb2
+from . import ui_pb2 as s2clientprotocol_dot_ui__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ds2clientprotocol/sc2api.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\x1a\x1bs2clientprotocol/data.proto\x1a\x1cs2clientprotocol/debug.proto\x1a\x1cs2clientprotocol/error.proto\x1a\x1cs2clientprotocol/query.proto\x1a\x1as2clientprotocol/raw.proto\x1a\x1cs2clientprotocol/score.proto\x1a\x1es2clientprotocol/spatial.proto\x1a\x19s2clientprotocol/ui.proto\"\xc3\t\n\x07Request\x12\x38\n\x0b\x63reate_game\x18\x01 \x01(\x0b\x32!.SC2APIProtocol.RequestCreateGameH\x00\x12\x34\n\tjoin_game\x18\x02 \x01(\x0b\x32\x1f.SC2APIProtocol.RequestJoinGameH\x00\x12:\n\x0crestart_game\x18\x03 \x01(\x0b\x32\".SC2APIProtocol.RequestRestartGameH\x00\x12:\n\x0cstart_replay\x18\x04 \x01(\x0b\x32\".SC2APIProtocol.RequestStartReplayH\x00\x12\x36\n\nleave_game\x18\x05 \x01(\x0b\x32 .SC2APIProtocol.RequestLeaveGameH\x00\x12\x36\n\nquick_save\x18\x06 \x01(\x0b\x32 .SC2APIProtocol.RequestQuickSaveH\x00\x12\x36\n\nquick_load\x18\x07 \x01(\x0b\x32 .SC2APIProtocol.RequestQuickLoadH\x00\x12+\n\x04quit\x18\x08 \x01(\x0b\x32\x1b.SC2APIProtocol.RequestQuitH\x00\x12\x34\n\tgame_info\x18\t \x01(\x0b\x32\x1f.SC2APIProtocol.RequestGameInfoH\x00\x12\x39\n\x0bobservation\x18\n \x01(\x0b\x32\".SC2APIProtocol.RequestObservationH\x00\x12/\n\x06\x61\x63tion\x18\x0b \x01(\x0b\x32\x1d.SC2APIProtocol.RequestActionH\x00\x12;\n\nobs_action\x18\x15 \x01(\x0b\x32%.SC2APIProtocol.RequestObserverActionH\x00\x12+\n\x04step\x18\x0c \x01(\x0b\x32\x1b.SC2APIProtocol.RequestStepH\x00\x12+\n\x04\x64\x61ta\x18\r \x01(\x0b\x32\x1b.SC2APIProtocol.RequestDataH\x00\x12-\n\x05query\x18\x0e \x01(\x0b\x32\x1c.SC2APIProtocol.RequestQueryH\x00\x12\x38\n\x0bsave_replay\x18\x0f \x01(\x0b\x32!.SC2APIProtocol.RequestSaveReplayH\x00\x12\x38\n\x0bmap_command\x18\x16 \x01(\x0b\x32!.SC2APIProtocol.RequestMapCommandH\x00\x12\x38\n\x0breplay_info\x18\x10 \x01(\x0b\x32!.SC2APIProtocol.RequestReplayInfoH\x00\x12>\n\x0e\x61vailable_maps\x18\x11 \x01(\x0b\x32$.SC2APIProtocol.RequestAvailableMapsH\x00\x12\x32\n\x08save_map\x18\x12 \x01(\x0b\x32\x1e.SC2APIProtocol.RequestSaveMapH\x00\x12+\n\x04ping\x18\x13 \x01(\x0b\x32\x1b.SC2APIProtocol.RequestPingH\x00\x12-\n\x05\x64\x65\x62ug\x18\x14 \x01(\x0b\x32\x1c.SC2APIProtocol.RequestDebugH\x00\x12\n\n\x02id\x18\x61 \x01(\rB\t\n\x07request\"\x92\n\n\x08Response\x12\x39\n\x0b\x63reate_game\x18\x01 \x01(\x0b\x32\".SC2APIProtocol.ResponseCreateGameH\x00\x12\x35\n\tjoin_game\x18\x02 \x01(\x0b\x32 .SC2APIProtocol.ResponseJoinGameH\x00\x12;\n\x0crestart_game\x18\x03 \x01(\x0b\x32#.SC2APIProtocol.ResponseRestartGameH\x00\x12;\n\x0cstart_replay\x18\x04 \x01(\x0b\x32#.SC2APIProtocol.ResponseStartReplayH\x00\x12\x37\n\nleave_game\x18\x05 \x01(\x0b\x32!.SC2APIProtocol.ResponseLeaveGameH\x00\x12\x37\n\nquick_save\x18\x06 \x01(\x0b\x32!.SC2APIProtocol.ResponseQuickSaveH\x00\x12\x37\n\nquick_load\x18\x07 \x01(\x0b\x32!.SC2APIProtocol.ResponseQuickLoadH\x00\x12,\n\x04quit\x18\x08 \x01(\x0b\x32\x1c.SC2APIProtocol.ResponseQuitH\x00\x12\x35\n\tgame_info\x18\t \x01(\x0b\x32 .SC2APIProtocol.ResponseGameInfoH\x00\x12:\n\x0bobservation\x18\n \x01(\x0b\x32#.SC2APIProtocol.ResponseObservationH\x00\x12\x30\n\x06\x61\x63tion\x18\x0b \x01(\x0b\x32\x1e.SC2APIProtocol.ResponseActionH\x00\x12<\n\nobs_action\x18\x15 \x01(\x0b\x32&.SC2APIProtocol.ResponseObserverActionH\x00\x12,\n\x04step\x18\x0c \x01(\x0b\x32\x1c.SC2APIProtocol.ResponseStepH\x00\x12,\n\x04\x64\x61ta\x18\r \x01(\x0b\x32\x1c.SC2APIProtocol.ResponseDataH\x00\x12.\n\x05query\x18\x0e \x01(\x0b\x32\x1d.SC2APIProtocol.ResponseQueryH\x00\x12\x39\n\x0bsave_replay\x18\x0f \x01(\x0b\x32\".SC2APIProtocol.ResponseSaveReplayH\x00\x12\x39\n\x0breplay_info\x18\x10 \x01(\x0b\x32\".SC2APIProtocol.ResponseReplayInfoH\x00\x12?\n\x0e\x61vailable_maps\x18\x11 \x01(\x0b\x32%.SC2APIProtocol.ResponseAvailableMapsH\x00\x12\x33\n\x08save_map\x18\x12 \x01(\x0b\x32\x1f.SC2APIProtocol.ResponseSaveMapH\x00\x12\x39\n\x0bmap_command\x18\x16 \x01(\x0b\x32\".SC2APIProtocol.ResponseMapCommandH\x00\x12,\n\x04ping\x18\x13 \x01(\x0b\x32\x1c.SC2APIProtocol.ResponsePingH\x00\x12.\n\x05\x64\x65\x62ug\x18\x14 \x01(\x0b\x32\x1d.SC2APIProtocol.ResponseDebugH\x00\x12\n\n\x02id\x18\x61 \x01(\r\x12\r\n\x05\x65rror\x18\x62 \x03(\t\x12&\n\x06status\x18\x63 \x01(\x0e\x32\x16.SC2APIProtocol.StatusB\n\n\x08response\"\xd6\x01\n\x11RequestCreateGame\x12-\n\tlocal_map\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.LocalMapH\x00\x12\x1c\n\x12\x62\x61ttlenet_map_name\x18\x02 \x01(\tH\x00\x12\x31\n\x0cplayer_setup\x18\x03 \x03(\x0b\x32\x1b.SC2APIProtocol.PlayerSetup\x12\x13\n\x0b\x64isable_fog\x18\x04 \x01(\x08\x12\x13\n\x0brandom_seed\x18\x05 \x01(\r\x12\x10\n\x08realtime\x18\x06 \x01(\x08\x42\x05\n\x03Map\".\n\x08LocalMap\x12\x10\n\x08map_path\x18\x01 \x01(\t\x12\x10\n\x08map_data\x18\x07 \x01(\x0c\"\x9c\x02\n\x12ResponseCreateGame\x12\x37\n\x05\x65rror\x18\x01 \x01(\x0e\x32(.SC2APIProtocol.ResponseCreateGame.Error\x12\x15\n\rerror_details\x18\x02 \x01(\t\"\xb5\x01\n\x05\x45rror\x12\x0e\n\nMissingMap\x10\x01\x12\x12\n\x0eInvalidMapPath\x10\x02\x12\x12\n\x0eInvalidMapData\x10\x03\x12\x12\n\x0eInvalidMapName\x10\x04\x12\x14\n\x10InvalidMapHandle\x10\x05\x12\x16\n\x12MissingPlayerSetup\x10\x06\x12\x16\n\x12InvalidPlayerSetup\x10\x07\x12\x1a\n\x16MultiplayerUnsupported\x10\x08\"\xb2\x02\n\x0fRequestJoinGame\x12$\n\x04race\x18\x01 \x01(\x0e\x32\x14.SC2APIProtocol.RaceH\x00\x12\x1c\n\x12observed_player_id\x18\x02 \x01(\rH\x00\x12\x31\n\x07options\x18\x03 \x01(\x0b\x32 .SC2APIProtocol.InterfaceOptions\x12-\n\x0cserver_ports\x18\x04 \x01(\x0b\x32\x17.SC2APIProtocol.PortSet\x12-\n\x0c\x63lient_ports\x18\x05 \x03(\x0b\x32\x17.SC2APIProtocol.PortSet\x12\x13\n\x0bshared_port\x18\x06 \x01(\x05\x12\x13\n\x0bplayer_name\x18\x07 \x01(\t\x12\x0f\n\x07host_ip\x18\x08 \x01(\tB\x0f\n\rparticipation\"/\n\x07PortSet\x12\x11\n\tgame_port\x18\x01 \x01(\x05\x12\x11\n\tbase_port\x18\x02 \x01(\x05\"\x82\x03\n\x10ResponseJoinGame\x12\x11\n\tplayer_id\x18\x01 \x01(\r\x12\x35\n\x05\x65rror\x18\x02 \x01(\x0e\x32&.SC2APIProtocol.ResponseJoinGame.Error\x12\x15\n\rerror_details\x18\x03 \x01(\t\"\x8c\x02\n\x05\x45rror\x12\x18\n\x14MissingParticipation\x10\x01\x12\x1b\n\x17InvalidObservedPlayerId\x10\x02\x12\x12\n\x0eMissingOptions\x10\x03\x12\x10\n\x0cMissingPorts\x10\x04\x12\x0c\n\x08GameFull\x10\x05\x12\x0f\n\x0bLaunchError\x10\x06\x12\x16\n\x12\x46\x65\x61tureUnsupported\x10\x07\x12\x12\n\x0eNoSpaceForUser\x10\x08\x12\x13\n\x0fMapDoesNotExist\x10\t\x12\x11\n\rCannotOpenMap\x10\n\x12\x11\n\rChecksumError\x10\x0b\x12\x10\n\x0cNetworkError\x10\x0c\x12\x0e\n\nOtherError\x10\r\"\x14\n\x12RequestRestartGame\"\x99\x01\n\x13ResponseRestartGame\x12\x38\n\x05\x65rror\x18\x01 \x01(\x0e\x32).SC2APIProtocol.ResponseRestartGame.Error\x12\x15\n\rerror_details\x18\x02 \x01(\t\x12\x17\n\x0fneed_hard_reset\x18\x03 \x01(\x08\"\x18\n\x05\x45rror\x12\x0f\n\x0bLaunchError\x10\x01\"\xeb\x01\n\x12RequestStartReplay\x12\x15\n\x0breplay_path\x18\x01 \x01(\tH\x00\x12\x15\n\x0breplay_data\x18\x05 \x01(\x0cH\x00\x12\x10\n\x08map_data\x18\x06 \x01(\x0c\x12\x1a\n\x12observed_player_id\x18\x02 \x01(\x05\x12\x31\n\x07options\x18\x03 \x01(\x0b\x32 .SC2APIProtocol.InterfaceOptions\x12\x13\n\x0b\x64isable_fog\x18\x04 \x01(\x08\x12\x10\n\x08realtime\x18\x07 \x01(\x08\x12\x15\n\rrecord_replay\x18\x08 \x01(\x08\x42\x08\n\x06replay\"\x87\x02\n\x13ResponseStartReplay\x12\x38\n\x05\x65rror\x18\x01 \x01(\x0e\x32).SC2APIProtocol.ResponseStartReplay.Error\x12\x15\n\rerror_details\x18\x02 \x01(\t\"\x9e\x01\n\x05\x45rror\x12\x11\n\rMissingReplay\x10\x01\x12\x15\n\x11InvalidReplayPath\x10\x02\x12\x15\n\x11InvalidReplayData\x10\x03\x12\x12\n\x0eInvalidMapData\x10\x04\x12\x1b\n\x17InvalidObservedPlayerId\x10\x05\x12\x12\n\x0eMissingOptions\x10\x06\x12\x0f\n\x0bLaunchError\x10\x07\"(\n\x11RequestMapCommand\x12\x13\n\x0btrigger_cmd\x18\x01 \x01(\t\"\x81\x01\n\x12ResponseMapCommand\x12\x37\n\x05\x65rror\x18\x01 \x01(\x0e\x32(.SC2APIProtocol.ResponseMapCommand.Error\x12\x15\n\rerror_details\x18\x02 \x01(\t\"\x1b\n\x05\x45rror\x12\x12\n\x0eNoTriggerError\x10\x01\"\x12\n\x10RequestLeaveGame\"\x13\n\x11ResponseLeaveGame\"\x12\n\x10RequestQuickSave\"\x13\n\x11ResponseQuickSave\"\x12\n\x10RequestQuickLoad\"\x13\n\x11ResponseQuickLoad\"\r\n\x0bRequestQuit\"\x0e\n\x0cResponseQuit\"\x11\n\x0fRequestGameInfo\"\xe0\x01\n\x10ResponseGameInfo\x12\x10\n\x08map_name\x18\x01 \x01(\t\x12\x11\n\tmod_names\x18\x06 \x03(\t\x12\x16\n\x0elocal_map_path\x18\x02 \x01(\t\x12/\n\x0bplayer_info\x18\x03 \x03(\x0b\x32\x1a.SC2APIProtocol.PlayerInfo\x12+\n\tstart_raw\x18\x04 \x01(\x0b\x32\x18.SC2APIProtocol.StartRaw\x12\x31\n\x07options\x18\x05 \x01(\x0b\x32 .SC2APIProtocol.InterfaceOptions\"<\n\x12RequestObservation\x12\x13\n\x0b\x64isable_fog\x18\x01 \x01(\x08\x12\x11\n\tgame_loop\x18\x02 \x01(\r\"\x85\x02\n\x13ResponseObservation\x12\'\n\x07\x61\x63tions\x18\x01 \x03(\x0b\x32\x16.SC2APIProtocol.Action\x12\x32\n\raction_errors\x18\x02 \x03(\x0b\x32\x1b.SC2APIProtocol.ActionError\x12\x30\n\x0bobservation\x18\x03 \x01(\x0b\x32\x1b.SC2APIProtocol.Observation\x12\x33\n\rplayer_result\x18\x04 \x03(\x0b\x32\x1c.SC2APIProtocol.PlayerResult\x12*\n\x04\x63hat\x18\x05 \x03(\x0b\x32\x1c.SC2APIProtocol.ChatReceived\"2\n\x0c\x43hatReceived\x12\x11\n\tplayer_id\x18\x01 \x01(\r\x12\x0f\n\x07message\x18\x02 \x01(\t\"8\n\rRequestAction\x12\'\n\x07\x61\x63tions\x18\x01 \x03(\x0b\x32\x16.SC2APIProtocol.Action\">\n\x0eResponseAction\x12,\n\x06result\x18\x01 \x03(\x0e\x32\x1c.SC2APIProtocol.ActionResult\"H\n\x15RequestObserverAction\x12/\n\x07\x61\x63tions\x18\x01 \x03(\x0b\x32\x1e.SC2APIProtocol.ObserverAction\"\x18\n\x16ResponseObserverAction\"\x1c\n\x0bRequestStep\x12\r\n\x05\x63ount\x18\x01 \x01(\r\"\'\n\x0cResponseStep\x12\x17\n\x0fsimulation_loop\x18\x01 \x01(\r\"o\n\x0bRequestData\x12\x12\n\nability_id\x18\x01 \x01(\x08\x12\x14\n\x0cunit_type_id\x18\x02 \x01(\x08\x12\x12\n\nupgrade_id\x18\x03 \x01(\x08\x12\x0f\n\x07\x62uff_id\x18\x04 \x01(\x08\x12\x11\n\teffect_id\x18\x05 \x01(\x08\"\xf0\x01\n\x0cResponseData\x12.\n\tabilities\x18\x01 \x03(\x0b\x32\x1b.SC2APIProtocol.AbilityData\x12+\n\x05units\x18\x02 \x03(\x0b\x32\x1c.SC2APIProtocol.UnitTypeData\x12-\n\x08upgrades\x18\x03 \x03(\x0b\x32\x1b.SC2APIProtocol.UpgradeData\x12\'\n\x05\x62uffs\x18\x04 \x03(\x0b\x32\x18.SC2APIProtocol.BuffData\x12+\n\x07\x65\x66\x66\x65\x63ts\x18\x05 \x03(\x0b\x32\x1a.SC2APIProtocol.EffectData\"\x13\n\x11RequestSaveReplay\"\"\n\x12ResponseSaveReplay\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"b\n\x11RequestReplayInfo\x12\x15\n\x0breplay_path\x18\x01 \x01(\tH\x00\x12\x15\n\x0breplay_data\x18\x02 \x01(\x0cH\x00\x12\x15\n\rdownload_data\x18\x03 \x01(\x08\x42\x08\n\x06replay\"\x9f\x01\n\x0fPlayerInfoExtra\x12/\n\x0bplayer_info\x18\x01 \x01(\x0b\x32\x1a.SC2APIProtocol.PlayerInfo\x12\x33\n\rplayer_result\x18\x02 \x01(\x0b\x32\x1c.SC2APIProtocol.PlayerResult\x12\x12\n\nplayer_mmr\x18\x03 \x01(\x05\x12\x12\n\nplayer_apm\x18\x04 \x01(\x05\"\xc3\x03\n\x12ResponseReplayInfo\x12\x10\n\x08map_name\x18\x01 \x01(\t\x12\x16\n\x0elocal_map_path\x18\x02 \x01(\t\x12\x34\n\x0bplayer_info\x18\x03 \x03(\x0b\x32\x1f.SC2APIProtocol.PlayerInfoExtra\x12\x1b\n\x13game_duration_loops\x18\x04 \x01(\r\x12\x1d\n\x15game_duration_seconds\x18\x05 \x01(\x02\x12\x14\n\x0cgame_version\x18\x06 \x01(\t\x12\x14\n\x0c\x64\x61ta_version\x18\x0b \x01(\t\x12\x12\n\ndata_build\x18\x07 \x01(\r\x12\x12\n\nbase_build\x18\x08 \x01(\r\x12\x37\n\x05\x65rror\x18\t \x01(\x0e\x32(.SC2APIProtocol.ResponseReplayInfo.Error\x12\x15\n\rerror_details\x18\n \x01(\t\"m\n\x05\x45rror\x12\x11\n\rMissingReplay\x10\x01\x12\x15\n\x11InvalidReplayPath\x10\x02\x12\x15\n\x11InvalidReplayData\x10\x03\x12\x10\n\x0cParsingError\x10\x04\x12\x11\n\rDownloadError\x10\x05\"\x16\n\x14RequestAvailableMaps\"M\n\x15ResponseAvailableMaps\x12\x17\n\x0flocal_map_paths\x18\x01 \x03(\t\x12\x1b\n\x13\x62\x61ttlenet_map_names\x18\x02 \x03(\t\"4\n\x0eRequestSaveMap\x12\x10\n\x08map_path\x18\x01 \x01(\t\x12\x10\n\x08map_data\x18\x02 \x01(\x0c\"d\n\x0fResponseSaveMap\x12\x34\n\x05\x65rror\x18\x01 \x01(\x0e\x32%.SC2APIProtocol.ResponseSaveMap.Error\"\x1b\n\x05\x45rror\x12\x12\n\x0eInvalidMapData\x10\x01\"\r\n\x0bRequestPing\"b\n\x0cResponsePing\x12\x14\n\x0cgame_version\x18\x01 \x01(\t\x12\x14\n\x0c\x64\x61ta_version\x18\x02 \x01(\t\x12\x12\n\ndata_build\x18\x03 \x01(\r\x12\x12\n\nbase_build\x18\x04 \x01(\r\";\n\x0cRequestDebug\x12+\n\x05\x64\x65\x62ug\x18\x01 \x03(\x0b\x32\x1c.SC2APIProtocol.DebugCommand\"\x0f\n\rResponseDebug\"\xcb\x01\n\x0bPlayerSetup\x12(\n\x04type\x18\x01 \x01(\x0e\x32\x1a.SC2APIProtocol.PlayerType\x12\"\n\x04race\x18\x02 \x01(\x0e\x32\x14.SC2APIProtocol.Race\x12.\n\ndifficulty\x18\x03 \x01(\x0e\x32\x1a.SC2APIProtocol.Difficulty\x12\x13\n\x0bplayer_name\x18\x04 \x01(\t\x12)\n\x08\x61i_build\x18\x05 \x01(\x0e\x32\x17.SC2APIProtocol.AIBuild\"\xc3\x01\n\x12SpatialCameraSetup\x12+\n\nresolution\x18\x02 \x01(\x0b\x32\x17.SC2APIProtocol.Size2DI\x12\x33\n\x12minimap_resolution\x18\x03 \x01(\x0b\x32\x17.SC2APIProtocol.Size2DI\x12\r\n\x05width\x18\x01 \x01(\x02\x12\x1d\n\x15\x63rop_to_playable_area\x18\x04 \x01(\x08\x12\x1d\n\x15\x61llow_cheating_layers\x18\x05 \x01(\x08\"\xaf\x02\n\x10InterfaceOptions\x12\x0b\n\x03raw\x18\x01 \x01(\x08\x12\r\n\x05score\x18\x02 \x01(\x08\x12\x39\n\rfeature_layer\x18\x03 \x01(\x0b\x32\".SC2APIProtocol.SpatialCameraSetup\x12\x32\n\x06render\x18\x04 \x01(\x0b\x32\".SC2APIProtocol.SpatialCameraSetup\x12\x14\n\x0cshow_cloaked\x18\x05 \x01(\x08\x12\x1d\n\x15show_burrowed_shadows\x18\t \x01(\x08\x12\x19\n\x11show_placeholders\x18\x08 \x01(\x08\x12\x1d\n\x15raw_affects_selection\x18\x06 \x01(\x08\x12!\n\x19raw_crop_to_playable_area\x18\x07 \x01(\x08\"\x92\x02\n\nPlayerInfo\x12\x11\n\tplayer_id\x18\x01 \x01(\r\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x1a.SC2APIProtocol.PlayerType\x12,\n\x0erace_requested\x18\x03 \x01(\x0e\x32\x14.SC2APIProtocol.Race\x12)\n\x0brace_actual\x18\x04 \x01(\x0e\x32\x14.SC2APIProtocol.Race\x12.\n\ndifficulty\x18\x05 \x01(\x0e\x32\x1a.SC2APIProtocol.Difficulty\x12)\n\x08\x61i_build\x18\x07 \x01(\x0e\x32\x17.SC2APIProtocol.AIBuild\x12\x13\n\x0bplayer_name\x18\x06 \x01(\t\"\xef\x01\n\x0cPlayerCommon\x12\x11\n\tplayer_id\x18\x01 \x01(\r\x12\x10\n\x08minerals\x18\x02 \x01(\r\x12\x0f\n\x07vespene\x18\x03 \x01(\r\x12\x10\n\x08\x66ood_cap\x18\x04 \x01(\r\x12\x11\n\tfood_used\x18\x05 \x01(\r\x12\x11\n\tfood_army\x18\x06 \x01(\r\x12\x14\n\x0c\x66ood_workers\x18\x07 \x01(\r\x12\x19\n\x11idle_worker_count\x18\x08 \x01(\r\x12\x12\n\narmy_count\x18\t \x01(\r\x12\x17\n\x0fwarp_gate_count\x18\n \x01(\r\x12\x13\n\x0blarva_count\x18\x0b \x01(\r\"\xb6\x03\n\x0bObservation\x12\x11\n\tgame_loop\x18\t \x01(\r\x12\x33\n\rplayer_common\x18\x01 \x01(\x0b\x32\x1c.SC2APIProtocol.PlayerCommon\x12%\n\x06\x61lerts\x18\n \x03(\x0e\x32\x15.SC2APIProtocol.Alert\x12\x33\n\tabilities\x18\x03 \x03(\x0b\x32 .SC2APIProtocol.AvailableAbility\x12$\n\x05score\x18\x04 \x01(\x0b\x32\x15.SC2APIProtocol.Score\x12\x30\n\x08raw_data\x18\x05 \x01(\x0b\x32\x1e.SC2APIProtocol.ObservationRaw\x12\x43\n\x12\x66\x65\x61ture_layer_data\x18\x06 \x01(\x0b\x32\'.SC2APIProtocol.ObservationFeatureLayer\x12\x36\n\x0brender_data\x18\x07 \x01(\x0b\x32!.SC2APIProtocol.ObservationRender\x12.\n\x07ui_data\x18\x08 \x01(\x0b\x32\x1d.SC2APIProtocol.ObservationUI\"\x9b\x02\n\x06\x41\x63tion\x12-\n\naction_raw\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ActionRaw\x12;\n\x14\x61\x63tion_feature_layer\x18\x02 \x01(\x0b\x32\x1d.SC2APIProtocol.ActionSpatial\x12\x34\n\raction_render\x18\x03 \x01(\x0b\x32\x1d.SC2APIProtocol.ActionSpatial\x12+\n\taction_ui\x18\x04 \x01(\x0b\x32\x18.SC2APIProtocol.ActionUI\x12/\n\x0b\x61\x63tion_chat\x18\x06 \x01(\x0b\x32\x1a.SC2APIProtocol.ActionChat\x12\x11\n\tgame_loop\x18\x07 \x01(\r\"v\n\nActionChat\x12\x33\n\x07\x63hannel\x18\x01 \x01(\x0e\x32\".SC2APIProtocol.ActionChat.Channel\x12\x0f\n\x07message\x18\x02 \x01(\t\"\"\n\x07\x43hannel\x12\r\n\tBroadcast\x10\x01\x12\x08\n\x04Team\x10\x02\"a\n\x0b\x41\x63tionError\x12\x10\n\x08unit_tag\x18\x01 \x01(\x04\x12\x12\n\nability_id\x18\x02 \x01(\x04\x12,\n\x06result\x18\x03 \x01(\x0e\x32\x1c.SC2APIProtocol.ActionResult\"\xcc\x02\n\x0eObserverAction\x12M\n\x12player_perspective\x18\x01 \x01(\x0b\x32/.SC2APIProtocol.ActionObserverPlayerPerspectiveH\x00\x12?\n\x0b\x63\x61mera_move\x18\x02 \x01(\x0b\x32(.SC2APIProtocol.ActionObserverCameraMoveH\x00\x12P\n\x14\x63\x61mera_follow_player\x18\x03 \x01(\x0b\x32\x30.SC2APIProtocol.ActionObserverCameraFollowPlayerH\x00\x12N\n\x13\x63\x61mera_follow_units\x18\x04 \x01(\x0b\x32/.SC2APIProtocol.ActionObserverCameraFollowUnitsH\x00\x42\x08\n\x06\x61\x63tion\"4\n\x1f\x41\x63tionObserverPlayerPerspective\x12\x11\n\tplayer_id\x18\x01 \x01(\r\"X\n\x18\x41\x63tionObserverCameraMove\x12*\n\tworld_pos\x18\x01 \x01(\x0b\x32\x17.SC2APIProtocol.Point2D\x12\x10\n\x08\x64istance\x18\x02 \x01(\x02\"5\n ActionObserverCameraFollowPlayer\x12\x11\n\tplayer_id\x18\x01 \x01(\r\"4\n\x1f\x41\x63tionObserverCameraFollowUnits\x12\x11\n\tunit_tags\x18\x01 \x03(\x04\"I\n\x0cPlayerResult\x12\x11\n\tplayer_id\x18\x01 \x01(\r\x12&\n\x06result\x18\x02 \x01(\x0e\x32\x16.SC2APIProtocol.Result*c\n\x06Status\x12\x0c\n\x08launched\x10\x01\x12\r\n\tinit_game\x10\x02\x12\x0b\n\x07in_game\x10\x03\x12\r\n\tin_replay\x10\x04\x12\t\n\x05\x65nded\x10\x05\x12\x08\n\x04quit\x10\x06\x12\x0b\n\x07unknown\x10\x63*\x96\x01\n\nDifficulty\x12\x0c\n\x08VeryEasy\x10\x01\x12\x08\n\x04\x45\x61sy\x10\x02\x12\n\n\x06Medium\x10\x03\x12\x0e\n\nMediumHard\x10\x04\x12\x08\n\x04Hard\x10\x05\x12\n\n\x06Harder\x10\x06\x12\x0c\n\x08VeryHard\x10\x07\x12\x0f\n\x0b\x43heatVision\x10\x08\x12\x0e\n\nCheatMoney\x10\t\x12\x0f\n\x0b\x43heatInsane\x10\n*9\n\nPlayerType\x12\x0f\n\x0bParticipant\x10\x01\x12\x0c\n\x08\x43omputer\x10\x02\x12\x0c\n\x08Observer\x10\x03*O\n\x07\x41IBuild\x12\x0f\n\x0bRandomBuild\x10\x01\x12\x08\n\x04Rush\x10\x02\x12\n\n\x06Timing\x10\x03\x12\t\n\x05Power\x10\x04\x12\t\n\x05Macro\x10\x05\x12\x07\n\x03\x41ir\x10\x06*\xdb\x03\n\x05\x41lert\x12\x0e\n\nAlertError\x10\x03\x12\x11\n\rAddOnComplete\x10\x04\x12\x14\n\x10\x42uildingComplete\x10\x05\x12\x17\n\x13\x42uildingUnderAttack\x10\x06\x12\x10\n\x0cLarvaHatched\x10\x07\x12\x11\n\rMergeComplete\x10\x08\x12\x15\n\x11MineralsExhausted\x10\t\x12\x11\n\rMorphComplete\x10\n\x12\x16\n\x12MothershipComplete\x10\x0b\x12\x0f\n\x0bMULEExpired\x10\x0c\x12\x19\n\x15NuclearLaunchDetected\x10\x01\x12\x10\n\x0cNukeComplete\x10\r\x12\x15\n\x11NydusWormDetected\x10\x02\x12\x14\n\x10ResearchComplete\x10\x0e\x12\x0e\n\nTrainError\x10\x0f\x12\x15\n\x11TrainUnitComplete\x10\x10\x12\x17\n\x13TrainWorkerComplete\x10\x11\x12\x1a\n\x16TransformationComplete\x10\x12\x12\x13\n\x0fUnitUnderAttack\x10\x13\x12\x13\n\x0fUpgradeComplete\x10\x14\x12\x14\n\x10VespeneExhausted\x10\x15\x12\x12\n\x0eWarpInComplete\x10\x16*9\n\x06Result\x12\x0b\n\x07Victory\x10\x01\x12\n\n\x06\x44\x65\x66\x65\x61t\x10\x02\x12\x07\n\x03Tie\x10\x03\x12\r\n\tUndecided\x10\x04')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.sc2api_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_STATUS']._serialized_start=10484
+ _globals['_STATUS']._serialized_end=10583
+ _globals['_DIFFICULTY']._serialized_start=10586
+ _globals['_DIFFICULTY']._serialized_end=10736
+ _globals['_PLAYERTYPE']._serialized_start=10738
+ _globals['_PLAYERTYPE']._serialized_end=10795
+ _globals['_AIBUILD']._serialized_start=10797
+ _globals['_AIBUILD']._serialized_end=10876
+ _globals['_ALERT']._serialized_start=10879
+ _globals['_ALERT']._serialized_end=11354
+ _globals['_RESULT']._serialized_start=11356
+ _globals['_RESULT']._serialized_end=11413
+ _globals['_REQUEST']._serialized_start=317
+ _globals['_REQUEST']._serialized_end=1536
+ _globals['_RESPONSE']._serialized_start=1539
+ _globals['_RESPONSE']._serialized_end=2837
+ _globals['_REQUESTCREATEGAME']._serialized_start=2840
+ _globals['_REQUESTCREATEGAME']._serialized_end=3054
+ _globals['_LOCALMAP']._serialized_start=3056
+ _globals['_LOCALMAP']._serialized_end=3102
+ _globals['_RESPONSECREATEGAME']._serialized_start=3105
+ _globals['_RESPONSECREATEGAME']._serialized_end=3389
+ _globals['_RESPONSECREATEGAME_ERROR']._serialized_start=3208
+ _globals['_RESPONSECREATEGAME_ERROR']._serialized_end=3389
+ _globals['_REQUESTJOINGAME']._serialized_start=3392
+ _globals['_REQUESTJOINGAME']._serialized_end=3698
+ _globals['_PORTSET']._serialized_start=3700
+ _globals['_PORTSET']._serialized_end=3747
+ _globals['_RESPONSEJOINGAME']._serialized_start=3750
+ _globals['_RESPONSEJOINGAME']._serialized_end=4136
+ _globals['_RESPONSEJOINGAME_ERROR']._serialized_start=3868
+ _globals['_RESPONSEJOINGAME_ERROR']._serialized_end=4136
+ _globals['_REQUESTRESTARTGAME']._serialized_start=4138
+ _globals['_REQUESTRESTARTGAME']._serialized_end=4158
+ _globals['_RESPONSERESTARTGAME']._serialized_start=4161
+ _globals['_RESPONSERESTARTGAME']._serialized_end=4314
+ _globals['_RESPONSERESTARTGAME_ERROR']._serialized_start=4290
+ _globals['_RESPONSERESTARTGAME_ERROR']._serialized_end=4314
+ _globals['_REQUESTSTARTREPLAY']._serialized_start=4317
+ _globals['_REQUESTSTARTREPLAY']._serialized_end=4552
+ _globals['_RESPONSESTARTREPLAY']._serialized_start=4555
+ _globals['_RESPONSESTARTREPLAY']._serialized_end=4818
+ _globals['_RESPONSESTARTREPLAY_ERROR']._serialized_start=4660
+ _globals['_RESPONSESTARTREPLAY_ERROR']._serialized_end=4818
+ _globals['_REQUESTMAPCOMMAND']._serialized_start=4820
+ _globals['_REQUESTMAPCOMMAND']._serialized_end=4860
+ _globals['_RESPONSEMAPCOMMAND']._serialized_start=4863
+ _globals['_RESPONSEMAPCOMMAND']._serialized_end=4992
+ _globals['_RESPONSEMAPCOMMAND_ERROR']._serialized_start=4965
+ _globals['_RESPONSEMAPCOMMAND_ERROR']._serialized_end=4992
+ _globals['_REQUESTLEAVEGAME']._serialized_start=4994
+ _globals['_REQUESTLEAVEGAME']._serialized_end=5012
+ _globals['_RESPONSELEAVEGAME']._serialized_start=5014
+ _globals['_RESPONSELEAVEGAME']._serialized_end=5033
+ _globals['_REQUESTQUICKSAVE']._serialized_start=5035
+ _globals['_REQUESTQUICKSAVE']._serialized_end=5053
+ _globals['_RESPONSEQUICKSAVE']._serialized_start=5055
+ _globals['_RESPONSEQUICKSAVE']._serialized_end=5074
+ _globals['_REQUESTQUICKLOAD']._serialized_start=5076
+ _globals['_REQUESTQUICKLOAD']._serialized_end=5094
+ _globals['_RESPONSEQUICKLOAD']._serialized_start=5096
+ _globals['_RESPONSEQUICKLOAD']._serialized_end=5115
+ _globals['_REQUESTQUIT']._serialized_start=5117
+ _globals['_REQUESTQUIT']._serialized_end=5130
+ _globals['_RESPONSEQUIT']._serialized_start=5132
+ _globals['_RESPONSEQUIT']._serialized_end=5146
+ _globals['_REQUESTGAMEINFO']._serialized_start=5148
+ _globals['_REQUESTGAMEINFO']._serialized_end=5165
+ _globals['_RESPONSEGAMEINFO']._serialized_start=5168
+ _globals['_RESPONSEGAMEINFO']._serialized_end=5392
+ _globals['_REQUESTOBSERVATION']._serialized_start=5394
+ _globals['_REQUESTOBSERVATION']._serialized_end=5454
+ _globals['_RESPONSEOBSERVATION']._serialized_start=5457
+ _globals['_RESPONSEOBSERVATION']._serialized_end=5718
+ _globals['_CHATRECEIVED']._serialized_start=5720
+ _globals['_CHATRECEIVED']._serialized_end=5770
+ _globals['_REQUESTACTION']._serialized_start=5772
+ _globals['_REQUESTACTION']._serialized_end=5828
+ _globals['_RESPONSEACTION']._serialized_start=5830
+ _globals['_RESPONSEACTION']._serialized_end=5892
+ _globals['_REQUESTOBSERVERACTION']._serialized_start=5894
+ _globals['_REQUESTOBSERVERACTION']._serialized_end=5966
+ _globals['_RESPONSEOBSERVERACTION']._serialized_start=5968
+ _globals['_RESPONSEOBSERVERACTION']._serialized_end=5992
+ _globals['_REQUESTSTEP']._serialized_start=5994
+ _globals['_REQUESTSTEP']._serialized_end=6022
+ _globals['_RESPONSESTEP']._serialized_start=6024
+ _globals['_RESPONSESTEP']._serialized_end=6063
+ _globals['_REQUESTDATA']._serialized_start=6065
+ _globals['_REQUESTDATA']._serialized_end=6176
+ _globals['_RESPONSEDATA']._serialized_start=6179
+ _globals['_RESPONSEDATA']._serialized_end=6419
+ _globals['_REQUESTSAVEREPLAY']._serialized_start=6421
+ _globals['_REQUESTSAVEREPLAY']._serialized_end=6440
+ _globals['_RESPONSESAVEREPLAY']._serialized_start=6442
+ _globals['_RESPONSESAVEREPLAY']._serialized_end=6476
+ _globals['_REQUESTREPLAYINFO']._serialized_start=6478
+ _globals['_REQUESTREPLAYINFO']._serialized_end=6576
+ _globals['_PLAYERINFOEXTRA']._serialized_start=6579
+ _globals['_PLAYERINFOEXTRA']._serialized_end=6738
+ _globals['_RESPONSEREPLAYINFO']._serialized_start=6741
+ _globals['_RESPONSEREPLAYINFO']._serialized_end=7192
+ _globals['_RESPONSEREPLAYINFO_ERROR']._serialized_start=7083
+ _globals['_RESPONSEREPLAYINFO_ERROR']._serialized_end=7192
+ _globals['_REQUESTAVAILABLEMAPS']._serialized_start=7194
+ _globals['_REQUESTAVAILABLEMAPS']._serialized_end=7216
+ _globals['_RESPONSEAVAILABLEMAPS']._serialized_start=7218
+ _globals['_RESPONSEAVAILABLEMAPS']._serialized_end=7295
+ _globals['_REQUESTSAVEMAP']._serialized_start=7297
+ _globals['_REQUESTSAVEMAP']._serialized_end=7349
+ _globals['_RESPONSESAVEMAP']._serialized_start=7351
+ _globals['_RESPONSESAVEMAP']._serialized_end=7451
+ _globals['_RESPONSESAVEMAP_ERROR']._serialized_start=7424
+ _globals['_RESPONSESAVEMAP_ERROR']._serialized_end=7451
+ _globals['_REQUESTPING']._serialized_start=7453
+ _globals['_REQUESTPING']._serialized_end=7466
+ _globals['_RESPONSEPING']._serialized_start=7468
+ _globals['_RESPONSEPING']._serialized_end=7566
+ _globals['_REQUESTDEBUG']._serialized_start=7568
+ _globals['_REQUESTDEBUG']._serialized_end=7627
+ _globals['_RESPONSEDEBUG']._serialized_start=7629
+ _globals['_RESPONSEDEBUG']._serialized_end=7644
+ _globals['_PLAYERSETUP']._serialized_start=7647
+ _globals['_PLAYERSETUP']._serialized_end=7850
+ _globals['_SPATIALCAMERASETUP']._serialized_start=7853
+ _globals['_SPATIALCAMERASETUP']._serialized_end=8048
+ _globals['_INTERFACEOPTIONS']._serialized_start=8051
+ _globals['_INTERFACEOPTIONS']._serialized_end=8354
+ _globals['_PLAYERINFO']._serialized_start=8357
+ _globals['_PLAYERINFO']._serialized_end=8631
+ _globals['_PLAYERCOMMON']._serialized_start=8634
+ _globals['_PLAYERCOMMON']._serialized_end=8873
+ _globals['_OBSERVATION']._serialized_start=8876
+ _globals['_OBSERVATION']._serialized_end=9314
+ _globals['_ACTION']._serialized_start=9317
+ _globals['_ACTION']._serialized_end=9600
+ _globals['_ACTIONCHAT']._serialized_start=9602
+ _globals['_ACTIONCHAT']._serialized_end=9720
+ _globals['_ACTIONCHAT_CHANNEL']._serialized_start=9686
+ _globals['_ACTIONCHAT_CHANNEL']._serialized_end=9720
+ _globals['_ACTIONERROR']._serialized_start=9722
+ _globals['_ACTIONERROR']._serialized_end=9819
+ _globals['_OBSERVERACTION']._serialized_start=9822
+ _globals['_OBSERVERACTION']._serialized_end=10154
+ _globals['_ACTIONOBSERVERPLAYERPERSPECTIVE']._serialized_start=10156
+ _globals['_ACTIONOBSERVERPLAYERPERSPECTIVE']._serialized_end=10208
+ _globals['_ACTIONOBSERVERCAMERAMOVE']._serialized_start=10210
+ _globals['_ACTIONOBSERVERCAMERAMOVE']._serialized_end=10298
+ _globals['_ACTIONOBSERVERCAMERAFOLLOWPLAYER']._serialized_start=10300
+ _globals['_ACTIONOBSERVERCAMERAFOLLOWPLAYER']._serialized_end=10353
+ _globals['_ACTIONOBSERVERCAMERAFOLLOWUNITS']._serialized_start=10355
+ _globals['_ACTIONOBSERVERCAMERAFOLLOWUNITS']._serialized_end=10407
+ _globals['_PLAYERRESULT']._serialized_start=10409
+ _globals['_PLAYERRESULT']._serialized_end=10482
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/score_pb2.py b/worlds/_sc2common/bot/proto/score_pb2.py
new file mode 100644
index 000000000000..904551647549
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/score_pb2.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/score.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/score.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/score.proto\x12\x0eSC2APIProtocol\"\xa8\x01\n\x05Score\x12\x33\n\nscore_type\x18\x06 \x01(\x0e\x32\x1f.SC2APIProtocol.Score.ScoreType\x12\r\n\x05score\x18\x07 \x01(\x05\x12\x33\n\rscore_details\x18\x08 \x01(\x0b\x32\x1c.SC2APIProtocol.ScoreDetails\"&\n\tScoreType\x12\x0e\n\nCurriculum\x10\x01\x12\t\n\x05Melee\x10\x02\"h\n\x14\x43\x61tegoryScoreDetails\x12\x0c\n\x04none\x18\x01 \x01(\x02\x12\x0c\n\x04\x61rmy\x18\x02 \x01(\x02\x12\x0f\n\x07\x65\x63onomy\x18\x03 \x01(\x02\x12\x12\n\ntechnology\x18\x04 \x01(\x02\x12\x0f\n\x07upgrade\x18\x05 \x01(\x02\"B\n\x11VitalScoreDetails\x12\x0c\n\x04life\x18\x01 \x01(\x02\x12\x0f\n\x07shields\x18\x02 \x01(\x02\x12\x0e\n\x06\x65nergy\x18\x03 \x01(\x02\"\x8a\n\n\x0cScoreDetails\x12\x1c\n\x14idle_production_time\x18\x01 \x01(\x02\x12\x18\n\x10idle_worker_time\x18\x02 \x01(\x02\x12\x19\n\x11total_value_units\x18\x03 \x01(\x02\x12\x1e\n\x16total_value_structures\x18\x04 \x01(\x02\x12\x1a\n\x12killed_value_units\x18\x05 \x01(\x02\x12\x1f\n\x17killed_value_structures\x18\x06 \x01(\x02\x12\x1a\n\x12\x63ollected_minerals\x18\x07 \x01(\x02\x12\x19\n\x11\x63ollected_vespene\x18\x08 \x01(\x02\x12 \n\x18\x63ollection_rate_minerals\x18\t \x01(\x02\x12\x1f\n\x17\x63ollection_rate_vespene\x18\n \x01(\x02\x12\x16\n\x0espent_minerals\x18\x0b \x01(\x02\x12\x15\n\rspent_vespene\x18\x0c \x01(\x02\x12\x37\n\tfood_used\x18\r \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12=\n\x0fkilled_minerals\x18\x0e \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12<\n\x0ekilled_vespene\x18\x0f \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12;\n\rlost_minerals\x18\x10 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12:\n\x0clost_vespene\x18\x11 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12\x44\n\x16\x66riendly_fire_minerals\x18\x12 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12\x43\n\x15\x66riendly_fire_vespene\x18\x13 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12;\n\rused_minerals\x18\x14 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12:\n\x0cused_vespene\x18\x15 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12\x41\n\x13total_used_minerals\x18\x16 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12@\n\x12total_used_vespene\x18\x17 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12=\n\x12total_damage_dealt\x18\x18 \x01(\x0b\x32!.SC2APIProtocol.VitalScoreDetails\x12=\n\x12total_damage_taken\x18\x19 \x01(\x0b\x32!.SC2APIProtocol.VitalScoreDetails\x12\x37\n\x0ctotal_healed\x18\x1a \x01(\x0b\x32!.SC2APIProtocol.VitalScoreDetails\x12\x13\n\x0b\x63urrent_apm\x18\x1b \x01(\x02\x12\x1d\n\x15\x63urrent_effective_apm\x18\x1c \x01(\x02')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.score_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_SCORE']._serialized_start=49
+ _globals['_SCORE']._serialized_end=217
+ _globals['_SCORE_SCORETYPE']._serialized_start=179
+ _globals['_SCORE_SCORETYPE']._serialized_end=217
+ _globals['_CATEGORYSCOREDETAILS']._serialized_start=219
+ _globals['_CATEGORYSCOREDETAILS']._serialized_end=323
+ _globals['_VITALSCOREDETAILS']._serialized_start=325
+ _globals['_VITALSCOREDETAILS']._serialized_end=391
+ _globals['_SCOREDETAILS']._serialized_start=394
+ _globals['_SCOREDETAILS']._serialized_end=1684
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/spatial_pb2.py b/worlds/_sc2common/bot/proto/spatial_pb2.py
new file mode 100644
index 000000000000..e570ab210fb4
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/spatial_pb2.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/spatial.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/spatial.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import common_pb2 as s2clientprotocol_dot_common__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1es2clientprotocol/spatial.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\x88\x01\n\x17ObservationFeatureLayer\x12.\n\x07renders\x18\x01 \x01(\x0b\x32\x1d.SC2APIProtocol.FeatureLayers\x12=\n\x0fminimap_renders\x18\x02 \x01(\x0b\x32$.SC2APIProtocol.FeatureLayersMinimap\"\x9c\n\n\rFeatureLayers\x12-\n\nheight_map\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0evisibility_map\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x63reep\x18\x03 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05power\x18\x04 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tplayer_id\x18\x05 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tunit_type\x18\x06 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08selected\x18\x07 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0funit_hit_points\x18\x08 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x38\n\x15unit_hit_points_ratio\x18\x11 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12.\n\x0bunit_energy\x18\t \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x34\n\x11unit_energy_ratio\x18\x12 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12/\n\x0cunit_shields\x18\n \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x35\n\x12unit_shields_ratio\x18\x13 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0fplayer_relative\x18\x0b \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0funit_density_aa\x18\x0e \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12/\n\x0cunit_density\x18\x0f \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12*\n\x07\x65\x66\x66\x65\x63ts\x18\x14 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0ehallucinations\x18\x15 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12*\n\x07\x63loaked\x18\x16 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\'\n\x04\x62lip\x18\x17 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x62uffs\x18\x18 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x30\n\rbuff_duration\x18\x1a \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12)\n\x06\x61\x63tive\x18\x19 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0e\x62uild_progress\x18\x1b \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tbuildable\x18\x1c \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08pathable\x18\x1d \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12.\n\x0bplaceholder\x18\x1e \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"\x90\x04\n\x14\x46\x65\x61tureLayersMinimap\x12-\n\nheight_map\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0evisibility_map\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x63reep\x18\x03 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12)\n\x06\x63\x61mera\x18\x04 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tplayer_id\x18\x05 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0fplayer_relative\x18\x06 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08selected\x18\x07 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12)\n\x06\x61lerts\x18\t \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tbuildable\x18\n \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08pathable\x18\x0b \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tunit_type\x18\x08 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"g\n\x11ObservationRender\x12&\n\x03map\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12*\n\x07minimap\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"\xbb\x02\n\rActionSpatial\x12@\n\x0cunit_command\x18\x01 \x01(\x0b\x32(.SC2APIProtocol.ActionSpatialUnitCommandH\x00\x12>\n\x0b\x63\x61mera_move\x18\x02 \x01(\x0b\x32\'.SC2APIProtocol.ActionSpatialCameraMoveH\x00\x12O\n\x14unit_selection_point\x18\x03 \x01(\x0b\x32/.SC2APIProtocol.ActionSpatialUnitSelectionPointH\x00\x12M\n\x13unit_selection_rect\x18\x04 \x01(\x0b\x32..SC2APIProtocol.ActionSpatialUnitSelectionRectH\x00\x42\x08\n\x06\x61\x63tion\"\xbe\x01\n\x18\x41\x63tionSpatialUnitCommand\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12\x35\n\x13target_screen_coord\x18\x02 \x01(\x0b\x32\x16.SC2APIProtocol.PointIH\x00\x12\x36\n\x14target_minimap_coord\x18\x03 \x01(\x0b\x32\x16.SC2APIProtocol.PointIH\x00\x12\x15\n\rqueue_command\x18\x04 \x01(\x08\x42\x08\n\x06target\"I\n\x17\x41\x63tionSpatialCameraMove\x12.\n\x0e\x63\x65nter_minimap\x18\x01 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\"\xda\x01\n\x1f\x41\x63tionSpatialUnitSelectionPoint\x12\x36\n\x16selection_screen_coord\x18\x01 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\x12\x42\n\x04type\x18\x02 \x01(\x0e\x32\x34.SC2APIProtocol.ActionSpatialUnitSelectionPoint.Type\";\n\x04Type\x12\n\n\x06Select\x10\x01\x12\n\n\x06Toggle\x10\x02\x12\x0b\n\x07\x41llType\x10\x03\x12\x0e\n\nAddAllType\x10\x04\"s\n\x1e\x41\x63tionSpatialUnitSelectionRect\x12:\n\x16selection_screen_coord\x18\x01 \x03(\x0b\x32\x1a.SC2APIProtocol.RectangleI\x12\x15\n\rselection_add\x18\x02 \x01(\x08')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.spatial_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_OBSERVATIONFEATURELAYER']._serialized_start=82
+ _globals['_OBSERVATIONFEATURELAYER']._serialized_end=218
+ _globals['_FEATURELAYERS']._serialized_start=221
+ _globals['_FEATURELAYERS']._serialized_end=1529
+ _globals['_FEATURELAYERSMINIMAP']._serialized_start=1532
+ _globals['_FEATURELAYERSMINIMAP']._serialized_end=2060
+ _globals['_OBSERVATIONRENDER']._serialized_start=2062
+ _globals['_OBSERVATIONRENDER']._serialized_end=2165
+ _globals['_ACTIONSPATIAL']._serialized_start=2168
+ _globals['_ACTIONSPATIAL']._serialized_end=2483
+ _globals['_ACTIONSPATIALUNITCOMMAND']._serialized_start=2486
+ _globals['_ACTIONSPATIALUNITCOMMAND']._serialized_end=2676
+ _globals['_ACTIONSPATIALCAMERAMOVE']._serialized_start=2678
+ _globals['_ACTIONSPATIALCAMERAMOVE']._serialized_end=2751
+ _globals['_ACTIONSPATIALUNITSELECTIONPOINT']._serialized_start=2754
+ _globals['_ACTIONSPATIALUNITSELECTIONPOINT']._serialized_end=2972
+ _globals['_ACTIONSPATIALUNITSELECTIONPOINT_TYPE']._serialized_start=2913
+ _globals['_ACTIONSPATIALUNITSELECTIONPOINT_TYPE']._serialized_end=2972
+ _globals['_ACTIONSPATIALUNITSELECTIONRECT']._serialized_start=2974
+ _globals['_ACTIONSPATIALUNITSELECTIONRECT']._serialized_end=3089
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/proto/ui_pb2.py b/worlds/_sc2common/bot/proto/ui_pb2.py
new file mode 100644
index 000000000000..c905b77db12f
--- /dev/null
+++ b/worlds/_sc2common/bot/proto/ui_pb2.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: s2clientprotocol/ui.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 's2clientprotocol/ui.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19s2clientprotocol/ui.proto\x12\x0eSC2APIProtocol\"\x86\x02\n\rObservationUI\x12,\n\x06groups\x18\x01 \x03(\x0b\x32\x1c.SC2APIProtocol.ControlGroup\x12-\n\x06single\x18\x02 \x01(\x0b\x32\x1b.SC2APIProtocol.SinglePanelH\x00\x12+\n\x05multi\x18\x03 \x01(\x0b\x32\x1a.SC2APIProtocol.MultiPanelH\x00\x12+\n\x05\x63\x61rgo\x18\x04 \x01(\x0b\x32\x1a.SC2APIProtocol.CargoPanelH\x00\x12\x35\n\nproduction\x18\x05 \x01(\x0b\x32\x1f.SC2APIProtocol.ProductionPanelH\x00\x42\x07\n\x05panel\"T\n\x0c\x43ontrolGroup\x12\x1b\n\x13\x63ontrol_group_index\x18\x01 \x01(\r\x12\x18\n\x10leader_unit_type\x18\x02 \x01(\r\x12\r\n\x05\x63ount\x18\x03 \x01(\r\"\x85\x02\n\x08UnitInfo\x12\x11\n\tunit_type\x18\x01 \x01(\r\x12\x17\n\x0fplayer_relative\x18\x02 \x01(\r\x12\x0e\n\x06health\x18\x03 \x01(\x05\x12\x0f\n\x07shields\x18\x04 \x01(\x05\x12\x0e\n\x06\x65nergy\x18\x05 \x01(\x05\x12\x1d\n\x15transport_slots_taken\x18\x06 \x01(\x05\x12\x16\n\x0e\x62uild_progress\x18\x07 \x01(\x02\x12(\n\x06\x61\x64\x64_on\x18\x08 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x12\n\nmax_health\x18\t \x01(\x05\x12\x13\n\x0bmax_shields\x18\n \x01(\x05\x12\x12\n\nmax_energy\x18\x0b \x01(\x05\"\x9d\x01\n\x0bSinglePanel\x12&\n\x04unit\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x1c\n\x14\x61ttack_upgrade_level\x18\x02 \x01(\x05\x12\x1b\n\x13\x61rmor_upgrade_level\x18\x03 \x01(\x05\x12\x1c\n\x14shield_upgrade_level\x18\x04 \x01(\x05\x12\r\n\x05\x62uffs\x18\x05 \x03(\x05\"5\n\nMultiPanel\x12\'\n\x05units\x18\x01 \x03(\x0b\x32\x18.SC2APIProtocol.UnitInfo\"{\n\nCargoPanel\x12&\n\x04unit\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12,\n\npassengers\x18\x02 \x03(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x17\n\x0fslots_available\x18\x03 \x01(\x05\"7\n\tBuildItem\x12\x12\n\nability_id\x18\x01 \x01(\r\x12\x16\n\x0e\x62uild_progress\x18\x02 \x01(\x02\"\x9d\x01\n\x0fProductionPanel\x12&\n\x04unit\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12-\n\x0b\x62uild_queue\x18\x02 \x03(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x33\n\x10production_queue\x18\x03 \x03(\x0b\x32\x19.SC2APIProtocol.BuildItem\"\xda\x04\n\x08\x41\x63tionUI\x12;\n\rcontrol_group\x18\x01 \x01(\x0b\x32\".SC2APIProtocol.ActionControlGroupH\x00\x12\x37\n\x0bselect_army\x18\x02 \x01(\x0b\x32 .SC2APIProtocol.ActionSelectArmyH\x00\x12\x42\n\x11select_warp_gates\x18\x03 \x01(\x0b\x32%.SC2APIProtocol.ActionSelectWarpGatesH\x00\x12\x39\n\x0cselect_larva\x18\x04 \x01(\x0b\x32!.SC2APIProtocol.ActionSelectLarvaH\x00\x12\x44\n\x12select_idle_worker\x18\x05 \x01(\x0b\x32&.SC2APIProtocol.ActionSelectIdleWorkerH\x00\x12\x37\n\x0bmulti_panel\x18\x06 \x01(\x0b\x32 .SC2APIProtocol.ActionMultiPanelH\x00\x12=\n\x0b\x63\x61rgo_panel\x18\x07 \x01(\x0b\x32&.SC2APIProtocol.ActionCargoPanelUnloadH\x00\x12P\n\x10production_panel\x18\x08 \x01(\x0b\x32\x34.SC2APIProtocol.ActionProductionPanelRemoveFromQueueH\x00\x12?\n\x0ftoggle_autocast\x18\t \x01(\x0b\x32$.SC2APIProtocol.ActionToggleAutocastH\x00\x42\x08\n\x06\x61\x63tion\"\xd4\x01\n\x12\x41\x63tionControlGroup\x12\x45\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x35.SC2APIProtocol.ActionControlGroup.ControlGroupAction\x12\x1b\n\x13\x63ontrol_group_index\x18\x02 \x01(\r\"Z\n\x12\x43ontrolGroupAction\x12\n\n\x06Recall\x10\x01\x12\x07\n\x03Set\x10\x02\x12\n\n\x06\x41ppend\x10\x03\x12\x0f\n\x0bSetAndSteal\x10\x04\x12\x12\n\x0e\x41ppendAndSteal\x10\x05\")\n\x10\x41\x63tionSelectArmy\x12\x15\n\rselection_add\x18\x01 \x01(\x08\".\n\x15\x41\x63tionSelectWarpGates\x12\x15\n\rselection_add\x18\x01 \x01(\x08\"\x13\n\x11\x41\x63tionSelectLarva\"\x82\x01\n\x16\x41\x63tionSelectIdleWorker\x12\x39\n\x04type\x18\x01 \x01(\x0e\x32+.SC2APIProtocol.ActionSelectIdleWorker.Type\"-\n\x04Type\x12\x07\n\x03Set\x10\x01\x12\x07\n\x03\x41\x64\x64\x10\x02\x12\x07\n\x03\x41ll\x10\x03\x12\n\n\x06\x41\x64\x64\x41ll\x10\x04\"\xb3\x01\n\x10\x41\x63tionMultiPanel\x12\x33\n\x04type\x18\x01 \x01(\x0e\x32%.SC2APIProtocol.ActionMultiPanel.Type\x12\x12\n\nunit_index\x18\x02 \x01(\x05\"V\n\x04Type\x12\x10\n\x0cSingleSelect\x10\x01\x12\x10\n\x0c\x44\x65selectUnit\x10\x02\x12\x13\n\x0fSelectAllOfType\x10\x03\x12\x15\n\x11\x44\x65selectAllOfType\x10\x04\",\n\x16\x41\x63tionCargoPanelUnload\x12\x12\n\nunit_index\x18\x01 \x01(\x05\":\n$ActionProductionPanelRemoveFromQueue\x12\x12\n\nunit_index\x18\x01 \x01(\x05\"*\n\x14\x41\x63tionToggleAutocast\x12\x12\n\nability_id\x18\x01 \x01(\x05')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.ui_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ DESCRIPTOR._loaded_options = None
+ _globals['_OBSERVATIONUI']._serialized_start=46
+ _globals['_OBSERVATIONUI']._serialized_end=308
+ _globals['_CONTROLGROUP']._serialized_start=310
+ _globals['_CONTROLGROUP']._serialized_end=394
+ _globals['_UNITINFO']._serialized_start=397
+ _globals['_UNITINFO']._serialized_end=658
+ _globals['_SINGLEPANEL']._serialized_start=661
+ _globals['_SINGLEPANEL']._serialized_end=818
+ _globals['_MULTIPANEL']._serialized_start=820
+ _globals['_MULTIPANEL']._serialized_end=873
+ _globals['_CARGOPANEL']._serialized_start=875
+ _globals['_CARGOPANEL']._serialized_end=998
+ _globals['_BUILDITEM']._serialized_start=1000
+ _globals['_BUILDITEM']._serialized_end=1055
+ _globals['_PRODUCTIONPANEL']._serialized_start=1058
+ _globals['_PRODUCTIONPANEL']._serialized_end=1215
+ _globals['_ACTIONUI']._serialized_start=1218
+ _globals['_ACTIONUI']._serialized_end=1820
+ _globals['_ACTIONCONTROLGROUP']._serialized_start=1823
+ _globals['_ACTIONCONTROLGROUP']._serialized_end=2035
+ _globals['_ACTIONCONTROLGROUP_CONTROLGROUPACTION']._serialized_start=1945
+ _globals['_ACTIONCONTROLGROUP_CONTROLGROUPACTION']._serialized_end=2035
+ _globals['_ACTIONSELECTARMY']._serialized_start=2037
+ _globals['_ACTIONSELECTARMY']._serialized_end=2078
+ _globals['_ACTIONSELECTWARPGATES']._serialized_start=2080
+ _globals['_ACTIONSELECTWARPGATES']._serialized_end=2126
+ _globals['_ACTIONSELECTLARVA']._serialized_start=2128
+ _globals['_ACTIONSELECTLARVA']._serialized_end=2147
+ _globals['_ACTIONSELECTIDLEWORKER']._serialized_start=2150
+ _globals['_ACTIONSELECTIDLEWORKER']._serialized_end=2280
+ _globals['_ACTIONSELECTIDLEWORKER_TYPE']._serialized_start=2235
+ _globals['_ACTIONSELECTIDLEWORKER_TYPE']._serialized_end=2280
+ _globals['_ACTIONMULTIPANEL']._serialized_start=2283
+ _globals['_ACTIONMULTIPANEL']._serialized_end=2462
+ _globals['_ACTIONMULTIPANEL_TYPE']._serialized_start=2376
+ _globals['_ACTIONMULTIPANEL_TYPE']._serialized_end=2462
+ _globals['_ACTIONCARGOPANELUNLOAD']._serialized_start=2464
+ _globals['_ACTIONCARGOPANELUNLOAD']._serialized_end=2508
+ _globals['_ACTIONPRODUCTIONPANELREMOVEFROMQUEUE']._serialized_start=2510
+ _globals['_ACTIONPRODUCTIONPANELREMOVEFROMQUEUE']._serialized_end=2568
+ _globals['_ACTIONTOGGLEAUTOCAST']._serialized_start=2570
+ _globals['_ACTIONTOGGLEAUTOCAST']._serialized_end=2612
+# @@protoc_insertion_point(module_scope)
diff --git a/worlds/_sc2common/bot/protocol.py b/worlds/_sc2common/bot/protocol.py
index d2c48facb57d..46a7d59adcc1 100644
--- a/worlds/_sc2common/bot/protocol.py
+++ b/worlds/_sc2common/bot/protocol.py
@@ -4,7 +4,7 @@
from aiohttp import ClientWebSocketResponse
from worlds._sc2common.bot import logger
-from s2clientprotocol import sc2api_pb2 as sc_pb
+from .proto import sc2api_pb2 as sc_pb
from .data import Status
diff --git a/worlds/_sc2common/bot/proxy.py b/worlds/_sc2common/bot/proxy.py
index fa9f8537af56..0e389fbe1729 100644
--- a/worlds/_sc2common/bot/proxy.py
+++ b/worlds/_sc2common/bot/proxy.py
@@ -8,7 +8,7 @@
from aiohttp import WSMsgType, web
from worlds._sc2common.bot import logger
-from s2clientprotocol import sc2api_pb2 as sc_pb
+from .proto import sc2api_pb2 as sc_pb
from .controller import Controller
from .data import Result, Status
diff --git a/worlds/_sc2common/bot/renderer.py b/worlds/_sc2common/bot/renderer.py
index 2edc75d8441c..2211fcb10cf2 100644
--- a/worlds/_sc2common/bot/renderer.py
+++ b/worlds/_sc2common/bot/renderer.py
@@ -1,6 +1,6 @@
import datetime
-from s2clientprotocol import score_pb2 as score_pb
+from .proto import score_pb2 as score_pb
from .position import Point2
diff --git a/worlds/_sc2common/requirements.txt b/worlds/_sc2common/requirements.txt
index 1ec405a4ab52..e8499aa7dbf2 100644
--- a/worlds/_sc2common/requirements.txt
+++ b/worlds/_sc2common/requirements.txt
@@ -1,6 +1,5 @@
-s2clientprotocol>=5.0.11.90136.0
mpyq>=0.2.5
portpicker>=1.5.2
aiohttp>=3.8.4
loguru>=0.7.0
-protobuf==3.20.3
+protobuf==6.31.1
diff --git a/worlds/adventure/Rom.py b/worlds/adventure/Rom.py
index cb104c56d867..e603ed06bc0a 100644
--- a/worlds/adventure/Rom.py
+++ b/worlds/adventure/Rom.py
@@ -8,7 +8,7 @@
import Utils
from settings import get_settings
-from worlds.Files import APPatch, AutoPatchRegister
+from worlds.Files import APPatch
from .Locations import LocationData
ADVENTUREHASH: str = "157bddb7192754a45372be196797f284"
@@ -78,7 +78,7 @@ def get_dict(self):
return ret_dict
-class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister):
+class AdventureDeltaPatch(APPatch):
hash = ADVENTUREHASH
game = "Adventure"
patch_file_ending = ".apadvn"
diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py
index 6b7da695934f..04d107f1ff1f 100644
--- a/worlds/alttp/Dungeons.py
+++ b/worlds/alttp/Dungeons.py
@@ -239,7 +239,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
multiworld.worlds[item.player].collect(all_state_base, item)
pre_fill_items = []
for player in in_dungeon_player_ids:
- pre_fill_items += multiworld.worlds[player].get_pre_fill_items()
+ pre_fill_items += [item for item in multiworld.worlds[player].get_pre_fill_items() if not item.crystal]
for item in in_dungeon_items:
try:
pre_fill_items.remove(item)
diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py
index 53059c64bc00..6b0968f6e598 100644
--- a/worlds/alttp/ItemPool.py
+++ b/worlds/alttp/ItemPool.py
@@ -2,7 +2,7 @@
import logging
from BaseClasses import ItemClassification
-from Fill import FillError
+from Options import OptionError
from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType
from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType
@@ -263,7 +263,6 @@ def generate_itempool(world):
('Frog', 'Get Frog'),
('Missing Smith', 'Return Smith'),
('Floodgate', 'Open Floodgate'),
- ('Agahnim 1', 'Beat Agahnim 1'),
('Flute Activation Spot', 'Activated Flute'),
('Capacity Upgrade Shop', 'Capacity Upgrade Shop')
]
@@ -410,15 +409,16 @@ def generate_itempool(world):
pool_count = len(items)
new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)]
if world.options.shuffle_capacity_upgrades or world.options.bombless_start:
- progressive = world.options.progressive
- progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on'
+ progressive = world.options.progressive.want_progressives(world.random)
if world.options.shuffle_capacity_upgrades == "on_combined":
new_items.append("Bomb Upgrade (50)")
elif world.options.shuffle_capacity_upgrades == "on":
new_items += ["Bomb Upgrade (+5)"] * 6
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
- if world.options.shuffle_capacity_upgrades != "on_combined" and world.options.bombless_start:
- new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
+ if world.options.bombless_start:
+ new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
+ elif world.options.bombless_start:
+ new_items.append("Bomb Upgrade (+10)")
if world.options.shuffle_capacity_upgrades and not world.options.retro_bow:
if world.options.shuffle_capacity_upgrades == "on_combined":
@@ -466,6 +466,9 @@ def cut_item(items, item_to_cut, minimum_items):
items_were_cut = items_were_cut or cut_item(items, *reduce_item)
elif len(reduce_item) == 4:
items_were_cut = items_were_cut or condense_items(items, *reduce_item)
+ if reduce_item[0] == "Piece of Heart" and world.logical_heart_pieces:
+ world.logical_heart_pieces -= reduce_item[2]
+ world.logical_heart_containers += reduce_item[3]
elif len(reduce_item) == 1: # Bottles
bottles = [item for item in items if item.name in item_name_groups["Bottles"]]
if len(bottles) > 4:
@@ -476,7 +479,7 @@ def cut_item(items, item_to_cut, minimum_items):
if items_were_cut:
break
else:
- raise Exception(f"Failed to limit item pool size for player {player}")
+ raise OptionError(f"Failed to limit item pool size for player {player}")
if len(items) < pool_count:
items += removed_filler[len(items) - pool_count:]
diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py
index 6a5792d21a3f..2f62b3768238 100644
--- a/worlds/alttp/Rom.py
+++ b/worlds/alttp/Rom.py
@@ -183,7 +183,7 @@ def check_enemizer(enemizercli):
if getattr(check_enemizer, "done", None):
return
if not os.path.exists(enemizercli) and not os.path.exists(enemizercli + ".exe"):
- raise Exception(f"Enemizer not found at {enemizercli}, please install it."
+ raise Exception(f"Enemizer not found at {enemizercli}, please install it. "
f"Such as https://github.com/Ijwu/Enemizer/releases")
with check_lock:
@@ -1197,8 +1197,8 @@ def chunk(l, n):
0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade
0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade
0x58, 0x01, 0x36 if local_world.options.retro_bow else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode)
- 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20
- 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20
+ 0x3E, local_world.logical_heart_containers, 0x47, 0xff, # boss heart -> green 20
+ 0x17, local_world.logical_heart_pieces, 0x47, 0xff, # piece of heart -> green 20
0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel
])
diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py
index a5b14e0c2da6..18e2965d8c5a 100644
--- a/worlds/alttp/Rules.py
+++ b/worlds/alttp/Rules.py
@@ -20,7 +20,7 @@
has_fire_source, has_hearts, has_melee_weapon,
has_misery_mire_medallion, has_sword, has_turtle_rock_medallion,
has_triforce_pieces, can_use_bombs, can_bomb_or_bonk,
- can_activate_crystal_switch)
+ can_activate_crystal_switch, can_kill_standard_start)
from .UnderworldGlitchRules import underworld_glitches_rules
@@ -1093,22 +1093,23 @@ def standard_rules(world, player):
if world.worlds[player].options.small_key_shuffle != small_key_shuffle.option_universal:
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
- and can_kill_most_things(state, player, 2))
+ and can_kill_standard_start(state, player, 2))
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
- and can_kill_most_things(state, player, 1))
-
+ and can_kill_standard_start(state, player, 1))
+ set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player),
+ lambda state: can_kill_standard_start(state, player, 1))
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2)
and state.has('Big Key (Hyrule Castle)', player)
and (world.worlds[player].options.enemy_health in ("easy", "default")
- or can_kill_most_things(state, player, 1)))
+ or can_kill_standard_start(state, player, 1)))
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3)
- and can_kill_most_things(state, player, 1))
+ and can_kill_standard_start(state, player, 1))
else:
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state.has('Big Key (Hyrule Castle)', player))
diff --git a/worlds/alttp/StateHelpers.py b/worlds/alttp/StateHelpers.py
index 98409c8a8d3c..2c1b5e2ab848 100644
--- a/worlds/alttp/StateHelpers.py
+++ b/worlds/alttp/StateHelpers.py
@@ -59,10 +59,11 @@ def has_hearts(state: CollectionState, player: int, count: int) -> int:
def heart_count(state: CollectionState, player: int) -> int:
# Warning: This only considers items that are marked as advancement items
- diff = state.multiworld.worlds[player].difficulty_requirements
- return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \
+ max_heart_pieces = state.multiworld.worlds[player].logical_heart_pieces
+ max_heart_containers = state.multiworld.worlds[player].logical_heart_containers
+ return min(state.count('Boss Heart Container', player), max_heart_containers) \
+ state.count('Sanctuary Heart Container', player) \
- + min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \
+ + min(state.count('Piece of Heart', player), max_heart_pieces) // 4 \
+ 3 # starting hearts
@@ -139,6 +140,16 @@ def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5)
and can_use_bombs(state, player, enemies * 4)))
+def can_kill_standard_start(state: CollectionState, player: int, enemies: int = 5) -> bool:
+ # Enemizer does not randomize standard start enemies
+ return (has_melee_weapon(state, player)
+ or state.has('Cane of Somaria', player)
+ or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player)))
+ or state.has_any(["Bow", "Progressive Bow"], player)
+ or state.has('Fire Rod', player)
+ or can_use_bombs(state, player, enemies)) # Escape assist is set
+
+
def can_get_good_bee(state: CollectionState, player: int) -> bool:
cave = state.multiworld.get_region('Good Bee Cave', player)
return (
diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py
index 4ee5b9d26640..2b99162837da 100644
--- a/worlds/alttp/__init__.py
+++ b/worlds/alttp/__init__.py
@@ -305,6 +305,8 @@ def __init__(self, *args, **kwargs):
self.required_medallions = ["Ether", "Quake"]
self.escape_assist = []
self.shops = []
+ self.logical_heart_containers = 10
+ self.logical_heart_pieces = 24
super(ALTTPWorld, self).__init__(*args, **kwargs)
@classmethod
@@ -384,6 +386,8 @@ def generate_early(self):
self.options.local_items.value |= self.dungeon_local_item_names
self.difficulty_requirements = difficulties[self.options.item_pool.current_key]
+ self.logical_heart_pieces = self.difficulty_requirements.heart_piece_limit
+ self.logical_heart_containers = self.difficulty_requirements.boss_heart_container_limit
# enforce pre-defined local items.
if self.options.goal in ["local_triforce_hunt", "local_ganon_triforce_hunt"]:
diff --git a/worlds/anodyne/tracker/locations.json b/worlds/anodyne/tracker/locations.json
index 0a86b01798cd..ba3e9bb66880 100644
--- a/worlds/anodyne/tracker/locations.json
+++ b/worlds/anodyne/tracker/locations.json
@@ -1,6114 +1,6114 @@
-[
- {
- "name": "Apartment",
- "children": [
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 24,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Ledge Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 536,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Rat Maze Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 600,
- "y": 920
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Exterior Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 136,
- "y": 72
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Couches Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1320,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Apartment - 2F Rat Maze Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1096,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Apartment - 3F Gauntlet Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1384,
- "y": 1176
- }
- ],
- "sections": [
- {
- "name": "Apartment - Boss Chest"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1192,
- "y": 1032
- }
- ],
- "sections": [
- {
- "name": "Apartment - Health Cicada"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 368,
- "y": 768
- }
- ],
- "sections": [
- {
- "name": "Apartment - Warp Pad"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 456,
- "y": 360
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Shortcut Room Dust 1"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 904,
- "y": 392
- }
- ],
- "sections": [
- {
- "name": "Apartment - 2F Switch Pillar Rat Maze Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1080,
- "y": 392
- }
- ],
- "sections": [
- {
- "name": "Apartment - 2F Dash Trap Rat Maze Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1144,
- "y": 552
- }
- ],
- "sections": [
- {
- "name": "Apartment - 2F Flooded Room Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 72,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Couches Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 424,
- "y": 424
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Shortcut Room Dust 2"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 88,
- "y": 392
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Rat Maze Chest Dust 1"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 72,
- "y": 424
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Rat Maze Chest Dust 2"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 24,
- "y": 376
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Rat Maze Chest Dust 3"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 392,
- "y": 840
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Entrance Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 280,
- "y": 536
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Flooded Room Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 360,
- "y": 296
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Flooded Library Dust"
- }
- ]
- },
- {
- "name": "Apartment",
- "map_locations": [
- {
- "map": "Apartment",
- "x": 1196,
- "y": 993
- }
- ],
- "sections": [
- {
- "name": "Defeat Watcher"
- }
- ]
- }
- ]
- },
- {
- "name": "Nexus",
- "children": [
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 592,
- "y": 833
- }
- ],
- "sections": [
- {
- "name": "Apartment - 1F Ledge Chest"
- },
- {
- "name": "Apartment - 1F Rat Maze Chest"
- },
- {
- "name": "Apartment - 1F Exterior Chest"
- },
- {
- "name": "Apartment - 1F Couches Chest"
- },
- {
- "name": "Apartment - 2F Rat Maze Chest"
- },
- {
- "name": "Apartment - 3F Gauntlet Chest"
- },
- {
- "name": "Apartment - Boss Chest"
- },
- {
- "name": "Apartment - Health Cicada"
- },
- {
- "name": "Apartment - Warp Pad"
- },
- {
- "name": "Apartment - 1F Shortcut Room Dust 1"
- },
- {
- "name": "Apartment - 2F Switch Pillar Rat Maze Dust"
- },
- {
- "name": "Apartment - 2F Dash Trap Rat Maze Dust"
- },
- {
- "name": "Apartment - 2F Flooded Room Dust"
- },
- {
- "name": "Apartment - 1F Couches Dust"
- },
- {
- "name": "Apartment - 1F Shortcut Room Dust 2"
- },
- {
- "name": "Apartment - 1F Rat Maze Chest Dust 1"
- },
- {
- "name": "Apartment - 1F Rat Maze Chest Dust 2"
- },
- {
- "name": "Apartment - 1F Rat Maze Chest Dust 3"
- },
- {
- "name": "Apartment - 1F Entrance Dust"
- },
- {
- "name": "Apartment - 1F Flooded Room Dust"
- },
- {
- "name": "Apartment - 1F Flooded Library Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 288,
- "y": 721
- }
- ],
- "sections": [
- {
- "name": "Beach - Dock Chest"
- },
- {
- "name": "Beach - Secret Chest"
- },
- {
- "name": "Beach - Out-of-bounds Chest"
- },
- {
- "name": "Beach - Health Cicada"
- },
- {
- "name": "Beach - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 592,
- "y": 897
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Entrance Chest"
- },
- {
- "name": "Temple of the Seeing One - Shieldy Room Chest"
- },
- {
- "name": "Temple of the Seeing One - Rock-Surrounded Chest"
- },
- {
- "name": "Temple of the Seeing One - Boss Chest"
- },
- {
- "name": "Temple of the Seeing One - After Statue Left Chest"
- },
- {
- "name": "Temple of the Seeing One - After Statue Right Chest"
- },
- {
- "name": "Temple of the Seeing One - Dark Room Chest"
- },
- {
- "name": "Temple of the Seeing One - Health Cicada"
- },
- {
- "name": "Temple of the Seeing One - Green Key"
- },
- {
- "name": "Temple of the Seeing One - Warp Pad"
- },
- {
- "name": "Temple of the Seeing One - Laser Room Dust 1"
- },
- {
- "name": "Temple of the Seeing One - Laser Room Dust 2"
- },
- {
- "name": "Temple of the Seeing One - Room With Holes Dust 1"
- },
- {
- "name": "Temple of the Seeing One - Room With Holes Dust 2"
- },
- {
- "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 1"
- },
- {
- "name": "Temple of the Seeing One - Before Boss Dust 1"
- },
- {
- "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 2"
- },
- {
- "name": "Temple of the Seeing One - Shieldy Room Dust 1"
- },
- {
- "name": "Temple of the Seeing One - Shieldy Room Dust 2"
- },
- {
- "name": "Temple of the Seeing One - Laser Room Dust 3"
- },
- {
- "name": "Temple of the Seeing One - Entrance Dust 1"
- },
- {
- "name": "Temple of the Seeing One - Entrance Dust 2"
- },
- {
- "name": "Temple of the Seeing One - Before Boss Dust 2"
- },
- {
- "name": "Temple of the Seeing One - Laser Room Dust 4"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 80,
- "y": 400
- }
- ],
- "sections": [
- {
- "name": "Blank - Card Chest"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 272,
- "y": 497
- }
- ],
- "sections": [
- {
- "name": "Cell - Top Left Chest"
- },
- {
- "name": "Cell - Chaser Gauntlet Chest"
- },
- {
- "name": "Cell - Health Cicada"
- },
- {
- "name": "Cell - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 192,
- "y": 497
- }
- ],
- "sections": [
- {
- "name": "Circus - Rat Maze Chest"
- },
- {
- "name": "Circus - Clowns Chest"
- },
- {
- "name": "Circus - Fire Pillar Chest"
- },
- {
- "name": "Circus - Arthur Chest"
- },
- {
- "name": "Circus - Javiera Chest"
- },
- {
- "name": "Circus - Lion Chest"
- },
- {
- "name": "Circus - Double Clowns Chest"
- },
- {
- "name": "Circus - Boss Chest"
- },
- {
- "name": "Circus - Health Cicada"
- },
- {
- "name": "Circus - Warp Pad"
- },
- {
- "name": "Circus - Dash Trap Dust"
- },
- {
- "name": "Circus - Fire Pillars in Water Dust 3"
- },
- {
- "name": "Circus - Lion Dust 1"
- },
- {
- "name": "Circus - Lion Dust 2"
- },
- {
- "name": "Circus - Fire Pillars in Water Dust 2"
- },
- {
- "name": "Circus - Fire Pillars in Water Dust 1"
- },
- {
- "name": "Circus - Fire Pillars in Water Dust 4"
- },
- {
- "name": "Circus - Dog Room Dust"
- },
- {
- "name": "Circus - Javiera Dust 2"
- },
- {
- "name": "Circus - Javiera Dust 1"
- },
- {
- "name": "Circus - Slime and Fire Pillar Dust"
- },
- {
- "name": "Circus - Small Contort Room Dust"
- },
- {
- "name": "Circus - Save Point Dust"
- },
- {
- "name": "Circus - Clown Dust 1"
- },
- {
- "name": "Circus - Clown Dust 2"
- },
- {
- "name": "Circus - Spike Dust"
- },
- {
- "name": "Circus - Dash Pad over Hole Dust"
- },
- {
- "name": "Circus - Lion and Dash Pad Dust"
- },
- {
- "name": "Circus - Spike Roller in Water Dust"
- },
- {
- "name": "Circus - Entrance Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 512,
- "y": 561
- }
- ],
- "sections": [
- {
- "name": "Cliffs - Upper Chest"
- },
- {
- "name": "Cliffs - Lower Chest"
- },
- {
- "name": "Cliffs - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 592,
- "y": 561
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Crowded Ledge Chest"
- },
- {
- "name": "Mountain Cavern - 2F Four Enemies Chest"
- },
- {
- "name": "Mountain Cavern - 2F Entrance Chest"
- },
- {
- "name": "Mountain Cavern - 2F Frogs and Dog Chest"
- },
- {
- "name": "Mountain Cavern - 3F Roller Chest"
- },
- {
- "name": "Mountain Cavern - Boss Chest"
- },
- {
- "name": "Mountain Cavern - Extend Upgrade Chest"
- },
- {
- "name": "Mountain Cavern - 2F Frogs and Rotators Chest"
- },
- {
- "name": "Mountain Cavern - Health Cicada"
- },
- {
- "name": "Mountain Cavern - Blue Key"
- },
- {
- "name": "Mountain Cavern - Warp Pad"
- },
- {
- "name": "Mountain Cavern - 3F Top Center Moving Platform Dust"
- },
- {
- "name": "Mountain Cavern - 3F Top Right Moving Platform Dust"
- },
- {
- "name": "Mountain Cavern - 3F Roller Dust"
- },
- {
- "name": "Mountain Cavern - 2F Frogs and Annoyers Dust"
- },
- {
- "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 1"
- },
- {
- "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 2"
- },
- {
- "name": "Mountain Cavern - 2F Circular Hole Dust 1"
- },
- {
- "name": "Mountain Cavern - 2F Circular Hole Dust 2"
- },
- {
- "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 1"
- },
- {
- "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 2"
- },
- {
- "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 1"
- },
- {
- "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 2"
- },
- {
- "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 3"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 256,
- "y": 240
- }
- ],
- "sections": [
- {
- "name": "Debug - River Puzzles Chest"
- },
- {
- "name": "Debug - Upper Prison Chest"
- },
- {
- "name": "Debug - Lower Prison Chest"
- },
- {
- "name": "Debug - Jumping Chest"
- },
- {
- "name": "Debug - Maze Chest"
- },
- {
- "name": "Debug - Moving Platform Dust"
- },
- {
- "name": "Debug - Whirlpool Room Dust 1"
- },
- {
- "name": "Debug - Whirlpool Room Dust 2"
- },
- {
- "name": "Debug - Sound Test Console Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 401,
- "y": 244
- }
- ],
- "sections": [
- {
- "name": "Drawer - Game Over Chest"
- },
- {
- "name": "Drawer - Brown Area Chest"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 400,
- "y": 721
- }
- ],
- "sections": [
- {
- "name": "Fields - Island Chest"
- },
- {
- "name": "Fields - Gauntlet Chest"
- },
- {
- "name": "Fields - Goldman's Cave Chest"
- },
- {
- "name": "Fields - Blocked River Chest"
- },
- {
- "name": "Fields - Cardboard Box"
- },
- {
- "name": "Fields - Shopkeeper Trade"
- },
- {
- "name": "Fields - Mitra Trade"
- },
- {
- "name": "Fields - Near Woods Secret Chest"
- },
- {
- "name": "Fields - Secluded Glen Chest"
- },
- {
- "name": "Fields - Near Crossing Secret Chest"
- },
- {
- "name": "Fields - Warp Pad"
- },
- {
- "name": "Fields - Goldman's Cave Dust 1"
- },
- {
- "name": "Fields - Goldman's Cave Dust 2"
- },
- {
- "name": "Fields - Goldman's Cave Dust 3"
- },
- {
- "name": "Fields - Goldman's Cave Dust 4"
- },
- {
- "name": "Fields - Goldman's Cave Dust 5"
- },
- {
- "name": "Fields - Goldman's Cave Dust 6"
- },
- {
- "name": "Fields - Goldman's Cave Dust 7"
- },
- {
- "name": "Fields - Goldman's Cave Dust 8"
- },
- {
- "name": "Fields - Goldman's Cave Dust 9"
- },
- {
- "name": "Fields - Lake After Spikes Dust"
- },
- {
- "name": "Fields - North River Dust"
- },
- {
- "name": "Fields - Lake After Holes Floating Dust"
- },
- {
- "name": "Fields - South East of Lake Dust"
- },
- {
- "name": "Fields - Lake Near Windmill Dust"
- },
- {
- "name": "Fields - North of Lake Rapids Dust"
- },
- {
- "name": "Fields - North East of Lake Dust"
- },
- {
- "name": "Fields - Before Annoyer Maze Dust"
- },
- {
- "name": "Fields - Mitra House Dust"
- },
- {
- "name": "Fields - Near Red Gate Dust"
- },
- {
- "name": "Fields - After Red Gate Dust"
- },
- {
- "name": "Fields - Near Terminal Dust"
- },
- {
- "name": "Fields - North West of Lake Dust"
- },
- {
- "name": "Fields - Near Beach Dust"
- },
- {
- "name": "Fields - South West Corner Dust"
- },
- {
- "name": "Fields - South East of Gauntlet Dust"
- },
- {
- "name": "Fields - Before Gauntlet Dust"
- },
- {
- "name": "Fields - Island Chest Dust"
- },
- {
- "name": "Fields - Island Start Dust"
- },
- {
- "name": "Fields - Post Whirlpool Dust"
- },
- {
- "name": "Fields - Olive Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 512,
- "y": 721
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Inlet Chest"
- },
- {
- "name": "Deep Forest - Bunny Chest"
- },
- {
- "name": "Deep Forest - Warp Pad"
- },
- {
- "name": "Deep Forest - Relaxation Pond Dust"
- },
- {
- "name": "Deep Forest - Near Cliff Dust"
- },
- {
- "name": "Deep Forest - Floating Dust"
- },
- {
- "name": "Deep Forest - Thorax Dust"
- },
- {
- "name": "Deep Forest - Carved Rock Dust"
- },
- {
- "name": "Deep Forest - Tiny Island Dust"
- },
- {
- "name": "Deep Forest - Inlet Dust"
- },
- {
- "name": "Deep Forest - Before Inlet Chest Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 352,
- "y": 577
- }
- ],
- "sections": [
- {
- "name": "Garden - Swap Upgrade Chest"
- },
- {
- "name": "Garden - Secret Color Puzzle Chest"
- },
- {
- "name": "Garden - Defeat Briar"
- },
- {
- "name": "Garden - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 592,
- "y": 497
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Annoyers Chest"
- },
- {
- "name": "Hotel - 4F Dust Blower Maze Chest"
- },
- {
- "name": "Hotel - 3F Dashers Chest"
- },
- {
- "name": "Hotel - 3F Gasguy Chest"
- },
- {
- "name": "Hotel - 3F Rotators Chest"
- },
- {
- "name": "Hotel - 2F Dog Chest"
- },
- {
- "name": "Hotel - 2F Crevice Right Chest"
- },
- {
- "name": "Hotel - 2F Backrooms Chest"
- },
- {
- "name": "Hotel - 1F Burst Flowers Chest"
- },
- {
- "name": "Hotel - 2F Crevice Left Chest"
- },
- {
- "name": "Hotel - Boss Chest"
- },
- {
- "name": "Hotel - Roof Chest"
- },
- {
- "name": "Hotel - Health Cicada"
- },
- {
- "name": "Hotel - Warp Pad"
- },
- {
- "name": "Hotel - 1F Floating Dustmaid Dust"
- },
- {
- "name": "Hotel - 3F Hallway Dustmaid Dust 2"
- },
- {
- "name": "Hotel - 3F Hallway Dustmaid Dust 1"
- },
- {
- "name": "Hotel - 4F Moving Platform Crossroad 1"
- },
- {
- "name": "Hotel - 1F Boss Dust"
- },
- {
- "name": "Hotel - 1F Gasguy Dust"
- },
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 3"
- },
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 2"
- },
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 1"
- },
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 4"
- },
- {
- "name": "Hotel - 2F Steampipe Dust 2"
- },
- {
- "name": "Hotel - 2F Steampipe Dust 1"
- },
- {
- "name": "Hotel - 1F Locked Dust"
- },
- {
- "name": "Hotel - 2F Dustmaid and Steampipe Dust 3"
- },
- {
- "name": "Hotel - 2F Dustmaid and Steampipe Dust 1"
- },
- {
- "name": "Hotel - 2F Dustmaid and Steampipe Dust 2"
- },
- {
- "name": "Hotel - 2F Dustmaid Hallway Dust"
- },
- {
- "name": "Hotel - 3F Stream Dustmaid Dust"
- },
- {
- "name": "Hotel - 3F Bedroom Dust"
- },
- {
- "name": "Hotel - 4F Slime Dust 2"
- },
- {
- "name": "Hotel - 4F Slime Dust 1"
- },
- {
- "name": "Hotel - 4F Moving Platform Crossroad 2"
- },
- {
- "name": "Hotel - 4F Dustmaid Dust"
- },
- {
- "name": "Hotel - 4F Near Elevator Dust"
- },
- {
- "name": "Hotel - 4F Spring Puzzle Dust"
- },
- {
- "name": "Hotel - 4F Moving Platform Puzzle Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 520,
- "y": 88
- }
- ],
- "sections": [
- {
- "name": "Nexus - Isolated Chest"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 512,
- "y": 897
- }
- ],
- "sections": [
- {
- "name": "Woods - Near Gate Chest"
- },
- {
- "name": "Woods - After Temple Chest"
- },
- {
- "name": "Woods - Health Cicada"
- },
- {
- "name": "Woods - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 192,
- "y": 562
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Slasher Chest"
- },
- {
- "name": "Red Grotto - Middle Cave Right Chest"
- },
- {
- "name": "Red Grotto - Middle Cave Left Chest"
- },
- {
- "name": "Red Grotto - Middle Cave Middle Chest"
- },
- {
- "name": "Red Grotto - Boss Chest"
- },
- {
- "name": "Red Grotto - Left Cave Rapids Chest"
- },
- {
- "name": "Red Grotto - Right Cave Slasher Chest"
- },
- {
- "name": "Red Grotto - Right Cave Four Shooter Chest"
- },
- {
- "name": "Red Grotto - Left Cave Sticky Chest"
- },
- {
- "name": "Red Grotto - Widen Upgrade Chest"
- },
- {
- "name": "Red Grotto - Isaac Dungeon Chest"
- },
- {
- "name": "Red Grotto - Health Cicada"
- },
- {
- "name": "Red Grotto - Red Key"
- },
- {
- "name": "Red Grotto - Middle Cave Right Tentacle"
- },
- {
- "name": "Red Grotto - Middle Cave Left Tentacle"
- },
- {
- "name": "Red Grotto - Left Cave Tentacle"
- },
- {
- "name": "Red Grotto - Right Cave Tentacle"
- },
- {
- "name": "Red Grotto - Warp Pad"
- },
- {
- "name": "Red Grotto - Top Cave Boss Dust 1"
- },
- {
- "name": "Red Grotto - Top Cave Boss Dust 2"
- },
- {
- "name": "Red Grotto - Top Cave Before Boss Dust"
- },
- {
- "name": "Red Grotto - Top Cave Slasher Dust"
- },
- {
- "name": "Red Grotto - Top Cave Before Slasher Dust"
- },
- {
- "name": "Red Grotto - Top Cave Boss Dust 3"
- },
- {
- "name": "Red Grotto - Top Cave Boss Dust 4"
- },
- {
- "name": "Red Grotto - Left Cave Rapids Dust 1"
- },
- {
- "name": "Red Grotto - Left Cave Rapids Dust 2"
- },
- {
- "name": "Red Grotto - Right Cave Before Slasher Dust"
- },
- {
- "name": "Red Grotto - Right Cave Whirlpool Dust 1"
- },
- {
- "name": "Red Grotto - Left Cave Whirlpool Dust 1"
- },
- {
- "name": "Red Grotto - Left Cave Whirlpool Dust 2"
- },
- {
- "name": "Red Grotto - Right Cave Whirlpool Dust 2"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 272,
- "y": 562
- }
- ],
- "sections": [
- {
- "name": "Red Sea - Lonely Chest"
- },
- {
- "name": "Red Sea - Out-of-bounds Chest"
- },
- {
- "name": "Red Sea - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 512,
- "y": 833
- }
- ],
- "sections": [
- {
- "name": "Young Town - Stab Reward Chest"
- },
- {
- "name": "Young Town - Killers Chest"
- },
- {
- "name": "Young Town - Health Cicada"
- },
- {
- "name": "Young Town - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 512,
- "y": 497
- }
- ],
- "sections": [
- {
- "name": "Space - Left Chest"
- },
- {
- "name": "Space - Right Chest"
- },
- {
- "name": "Space - Gauntlet Chest"
- },
- {
- "name": "Space - Hidden Chest"
- },
- {
- "name": "Space - Warp Pad"
- },
- {
- "name": "Space - Challenge Area Dustmaid Dust 1"
- },
- {
- "name": "Space - Challenge Area Dustmaid Dust 2"
- },
- {
- "name": "Space - Challenge Area Lion Dust 1"
- },
- {
- "name": "Space - Challenge Area Lion Dust 2"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 256,
- "y": 897
- }
- ],
- "sections": [
- {
- "name": "Street - Key Chest"
- },
- {
- "name": "Street - Broom Chest"
- },
- {
- "name": "Street - Secret Chest"
- },
- {
- "name": "Street - After Bridge Dust 1"
- },
- {
- "name": "Street - After Bridge Dust 2"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 352,
- "y": 657
- }
- ],
- "sections": [
- {
- "name": "Crossing - Broken Bridge Chest"
- },
- {
- "name": "Crossing - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 448,
- "y": 657
- }
- ],
- "sections": [
- {
- "name": "Windmill - Post Activation Chest"
- },
- {
- "name": "Windmill - Activation"
- },
- {
- "name": "Windmill - Warp Pad"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 512,
- "y": 240
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Reward Chest"
- },
- {
- "name": "Boss Rush - Before Red Boss Dust"
- },
- {
- "name": "Boss Rush - Red Boss Dust 1"
- },
- {
- "name": "Boss Rush - Red Boss Dust 2"
- },
- {
- "name": "Boss Rush - Red Boss Dust 3"
- },
- {
- "name": "Boss Rush - Red Boss Dust 4"
- },
- {
- "name": "Boss Rush - Manager Phase 1 Dust"
- },
- {
- "name": "Boss Rush - Manager Phase 2 Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 352,
- "y": 513
- }
- ],
- "sections": [
- {
- "name": "Blue - Warp Pad"
- },
- {
- "name": "Blue - Completion Reward"
- },
- {
- "name": "Blue - Laser Room Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 448,
- "y": 513
- }
- ],
- "sections": [
- {
- "name": "Red - Warp Pad"
- },
- {
- "name": "Red - Completion Reward"
- },
- {
- "name": "Red - Final Room Dust"
- },
- {
- "name": "Red - Dustmaid Dust"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 567,
- "y": 897
- }
- ],
- "sections": [
- {
- "name": "Defeat Seer"
- },
- {
- "name": "Grab Green Key"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 567,
- "y": 561
- }
- ],
- "sections": [
- {
- "name": "Defeat The Wall"
- },
- {
- "name": "Grab Blue Key"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 423,
- "y": 657
- }
- ],
- "sections": [
- {
- "name": "Windmill activated"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 567,
- "y": 497
- }
- ],
- "sections": [
- {
- "name": "Defeat Manager"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 167,
- "y": 497
- }
- ],
- "sections": [
- {
- "name": "Defeat Servants"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 567,
- "y": 833
- }
- ],
- "sections": [
- {
- "name": "Defeat Watcher"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 327,
- "y": 657
- }
- ],
- "sections": [
- {
- "name": "Defeat Sage"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 327,
- "y": 577
- }
- ],
- "sections": [
- {
- "name": "Defeat Briar"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 327,
- "y": 513
- }
- ],
- "sections": [
- {
- "name": "Blue Completion"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 423,
- "y": 513
- }
- ],
- "sections": [
- {
- "name": "Happy Completion"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 400,
- "y": 32
- }
- ],
- "sections": [
- {
- "name": "Open final gate"
- }
- ]
- },
- {
- "name": "Nexus",
- "map_locations": [
- {
- "map": "Nexus",
- "x": 167,
- "y": 562
- }
- ],
- "sections": [
- {
- "name": "Center left tentacle hit"
- },
- {
- "name": "Center right tentacle hit"
- },
- {
- "name": "Left tentacle hit"
- },
- {
- "name": "Right tentacle hit"
- },
- {
- "name": "Defeat Rogue"
- },
- {
- "name": "Grab Red Key"
- }
- ]
- }
- ]
- },
- {
- "name": "Beach",
- "children": [
- {
- "name": "Beach",
- "map_locations": [
- {
- "map": "Beach",
- "x": 711,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Beach - Dock Chest"
- }
- ]
- },
- {
- "name": "Beach",
- "map_locations": [
- {
- "map": "Beach",
- "x": 376,
- "y": 56
- }
- ],
- "sections": [
- {
- "name": "Beach - Secret Chest"
- }
- ]
- },
- {
- "name": "Beach",
- "map_locations": [
- {
- "map": "Beach",
- "x": 712,
- "y": 1080
- }
- ],
- "sections": [
- {
- "name": "Beach - Out-of-bounds Chest"
- }
- ]
- },
- {
- "name": "Beach",
- "map_locations": [
- {
- "map": "Beach",
- "x": 232,
- "y": 408
- }
- ],
- "sections": [
- {
- "name": "Beach - Health Cicada"
- }
- ]
- },
- {
- "name": "Beach",
- "map_locations": [
- {
- "map": "Beach",
- "x": 1039,
- "y": 400
- }
- ],
- "sections": [
- {
- "name": "Beach - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "children": [
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 24,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Entrance Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 184,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Shieldy Room Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 456,
- "y": 600
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Rock-Surrounded Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 552,
- "y": 40
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Boss Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 920,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - After Statue Left Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 936,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - After Statue Right Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 184,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Dark Room Chest"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 360,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Health Cicada"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 600,
- "y": 104
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Green Key"
- },
- {
- "name": "Grab Green Key"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 672,
- "y": 32
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Warp Pad"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 408,
- "y": 536
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Laser Room Dust 1"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 392,
- "y": 568
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Laser Room Dust 2"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 200,
- "y": 344
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Room With Holes Dust 1"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 296,
- "y": 360
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Room With Holes Dust 2"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 264,
- "y": 280
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 1"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 392,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Before Boss Dust 1"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 188,
- "y": 294
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 2"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 286,
- "y": 137
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Shieldy Room Dust 1"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 295,
- "y": 76
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Shieldy Room Dust 2"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 402,
- "y": 569
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Laser Room Dust 3"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 360,
- "y": 680
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Entrance Dust 1"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 440,
- "y": 680
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Entrance Dust 2"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 373,
- "y": 297
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Before Boss Dust 2"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 454,
- "y": 508
- }
- ],
- "sections": [
- {
- "name": "Temple of the Seeing One - Laser Room Dust 4"
- }
- ]
- },
- {
- "name": "Temple of the Seeing One",
- "map_locations": [
- {
- "map": "Temple of the Seeing One",
- "x": 392,
- "y": 59
- }
- ],
- "sections": [
- {
- "name": "Defeat Seer"
- }
- ]
- }
- ]
- },
- {
- "name": "Blank",
- "children": [
- {
- "name": "Blank",
- "map_locations": [
- {
- "map": "Blank",
- "x": 920,
- "y": 264
- }
- ],
- "sections": [
- {
- "name": "Blank - Card Chest"
- }
- ]
- }
- ]
- },
- {
- "name": "Cell",
- "children": [
- {
- "name": "Cell",
- "map_locations": [
- {
- "map": "Cell",
- "x": 104,
- "y": 56
- }
- ],
- "sections": [
- {
- "name": "Cell - Top Left Chest"
- }
- ]
- },
- {
- "name": "Cell",
- "map_locations": [
- {
- "map": "Cell",
- "x": 904,
- "y": 1352
- }
- ],
- "sections": [
- {
- "name": "Cell - Chaser Gauntlet Chest"
- }
- ]
- },
- {
- "name": "Cell",
- "map_locations": [
- {
- "map": "Cell",
- "x": 1000,
- "y": 1000
- }
- ],
- "sections": [
- {
- "name": "Cell - Health Cicada"
- }
- ]
- },
- {
- "name": "Cell",
- "map_locations": [
- {
- "map": "Cell",
- "x": 560,
- "y": 528
- }
- ],
- "sections": [
- {
- "name": "Cell - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Circus",
- "children": [
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 360,
- "y": 824
- }
- ],
- "sections": [
- {
- "name": "Circus - Rat Maze Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 88,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Circus - Clowns Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 536,
- "y": 1080
- }
- ],
- "sections": [
- {
- "name": "Circus - Fire Pillar Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 1208,
- "y": 824
- }
- ],
- "sections": [
- {
- "name": "Circus - Arthur Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 1208,
- "y": 334
- }
- ],
- "sections": [
- {
- "name": "Circus - Javiera Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 232,
- "y": 344
- }
- ],
- "sections": [
- {
- "name": "Circus - Lion Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 616,
- "y": 424
- }
- ],
- "sections": [
- {
- "name": "Circus - Double Clowns Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 1192,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Circus - Boss Chest"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 712,
- "y": 184
- }
- ],
- "sections": [
- {
- "name": "Circus - Health Cicada"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 752,
- "y": 1376
- }
- ],
- "sections": [
- {
- "name": "Circus - Warp Pad"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 1000,
- "y": 1192
- }
- ],
- "sections": [
- {
- "name": "Circus - Dash Trap Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 344,
- "y": 536
- }
- ],
- "sections": [
- {
- "name": "Circus - Fire Pillars in Water Dust 3"
- },
- {
- "name": "Circus - Fire Pillars in Water Dust 4"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 280,
- "y": 440
- }
- ],
- "sections": [
- {
- "name": "Circus - Lion Dust 1"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 200,
- "y": 456
- }
- ],
- "sections": [
- {
- "name": "Circus - Lion Dust 2"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 440,
- "y": 584
- }
- ],
- "sections": [
- {
- "name": "Circus - Fire Pillars in Water Dust 2"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 456,
- "y": 536
- }
- ],
- "sections": [
- {
- "name": "Circus - Fire Pillars in Water Dust 1"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 824,
- "y": 104
- }
- ],
- "sections": [
- {
- "name": "Circus - Dog Room Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 1160,
- "y": 392
- }
- ],
- "sections": [
- {
- "name": "Circus - Javiera Dust 2"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 1224,
- "y": 424
- }
- ],
- "sections": [
- {
- "name": "Circus - Javiera Dust 1"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 696,
- "y": 1080
- }
- ],
- "sections": [
- {
- "name": "Circus - Slime and Fire Pillar Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 536,
- "y": 520
- }
- ],
- "sections": [
- {
- "name": "Circus - Small Contort Room Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 664,
- "y": 520
- }
- ],
- "sections": [
- {
- "name": "Circus - Save Point Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 136,
- "y": 552
- }
- ],
- "sections": [
- {
- "name": "Circus - Clown Dust 1"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 120,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Circus - Clown Dust 2"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 920,
- "y": 408
- }
- ],
- "sections": [
- {
- "name": "Circus - Spike Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 600,
- "y": 1208
- }
- ],
- "sections": [
- {
- "name": "Circus - Dash Pad over Hole Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 760,
- "y": 840
- }
- ],
- "sections": [
- {
- "name": "Circus - Lion and Dash Pad Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 824,
- "y": 1240
- }
- ],
- "sections": [
- {
- "name": "Circus - Spike Roller in Water Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 776,
- "y": 1320
- }
- ],
- "sections": [
- {
- "name": "Circus - Entrance Dust"
- }
- ]
- },
- {
- "name": "Circus",
- "map_locations": [
- {
- "map": "Circus",
- "x": 734,
- "y": 184
- }
- ],
- "sections": [
- {
- "name": "Defeat Servants"
- }
- ]
- }
- ]
- },
- {
- "name": "Cliffs",
- "children": [
- {
- "name": "Cliffs",
- "map_locations": [
- {
- "map": "Cliffs",
- "x": 744,
- "y": 932
- }
- ],
- "sections": [
- {
- "name": "Cliffs - Upper Chest"
- }
- ]
- },
- {
- "name": "Cliffs",
- "map_locations": [
- {
- "map": "Cliffs",
- "x": 440,
- "y": 1224
- }
- ],
- "sections": [
- {
- "name": "Cliffs - Lower Chest"
- }
- ]
- },
- {
- "name": "Cliffs",
- "map_locations": [
- {
- "map": "Cliffs",
- "x": 554,
- "y": 688
- }
- ],
- "sections": [
- {
- "name": "Cliffs - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "children": [
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 360,
- "y": 344
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Crowded Ledge Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 344,
- "y": 600
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Four Enemies Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 504,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Entrance Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 616,
- "y": 344
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Frogs and Dog Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1416,
- "y": 440
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 3F Roller Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1496,
- "y": 840
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - Boss Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1208,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - Extend Upgrade Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 904,
- "y": 712
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Frogs and Rotators Chest"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1512,
- "y": 1016
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - Health Cicada"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1544,
- "y": 872
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - Blue Key"
- },
- {
- "name": "Grab Blue Key"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1520,
- "y": 745
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - Warp Pad"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1304,
- "y": 104
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 3F Top Center Moving Platform Dust"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1544,
- "y": 72
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 3F Top Right Moving Platform Dust"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1368,
- "y": 360
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 3F Roller Dust"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 24,
- "y": 680
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Frogs and Annoyers Dust"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 216,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 1"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 184,
- "y": 680
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 2"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 184,
- "y": 1080
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Circular Hole Dust 1"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 216,
- "y": 1096
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Circular Hole Dust 2"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 408,
- "y": 1144
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 1"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 456,
- "y": 1224
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 2"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 360,
- "y": 1048
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 1"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 472,
- "y": 1032
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 2"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 376,
- "y": 1096
- }
- ],
- "sections": [
- {
- "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 3"
- }
- ]
- },
- {
- "name": "Mountain Cavern",
- "map_locations": [
- {
- "map": "Mountain Cavern",
- "x": 1519,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Defeat The Wall"
- }
- ]
- }
- ]
- },
- {
- "name": "Debug",
- "children": [
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 728,
- "y": 600
- }
- ],
- "sections": [
- {
- "name": "Debug - River Puzzles Chest"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 72,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Debug - Upper Prison Chest"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 136,
- "y": 1080
- }
- ],
- "sections": [
- {
- "name": "Debug - Lower Prison Chest"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 872,
- "y": 56
- }
- ],
- "sections": [
- {
- "name": "Debug - Jumping Chest"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 888,
- "y": 1496
- }
- ],
- "sections": [
- {
- "name": "Debug - Maze Chest"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 40,
- "y": 360
- }
- ],
- "sections": [
- {
- "name": "Debug - Moving Platform Dust"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 232,
- "y": 264
- }
- ],
- "sections": [
- {
- "name": "Debug - Whirlpool Room Dust 1"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 264,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Debug - Whirlpool Room Dust 2"
- }
- ]
- },
- {
- "name": "Debug",
- "map_locations": [
- {
- "map": "Debug",
- "x": 120,
- "y": 200
- }
- ],
- "sections": [
- {
- "name": "Debug - Sound Test Console Dust"
- }
- ]
- }
- ]
- },
- {
- "name": "Drawer",
- "children": [
- {
- "name": "Drawer",
- "map_locations": [
- {
- "map": "Drawer",
- "x": 440,
- "y": 200
- }
- ],
- "sections": [
- {
- "name": "Drawer - Game Over Chest"
- }
- ]
- },
- {
- "name": "Drawer",
- "map_locations": [
- {
- "map": "Drawer",
- "x": 776,
- "y": 1240
- }
- ],
- "sections": [
- {
- "name": "Drawer - Brown Area Chest"
- }
- ]
- }
- ]
- },
- {
- "name": "Fields",
- "children": [
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1032,
- "y": 1464
- }
- ],
- "sections": [
- {
- "name": "Fields - Island Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 536,
- "y": 1352
- }
- ],
- "sections": [
- {
- "name": "Fields - Gauntlet Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1368,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1864,
- "y": 1512
- }
- ],
- "sections": [
- {
- "name": "Fields - Blocked River Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1160,
- "y": 440
- }
- ],
- "sections": [
- {
- "name": "Fields - Cardboard Box"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1192,
- "y": 712
- }
- ],
- "sections": [
- {
- "name": "Fields - Shopkeeper Trade"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 904,
- "y": 712
- }
- ],
- "sections": [
- {
- "name": "Fields - Mitra Trade"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 744,
- "y": 264
- }
- ],
- "sections": [
- {
- "name": "Fields - Near Woods Secret Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1736,
- "y": 1176
- }
- ],
- "sections": [
- {
- "name": "Fields - Secluded Glen Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 536,
- "y": 232
- }
- ],
- "sections": [
- {
- "name": "Fields - Near Crossing Secret Chest"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1040,
- "y": 576
- }
- ],
- "sections": [
- {
- "name": "Fields - Warp Pad"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1304,
- "y": 104
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 1"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1320,
- "y": 120
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 2"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1336,
- "y": 72
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 3"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1384,
- "y": 72
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 4"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1400,
- "y": 72
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 5"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1400,
- "y": 88
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 6"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1304,
- "y": 120
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 7"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1368,
- "y": 104
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 8"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1400,
- "y": 120
- }
- ],
- "sections": [
- {
- "name": "Fields - Goldman's Cave Dust 9"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1208,
- "y": 1032
- }
- ],
- "sections": [
- {
- "name": "Fields - Lake After Spikes Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1032,
- "y": 248
- }
- ],
- "sections": [
- {
- "name": "Fields - North River Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1160,
- "y": 1704
- }
- ],
- "sections": [
- {
- "name": "Fields - Lake After Holes Floating Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1416,
- "y": 1528
- }
- ],
- "sections": [
- {
- "name": "Fields - South East of Lake Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1352,
- "y": 1320
- }
- ],
- "sections": [
- {
- "name": "Fields - Lake Near Windmill Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 936,
- "y": 824
- }
- ],
- "sections": [
- {
- "name": "Fields - North of Lake Rapids Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1176,
- "y": 840
- }
- ],
- "sections": [
- {
- "name": "Fields - North East of Lake Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1304,
- "y": 568
- }
- ],
- "sections": [
- {
- "name": "Fields - Before Annoyer Maze Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 872,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Fields - Mitra House Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 680,
- "y": 824
- }
- ],
- "sections": [
- {
- "name": "Fields - Near Red Gate Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 504,
- "y": 616
- }
- ],
- "sections": [
- {
- "name": "Fields - After Red Gate Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 184,
- "y": 280
- }
- ],
- "sections": [
- {
- "name": "Fields - Near Terminal Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 424,
- "y": 840
- }
- ],
- "sections": [
- {
- "name": "Fields - North West of Lake Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 248,
- "y": 920
- }
- ],
- "sections": [
- {
- "name": "Fields - Near Beach Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 216,
- "y": 1688
- }
- ],
- "sections": [
- {
- "name": "Fields - South West Corner Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 568,
- "y": 1480
- }
- ],
- "sections": [
- {
- "name": "Fields - South East of Gauntlet Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 216,
- "y": 1320
- }
- ],
- "sections": [
- {
- "name": "Fields - Before Gauntlet Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1000,
- "y": 1512
- }
- ],
- "sections": [
- {
- "name": "Fields - Island Chest Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 856,
- "y": 1496
- }
- ],
- "sections": [
- {
- "name": "Fields - Island Start Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1096,
- "y": 1224
- }
- ],
- "sections": [
- {
- "name": "Fields - Post Whirlpool Dust"
- }
- ]
- },
- {
- "name": "Fields",
- "map_locations": [
- {
- "map": "Fields",
- "x": 1048,
- "y": 1256
- }
- ],
- "sections": [
- {
- "name": "Fields - Olive Dust"
- }
- ]
- }
- ]
- },
- {
- "name": "Deep Forest",
- "children": [
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 536,
- "y": 392
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Inlet Chest"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 88,
- "y": 88
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Bunny Chest"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 240,
- "y": 1040
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Warp Pad"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 360,
- "y": 696
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Relaxation Pond Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 696,
- "y": 1144
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Near Cliff Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 200,
- "y": 1176
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Floating Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 88,
- "y": 1144
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Thorax Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 72,
- "y": 1416
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Carved Rock Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 296,
- "y": 1416
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Tiny Island Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 568,
- "y": 440
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Inlet Dust"
- }
- ]
- },
- {
- "name": "Deep Forest",
- "map_locations": [
- {
- "map": "Deep Forest",
- "x": 600,
- "y": 792
- }
- ],
- "sections": [
- {
- "name": "Deep Forest - Before Inlet Chest Dust"
- }
- ]
- }
- ]
- },
- {
- "name": "Garden",
- "children": [
- {
- "name": "Garden",
- "map_locations": [
- {
- "map": "Garden",
- "x": 401,
- "y": 760
- }
- ],
- "sections": [
- {
- "name": "Garden - Swap Upgrade Chest"
- }
- ]
- },
- {
- "name": "Garden",
- "map_locations": [
- {
- "map": "Garden",
- "x": 139,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Garden - Secret Color Puzzle Chest"
- }
- ]
- },
- {
- "name": "Garden",
- "map_locations": [
- {
- "map": "Garden",
- "x": 400,
- "y": 240
- }
- ],
- "sections": [
- {
- "name": "Garden - Defeat Briar"
- }
- ]
- },
- {
- "name": "Garden",
- "map_locations": [
- {
- "map": "Garden",
- "x": 400,
- "y": 720
- }
- ],
- "sections": [
- {
- "name": "Garden - Warp Pad"
- }
- ]
- },
- {
- "name": "Garden",
- "map_locations": [
- {
- "map": "Garden",
- "x": 400,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Defeat Briar"
- }
- ]
- }
- ]
- },
- {
- "name": "Hotel",
- "children": [
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 776,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Annoyers Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 616,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Dust Blower Maze Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 296,
- "y": 1304
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Dashers Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 616,
- "y": 1784
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Gasguy Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 616,
- "y": 1624
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Rotators Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1576,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Dog Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1416,
- "y": 344
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Crevice Right Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1032,
- "y": 552
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Backrooms Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1144,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Burst Flowers Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1336,
- "y": 376
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Crevice Left Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1352,
- "y": 1832
- }
- ],
- "sections": [
- {
- "name": "Hotel - Boss Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 936,
- "y": 88
- }
- ],
- "sections": [
- {
- "name": "Hotel - Roof Chest"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1352,
- "y": 1688
- }
- ],
- "sections": [
- {
- "name": "Hotel - Health Cicada"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 448,
- "y": 880
- }
- ],
- "sections": [
- {
- "name": "Hotel - Warp Pad"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1368,
- "y": 872
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Floating Dustmaid Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 360,
- "y": 1368
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Hallway Dustmaid Dust 2"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 440,
- "y": 1400
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Hallway Dustmaid Dust 1"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 424,
- "y": 632
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Moving Platform Crossroad 1"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1320,
- "y": 1464
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Boss Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1368,
- "y": 1032
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Gasguy Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1512,
- "y": 1032
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 3"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1464,
- "y": 1016
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 2"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1496,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 1"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1464,
- "y": 1048
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Dustmaid and Steampipe Dust 4"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1032,
- "y": 40
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Steampipe Dust 2"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1080,
- "y": 40
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Steampipe Dust 1"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1096,
- "y": 1144
- }
- ],
- "sections": [
- {
- "name": "Hotel - 1F Locked Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1048,
- "y": 456
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Dustmaid and Steampipe Dust 3"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1032,
- "y": 344
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Dustmaid and Steampipe Dust 1"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1048,
- "y": 376
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Dustmaid and Steampipe Dust 2"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1480,
- "y": 584
- }
- ],
- "sections": [
- {
- "name": "Hotel - 2F Dustmaid Hallway Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 248,
- "y": 1640
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Stream Dustmaid Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 616,
- "y": 1576
- }
- ],
- "sections": [
- {
- "name": "Hotel - 3F Bedroom Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 776,
- "y": 936
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Slime Dust 2"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 680,
- "y": 824
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Slime Dust 1"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 456,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Moving Platform Crossroad 2"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 616,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Dustmaid Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 376,
- "y": 776
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Near Elevator Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 568,
- "y": 1064
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Spring Puzzle Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 264,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Hotel - 4F Moving Platform Puzzle Dust"
- }
- ]
- },
- {
- "name": "Hotel",
- "map_locations": [
- {
- "map": "Hotel",
- "x": 1356,
- "y": 1661
- }
- ],
- "sections": [
- {
- "name": "Defeat Manager"
- }
- ]
- }
- ]
- },
- {
- "name": "Woods",
- "children": [
- {
- "name": "Woods",
- "map_locations": [
- {
- "map": "Woods",
- "x": 184,
- "y": 1544
- }
- ],
- "sections": [
- {
- "name": "Woods - Near Gate Chest"
- }
- ]
- },
- {
- "name": "Woods",
- "map_locations": [
- {
- "map": "Woods",
- "x": 920,
- "y": 1240
- }
- ],
- "sections": [
- {
- "name": "Woods - After Temple Chest"
- }
- ]
- },
- {
- "name": "Woods",
- "map_locations": [
- {
- "map": "Woods",
- "x": 264,
- "y": 904
- }
- ],
- "sections": [
- {
- "name": "Woods - Health Cicada"
- }
- ]
- },
- {
- "name": "Woods",
- "map_locations": [
- {
- "map": "Woods",
- "x": 832,
- "y": 1056
- }
- ],
- "sections": [
- {
- "name": "Woods - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Red Grotto",
- "children": [
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 24,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Slasher Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 552,
- "y": 184
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Middle Cave Right Chest"
- },
- {
- "name": "Red Grotto - Middle Cave Middle Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 520,
- "y": 184
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Middle Cave Left Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 984,
- "y": 264
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Boss Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 40,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Rapids Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1032,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Right Cave Slasher Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 936,
- "y": 504
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Right Cave Four Shooter Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 280,
- "y": 520
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Sticky Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 776,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Widen Upgrade Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 392,
- "y": 1464
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Isaac Dungeon Chest"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1032,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Health Cicada"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1096,
- "y": 296
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Red Key"
- },
- {
- "name": "Grab Red Key"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 840,
- "y": 200
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Middle Cave Right Tentacle"
- },
- {
- "name": "Center right tentacle hit"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 232,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Middle Cave Left Tentacle"
- },
- {
- "name": "Center left tentacle hit"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 200,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Tentacle"
- },
- {
- "name": "Left tentacle hit"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 872,
- "y": 680
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Right Cave Tentacle"
- },
- {
- "name": "Right tentacle hit"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1040,
- "y": 368
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Warp Pad"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 984,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Boss Dust 1"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1096,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Boss Dust 2"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 888,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Before Boss Dust"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 72,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Slasher Dust"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 344,
- "y": 72
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Before Slasher Dust"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1096,
- "y": 136
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Boss Dust 3"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 984,
- "y": 136
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Top Cave Boss Dust 4"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 40,
- "y": 568
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Rapids Dust 1"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 40,
- "y": 616
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Rapids Dust 2"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1032,
- "y": 712
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Right Cave Before Slasher Dust"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1080,
- "y": 808
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Right Cave Whirlpool Dust 1"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 392,
- "y": 856
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Whirlpool Dust 1"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 344,
- "y": 888
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Left Cave Whirlpool Dust 2"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 984,
- "y": 920
- }
- ],
- "sections": [
- {
- "name": "Red Grotto - Right Cave Whirlpool Dust 2"
- }
- ]
- },
- {
- "name": "Red Grotto",
- "map_locations": [
- {
- "map": "Red Grotto",
- "x": 1056,
- "y": 80
- }
- ],
- "sections": [
- {
- "name": "Defeat Rogue"
- }
- ]
- }
- ]
- },
- {
- "name": "Red Sea",
- "children": [
- {
- "name": "Red Sea",
- "map_locations": [
- {
- "map": "Red Sea",
- "x": 392,
- "y": 440
- }
- ],
- "sections": [
- {
- "name": "Red Sea - Lonely Chest"
- }
- ]
- },
- {
- "name": "Red Sea",
- "map_locations": [
- {
- "map": "Red Sea",
- "x": 40,
- "y": 1032
- }
- ],
- "sections": [
- {
- "name": "Red Sea - Out-of-bounds Chest"
- }
- ]
- },
- {
- "name": "Red Sea",
- "map_locations": [
- {
- "map": "Red Sea",
- "x": 560,
- "y": 384
- }
- ],
- "sections": [
- {
- "name": "Red Sea - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Young Town",
- "children": [
- {
- "name": "Young Town",
- "map_locations": [
- {
- "map": "Young Town",
- "x": 264,
- "y": 1192
- }
- ],
- "sections": [
- {
- "name": "Young Town - Stab Reward Chest"
- }
- ]
- },
- {
- "name": "Young Town",
- "map_locations": [
- {
- "map": "Young Town",
- "x": 392,
- "y": 1240
- }
- ],
- "sections": [
- {
- "name": "Young Town - Killers Chest"
- }
- ]
- },
- {
- "name": "Young Town",
- "map_locations": [
- {
- "map": "Young Town",
- "x": 680,
- "y": 408
- }
- ],
- "sections": [
- {
- "name": "Young Town - Health Cicada"
- }
- ]
- },
- {
- "name": "Young Town",
- "map_locations": [
- {
- "map": "Young Town",
- "x": 400,
- "y": 544
- }
- ],
- "sections": [
- {
- "name": "Young Town - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Space",
- "children": [
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 88,
- "y": 56
- }
- ],
- "sections": [
- {
- "name": "Space - Left Chest"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 1528,
- "y": 40
- }
- ],
- "sections": [
- {
- "name": "Space - Right Chest"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 880,
- "y": 1210
- }
- ],
- "sections": [
- {
- "name": "Space - Gauntlet Chest"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 504,
- "y": 24
- }
- ],
- "sections": [
- {
- "name": "Space - Hidden Chest"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 880,
- "y": 400
- }
- ],
- "sections": [
- {
- "name": "Space - Warp Pad"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 552,
- "y": 872
- }
- ],
- "sections": [
- {
- "name": "Space - Challenge Area Dustmaid Dust 1"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 568,
- "y": 872
- }
- ],
- "sections": [
- {
- "name": "Space - Challenge Area Dustmaid Dust 2"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 1144,
- "y": 856
- }
- ],
- "sections": [
- {
- "name": "Space - Challenge Area Lion Dust 1"
- }
- ]
- },
- {
- "name": "Space",
- "map_locations": [
- {
- "map": "Space",
- "x": 1144,
- "y": 888
- }
- ],
- "sections": [
- {
- "name": "Space - Challenge Area Lion Dust 2"
- }
- ]
- }
- ]
- },
- {
- "name": "Street",
- "children": [
- {
- "name": "Street",
- "map_locations": [
- {
- "map": "Street",
- "x": 24,
- "y": 664
- }
- ],
- "sections": [
- {
- "name": "Street - Key Chest"
- }
- ]
- },
- {
- "name": "Street",
- "map_locations": [
- {
- "map": "Street",
- "x": 776,
- "y": 680
- }
- ],
- "sections": [
- {
- "name": "Street - Broom Chest"
- }
- ]
- },
- {
- "name": "Street",
- "map_locations": [
- {
- "map": "Street",
- "x": 280,
- "y": 1016
- }
- ],
- "sections": [
- {
- "name": "Street - Secret Chest"
- }
- ]
- },
- {
- "name": "Street",
- "map_locations": [
- {
- "map": "Street",
- "x": 442,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Street - After Bridge Dust 1"
- }
- ]
- },
- {
- "name": "Street",
- "map_locations": [
- {
- "map": "Street",
- "x": 410,
- "y": 232
- }
- ],
- "sections": [
- {
- "name": "Street - After Bridge Dust 2"
- }
- ]
- }
- ]
- },
- {
- "name": "Crossing",
- "children": [
- {
- "name": "Crossing",
- "map_locations": [
- {
- "map": "Crossing",
- "x": 744,
- "y": 392
- }
- ],
- "sections": [
- {
- "name": "Crossing - Broken Bridge Chest"
- }
- ]
- },
- {
- "name": "Crossing",
- "map_locations": [
- {
- "map": "Crossing",
- "x": 448,
- "y": 736
- }
- ],
- "sections": [
- {
- "name": "Crossing - Warp Pad"
- }
- ]
- },
- {
- "name": "Crossing",
- "map_locations": [
- {
- "map": "Crossing",
- "x": 400,
- "y": 522
- }
- ],
- "sections": [
- {
- "name": "Defeat Sage"
- }
- ]
- }
- ]
- },
- {
- "name": "Windmill",
- "children": [
- {
- "name": "Windmill",
- "map_locations": [
- {
- "map": "Windmill",
- "x": 216,
- "y": 376
- }
- ],
- "sections": [
- {
- "name": "Windmill - Post Activation Chest"
- },
- {
- "name": "Windmill - Activation"
- },
- {
- "name": "Windmill activated"
- }
- ]
- },
- {
- "name": "Windmill",
- "map_locations": [
- {
- "map": "Windmill",
- "x": 272,
- "y": 1040
- }
- ],
- "sections": [
- {
- "name": "Windmill - Warp Pad"
- }
- ]
- }
- ]
- },
- {
- "name": "Boss Rush",
- "children": [
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 376,
- "y": 104
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Reward Chest"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 568,
- "y": 1048
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Before Red Boss Dust"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 664,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Red Boss Dust 1"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 664,
- "y": 1096
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Red Boss Dust 2"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 776,
- "y": 1096
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Red Boss Dust 3"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 776,
- "y": 984
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Red Boss Dust 4"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 40,
- "y": 824
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Manager Phase 1 Dust"
- }
- ]
- },
- {
- "name": "Boss Rush",
- "map_locations": [
- {
- "map": "Boss Rush",
- "x": 40,
- "y": 1000
- }
- ],
- "sections": [
- {
- "name": "Boss Rush - Manager Phase 2 Dust"
- }
- ]
- }
- ]
- },
- {
- "name": "Blue",
- "children": [
- {
- "name": "Blue",
- "map_locations": [
- {
- "map": "Blue",
- "x": 128,
- "y": 128
- }
- ],
- "sections": [
- {
- "name": "Blue - Warp Pad"
- }
- ]
- },
- {
- "name": "Blue",
- "map_locations": [
- {
- "map": "Blue",
- "x": 88,
- "y": 40
- }
- ],
- "sections": [
- {
- "name": "Blue - Completion Reward"
- }
- ]
- },
- {
- "name": "Blue",
- "map_locations": [
- {
- "map": "Blue",
- "x": 440,
- "y": 136
- }
- ],
- "sections": [
- {
- "name": "Blue - Laser Room Dust"
- }
- ]
- },
- {
- "name": "Blue",
- "map_locations": [
- {
- "map": "Blue",
- "x": 80,
- "y": 32
- }
- ],
- "sections": [
- {
- "name": "Blue Completion"
- }
- ]
- }
- ]
- },
- {
- "name": "Red",
- "children": [
- {
- "name": "Red",
- "map_locations": [
- {
- "map": "Red",
- "x": 264,
- "y": 400
- }
- ],
- "sections": [
- {
- "name": "Red - Warp Pad"
- }
- ]
- },
- {
- "name": "Red",
- "map_locations": [
- {
- "map": "Red",
- "x": 662,
- "y": 186
- }
- ],
- "sections": [
- {
- "name": "Red - Completion Reward"
- }
- ]
- },
- {
- "name": "Red",
- "map_locations": [
- {
- "map": "Red",
- "x": 616,
- "y": 216
- }
- ],
- "sections": [
- {
- "name": "Red - Final Room Dust"
- }
- ]
- },
- {
- "name": "Red",
- "map_locations": [
- {
- "map": "Red",
- "x": 184,
- "y": 120
- }
- ],
- "sections": [
- {
- "name": "Red - Dustmaid Dust"
- }
- ]
- },
- {
- "name": "Red",
- "map_locations": [
- {
- "map": "Red",
- "x": 656,
- "y": 176
- }
- ],
- "sections": [
- {
- "name": "Happy Completion"
- }
- ]
- }
- ]
- }
+[
+ {
+ "name": "Apartment",
+ "children": [
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 24,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Ledge Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 536,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Rat Maze Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 600,
+ "y": 920
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Exterior Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 136,
+ "y": 72
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Couches Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1320,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 2F Rat Maze Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1096,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 3F Gauntlet Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1384,
+ "y": 1176
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - Boss Chest"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1192,
+ "y": 1032
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 368,
+ "y": 768
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 456,
+ "y": 360
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Shortcut Room Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 904,
+ "y": 392
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 2F Switch Pillar Rat Maze Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1080,
+ "y": 392
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 2F Dash Trap Rat Maze Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1144,
+ "y": 552
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 2F Flooded Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 72,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Couches Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 424,
+ "y": 424
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Shortcut Room Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 88,
+ "y": 392
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Rat Maze Chest Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 72,
+ "y": 424
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Rat Maze Chest Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 24,
+ "y": 376
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Rat Maze Chest Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 392,
+ "y": 840
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Entrance Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 280,
+ "y": 536
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Flooded Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 360,
+ "y": 296
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Flooded Library Dust"
+ }
+ ]
+ },
+ {
+ "name": "Apartment",
+ "map_locations": [
+ {
+ "map": "Apartment",
+ "x": 1196,
+ "y": 993
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Watcher"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "children": [
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 592,
+ "y": 833
+ }
+ ],
+ "sections": [
+ {
+ "name": "Apartment - 1F Ledge Chest"
+ },
+ {
+ "name": "Apartment - 1F Rat Maze Chest"
+ },
+ {
+ "name": "Apartment - 1F Exterior Chest"
+ },
+ {
+ "name": "Apartment - 1F Couches Chest"
+ },
+ {
+ "name": "Apartment - 2F Rat Maze Chest"
+ },
+ {
+ "name": "Apartment - 3F Gauntlet Chest"
+ },
+ {
+ "name": "Apartment - Boss Chest"
+ },
+ {
+ "name": "Apartment - Health Cicada"
+ },
+ {
+ "name": "Apartment - Warp Pad"
+ },
+ {
+ "name": "Apartment - 1F Shortcut Room Dust 1"
+ },
+ {
+ "name": "Apartment - 2F Switch Pillar Rat Maze Dust"
+ },
+ {
+ "name": "Apartment - 2F Dash Trap Rat Maze Dust"
+ },
+ {
+ "name": "Apartment - 2F Flooded Room Dust"
+ },
+ {
+ "name": "Apartment - 1F Couches Dust"
+ },
+ {
+ "name": "Apartment - 1F Shortcut Room Dust 2"
+ },
+ {
+ "name": "Apartment - 1F Rat Maze Chest Dust 1"
+ },
+ {
+ "name": "Apartment - 1F Rat Maze Chest Dust 2"
+ },
+ {
+ "name": "Apartment - 1F Rat Maze Chest Dust 3"
+ },
+ {
+ "name": "Apartment - 1F Entrance Dust"
+ },
+ {
+ "name": "Apartment - 1F Flooded Room Dust"
+ },
+ {
+ "name": "Apartment - 1F Flooded Library Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 288,
+ "y": 721
+ }
+ ],
+ "sections": [
+ {
+ "name": "Beach - Dock Chest"
+ },
+ {
+ "name": "Beach - Secret Chest"
+ },
+ {
+ "name": "Beach - Out-of-bounds Chest"
+ },
+ {
+ "name": "Beach - Health Cicada"
+ },
+ {
+ "name": "Beach - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 592,
+ "y": 897
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Entrance Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - Shieldy Room Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - Rock-Surrounded Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - Boss Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - After Statue Left Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - After Statue Right Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - Dark Room Chest"
+ },
+ {
+ "name": "Temple of the Seeing One - Health Cicada"
+ },
+ {
+ "name": "Temple of the Seeing One - Green Key"
+ },
+ {
+ "name": "Temple of the Seeing One - Warp Pad"
+ },
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 1"
+ },
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 2"
+ },
+ {
+ "name": "Temple of the Seeing One - Room With Holes Dust 1"
+ },
+ {
+ "name": "Temple of the Seeing One - Room With Holes Dust 2"
+ },
+ {
+ "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 1"
+ },
+ {
+ "name": "Temple of the Seeing One - Before Boss Dust 1"
+ },
+ {
+ "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 2"
+ },
+ {
+ "name": "Temple of the Seeing One - Shieldy Room Dust 1"
+ },
+ {
+ "name": "Temple of the Seeing One - Shieldy Room Dust 2"
+ },
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 3"
+ },
+ {
+ "name": "Temple of the Seeing One - Entrance Dust 1"
+ },
+ {
+ "name": "Temple of the Seeing One - Entrance Dust 2"
+ },
+ {
+ "name": "Temple of the Seeing One - Before Boss Dust 2"
+ },
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 80,
+ "y": 400
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blank - Card Chest"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 272,
+ "y": 497
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cell - Top Left Chest"
+ },
+ {
+ "name": "Cell - Chaser Gauntlet Chest"
+ },
+ {
+ "name": "Cell - Health Cicada"
+ },
+ {
+ "name": "Cell - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 192,
+ "y": 497
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Rat Maze Chest"
+ },
+ {
+ "name": "Circus - Clowns Chest"
+ },
+ {
+ "name": "Circus - Fire Pillar Chest"
+ },
+ {
+ "name": "Circus - Arthur Chest"
+ },
+ {
+ "name": "Circus - Javiera Chest"
+ },
+ {
+ "name": "Circus - Lion Chest"
+ },
+ {
+ "name": "Circus - Double Clowns Chest"
+ },
+ {
+ "name": "Circus - Boss Chest"
+ },
+ {
+ "name": "Circus - Health Cicada"
+ },
+ {
+ "name": "Circus - Warp Pad"
+ },
+ {
+ "name": "Circus - Dash Trap Dust"
+ },
+ {
+ "name": "Circus - Fire Pillars in Water Dust 3"
+ },
+ {
+ "name": "Circus - Lion Dust 1"
+ },
+ {
+ "name": "Circus - Lion Dust 2"
+ },
+ {
+ "name": "Circus - Fire Pillars in Water Dust 2"
+ },
+ {
+ "name": "Circus - Fire Pillars in Water Dust 1"
+ },
+ {
+ "name": "Circus - Fire Pillars in Water Dust 4"
+ },
+ {
+ "name": "Circus - Dog Room Dust"
+ },
+ {
+ "name": "Circus - Javiera Dust 2"
+ },
+ {
+ "name": "Circus - Javiera Dust 1"
+ },
+ {
+ "name": "Circus - Slime and Fire Pillar Dust"
+ },
+ {
+ "name": "Circus - Small Contort Room Dust"
+ },
+ {
+ "name": "Circus - Save Point Dust"
+ },
+ {
+ "name": "Circus - Clown Dust 1"
+ },
+ {
+ "name": "Circus - Clown Dust 2"
+ },
+ {
+ "name": "Circus - Spike Dust"
+ },
+ {
+ "name": "Circus - Dash Pad over Hole Dust"
+ },
+ {
+ "name": "Circus - Lion and Dash Pad Dust"
+ },
+ {
+ "name": "Circus - Spike Roller in Water Dust"
+ },
+ {
+ "name": "Circus - Entrance Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 512,
+ "y": 561
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cliffs - Upper Chest"
+ },
+ {
+ "name": "Cliffs - Lower Chest"
+ },
+ {
+ "name": "Cliffs - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 592,
+ "y": 561
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Crowded Ledge Chest"
+ },
+ {
+ "name": "Mountain Cavern - 2F Four Enemies Chest"
+ },
+ {
+ "name": "Mountain Cavern - 2F Entrance Chest"
+ },
+ {
+ "name": "Mountain Cavern - 2F Frogs and Dog Chest"
+ },
+ {
+ "name": "Mountain Cavern - 3F Roller Chest"
+ },
+ {
+ "name": "Mountain Cavern - Boss Chest"
+ },
+ {
+ "name": "Mountain Cavern - Extend Upgrade Chest"
+ },
+ {
+ "name": "Mountain Cavern - 2F Frogs and Rotators Chest"
+ },
+ {
+ "name": "Mountain Cavern - Health Cicada"
+ },
+ {
+ "name": "Mountain Cavern - Blue Key"
+ },
+ {
+ "name": "Mountain Cavern - Warp Pad"
+ },
+ {
+ "name": "Mountain Cavern - 3F Top Center Moving Platform Dust"
+ },
+ {
+ "name": "Mountain Cavern - 3F Top Right Moving Platform Dust"
+ },
+ {
+ "name": "Mountain Cavern - 3F Roller Dust"
+ },
+ {
+ "name": "Mountain Cavern - 2F Frogs and Annoyers Dust"
+ },
+ {
+ "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 1"
+ },
+ {
+ "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 2"
+ },
+ {
+ "name": "Mountain Cavern - 2F Circular Hole Dust 1"
+ },
+ {
+ "name": "Mountain Cavern - 2F Circular Hole Dust 2"
+ },
+ {
+ "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 1"
+ },
+ {
+ "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 2"
+ },
+ {
+ "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 1"
+ },
+ {
+ "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 2"
+ },
+ {
+ "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 256,
+ "y": 240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - River Puzzles Chest"
+ },
+ {
+ "name": "Debug - Upper Prison Chest"
+ },
+ {
+ "name": "Debug - Lower Prison Chest"
+ },
+ {
+ "name": "Debug - Jumping Chest"
+ },
+ {
+ "name": "Debug - Maze Chest"
+ },
+ {
+ "name": "Debug - Moving Platform Dust"
+ },
+ {
+ "name": "Debug - Whirlpool Room Dust 1"
+ },
+ {
+ "name": "Debug - Whirlpool Room Dust 2"
+ },
+ {
+ "name": "Debug - Sound Test Console Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 401,
+ "y": 244
+ }
+ ],
+ "sections": [
+ {
+ "name": "Drawer - Game Over Chest"
+ },
+ {
+ "name": "Drawer - Brown Area Chest"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 400,
+ "y": 721
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Island Chest"
+ },
+ {
+ "name": "Fields - Gauntlet Chest"
+ },
+ {
+ "name": "Fields - Goldman's Cave Chest"
+ },
+ {
+ "name": "Fields - Blocked River Chest"
+ },
+ {
+ "name": "Fields - Cardboard Box"
+ },
+ {
+ "name": "Fields - Shopkeeper Trade"
+ },
+ {
+ "name": "Fields - Mitra Trade"
+ },
+ {
+ "name": "Fields - Near Woods Secret Chest"
+ },
+ {
+ "name": "Fields - Secluded Glen Chest"
+ },
+ {
+ "name": "Fields - Near Crossing Secret Chest"
+ },
+ {
+ "name": "Fields - Warp Pad"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 1"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 2"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 3"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 4"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 5"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 6"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 7"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 8"
+ },
+ {
+ "name": "Fields - Goldman's Cave Dust 9"
+ },
+ {
+ "name": "Fields - Lake After Spikes Dust"
+ },
+ {
+ "name": "Fields - North River Dust"
+ },
+ {
+ "name": "Fields - Lake After Holes Floating Dust"
+ },
+ {
+ "name": "Fields - South East of Lake Dust"
+ },
+ {
+ "name": "Fields - Lake Near Windmill Dust"
+ },
+ {
+ "name": "Fields - North of Lake Rapids Dust"
+ },
+ {
+ "name": "Fields - North East of Lake Dust"
+ },
+ {
+ "name": "Fields - Before Annoyer Maze Dust"
+ },
+ {
+ "name": "Fields - Mitra House Dust"
+ },
+ {
+ "name": "Fields - Near Red Gate Dust"
+ },
+ {
+ "name": "Fields - After Red Gate Dust"
+ },
+ {
+ "name": "Fields - Near Terminal Dust"
+ },
+ {
+ "name": "Fields - North West of Lake Dust"
+ },
+ {
+ "name": "Fields - Near Beach Dust"
+ },
+ {
+ "name": "Fields - South West Corner Dust"
+ },
+ {
+ "name": "Fields - South East of Gauntlet Dust"
+ },
+ {
+ "name": "Fields - Before Gauntlet Dust"
+ },
+ {
+ "name": "Fields - Island Chest Dust"
+ },
+ {
+ "name": "Fields - Island Start Dust"
+ },
+ {
+ "name": "Fields - Post Whirlpool Dust"
+ },
+ {
+ "name": "Fields - Olive Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 512,
+ "y": 721
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Inlet Chest"
+ },
+ {
+ "name": "Deep Forest - Bunny Chest"
+ },
+ {
+ "name": "Deep Forest - Warp Pad"
+ },
+ {
+ "name": "Deep Forest - Relaxation Pond Dust"
+ },
+ {
+ "name": "Deep Forest - Near Cliff Dust"
+ },
+ {
+ "name": "Deep Forest - Floating Dust"
+ },
+ {
+ "name": "Deep Forest - Thorax Dust"
+ },
+ {
+ "name": "Deep Forest - Carved Rock Dust"
+ },
+ {
+ "name": "Deep Forest - Tiny Island Dust"
+ },
+ {
+ "name": "Deep Forest - Inlet Dust"
+ },
+ {
+ "name": "Deep Forest - Before Inlet Chest Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 352,
+ "y": 577
+ }
+ ],
+ "sections": [
+ {
+ "name": "Garden - Swap Upgrade Chest"
+ },
+ {
+ "name": "Garden - Secret Color Puzzle Chest"
+ },
+ {
+ "name": "Garden - Defeat Briar"
+ },
+ {
+ "name": "Garden - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 592,
+ "y": 497
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Annoyers Chest"
+ },
+ {
+ "name": "Hotel - 4F Dust Blower Maze Chest"
+ },
+ {
+ "name": "Hotel - 3F Dashers Chest"
+ },
+ {
+ "name": "Hotel - 3F Gasguy Chest"
+ },
+ {
+ "name": "Hotel - 3F Rotators Chest"
+ },
+ {
+ "name": "Hotel - 2F Dog Chest"
+ },
+ {
+ "name": "Hotel - 2F Crevice Right Chest"
+ },
+ {
+ "name": "Hotel - 2F Backrooms Chest"
+ },
+ {
+ "name": "Hotel - 1F Burst Flowers Chest"
+ },
+ {
+ "name": "Hotel - 2F Crevice Left Chest"
+ },
+ {
+ "name": "Hotel - Boss Chest"
+ },
+ {
+ "name": "Hotel - Roof Chest"
+ },
+ {
+ "name": "Hotel - Health Cicada"
+ },
+ {
+ "name": "Hotel - Warp Pad"
+ },
+ {
+ "name": "Hotel - 1F Floating Dustmaid Dust"
+ },
+ {
+ "name": "Hotel - 3F Hallway Dustmaid Dust 2"
+ },
+ {
+ "name": "Hotel - 3F Hallway Dustmaid Dust 1"
+ },
+ {
+ "name": "Hotel - 4F Moving Platform Crossroad 1"
+ },
+ {
+ "name": "Hotel - 1F Boss Dust"
+ },
+ {
+ "name": "Hotel - 1F Gasguy Dust"
+ },
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 3"
+ },
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 2"
+ },
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 1"
+ },
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 4"
+ },
+ {
+ "name": "Hotel - 2F Steampipe Dust 2"
+ },
+ {
+ "name": "Hotel - 2F Steampipe Dust 1"
+ },
+ {
+ "name": "Hotel - 1F Locked Dust"
+ },
+ {
+ "name": "Hotel - 2F Dustmaid and Steampipe Dust 3"
+ },
+ {
+ "name": "Hotel - 2F Dustmaid and Steampipe Dust 1"
+ },
+ {
+ "name": "Hotel - 2F Dustmaid and Steampipe Dust 2"
+ },
+ {
+ "name": "Hotel - 2F Dustmaid Hallway Dust"
+ },
+ {
+ "name": "Hotel - 3F Stream Dustmaid Dust"
+ },
+ {
+ "name": "Hotel - 3F Bedroom Dust"
+ },
+ {
+ "name": "Hotel - 4F Slime Dust 2"
+ },
+ {
+ "name": "Hotel - 4F Slime Dust 1"
+ },
+ {
+ "name": "Hotel - 4F Moving Platform Crossroad 2"
+ },
+ {
+ "name": "Hotel - 4F Dustmaid Dust"
+ },
+ {
+ "name": "Hotel - 4F Near Elevator Dust"
+ },
+ {
+ "name": "Hotel - 4F Spring Puzzle Dust"
+ },
+ {
+ "name": "Hotel - 4F Moving Platform Puzzle Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 520,
+ "y": 88
+ }
+ ],
+ "sections": [
+ {
+ "name": "Nexus - Isolated Chest"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 512,
+ "y": 897
+ }
+ ],
+ "sections": [
+ {
+ "name": "Woods - Near Gate Chest"
+ },
+ {
+ "name": "Woods - After Temple Chest"
+ },
+ {
+ "name": "Woods - Health Cicada"
+ },
+ {
+ "name": "Woods - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 192,
+ "y": 562
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Slasher Chest"
+ },
+ {
+ "name": "Red Grotto - Middle Cave Right Chest"
+ },
+ {
+ "name": "Red Grotto - Middle Cave Left Chest"
+ },
+ {
+ "name": "Red Grotto - Middle Cave Middle Chest"
+ },
+ {
+ "name": "Red Grotto - Boss Chest"
+ },
+ {
+ "name": "Red Grotto - Left Cave Rapids Chest"
+ },
+ {
+ "name": "Red Grotto - Right Cave Slasher Chest"
+ },
+ {
+ "name": "Red Grotto - Right Cave Four Shooter Chest"
+ },
+ {
+ "name": "Red Grotto - Left Cave Sticky Chest"
+ },
+ {
+ "name": "Red Grotto - Widen Upgrade Chest"
+ },
+ {
+ "name": "Red Grotto - Isaac Dungeon Chest"
+ },
+ {
+ "name": "Red Grotto - Health Cicada"
+ },
+ {
+ "name": "Red Grotto - Red Key"
+ },
+ {
+ "name": "Red Grotto - Middle Cave Right Tentacle"
+ },
+ {
+ "name": "Red Grotto - Middle Cave Left Tentacle"
+ },
+ {
+ "name": "Red Grotto - Left Cave Tentacle"
+ },
+ {
+ "name": "Red Grotto - Right Cave Tentacle"
+ },
+ {
+ "name": "Red Grotto - Warp Pad"
+ },
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 1"
+ },
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 2"
+ },
+ {
+ "name": "Red Grotto - Top Cave Before Boss Dust"
+ },
+ {
+ "name": "Red Grotto - Top Cave Slasher Dust"
+ },
+ {
+ "name": "Red Grotto - Top Cave Before Slasher Dust"
+ },
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 3"
+ },
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 4"
+ },
+ {
+ "name": "Red Grotto - Left Cave Rapids Dust 1"
+ },
+ {
+ "name": "Red Grotto - Left Cave Rapids Dust 2"
+ },
+ {
+ "name": "Red Grotto - Right Cave Before Slasher Dust"
+ },
+ {
+ "name": "Red Grotto - Right Cave Whirlpool Dust 1"
+ },
+ {
+ "name": "Red Grotto - Left Cave Whirlpool Dust 1"
+ },
+ {
+ "name": "Red Grotto - Left Cave Whirlpool Dust 2"
+ },
+ {
+ "name": "Red Grotto - Right Cave Whirlpool Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 272,
+ "y": 562
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Sea - Lonely Chest"
+ },
+ {
+ "name": "Red Sea - Out-of-bounds Chest"
+ },
+ {
+ "name": "Red Sea - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 512,
+ "y": 833
+ }
+ ],
+ "sections": [
+ {
+ "name": "Young Town - Stab Reward Chest"
+ },
+ {
+ "name": "Young Town - Killers Chest"
+ },
+ {
+ "name": "Young Town - Health Cicada"
+ },
+ {
+ "name": "Young Town - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 512,
+ "y": 497
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Left Chest"
+ },
+ {
+ "name": "Space - Right Chest"
+ },
+ {
+ "name": "Space - Gauntlet Chest"
+ },
+ {
+ "name": "Space - Hidden Chest"
+ },
+ {
+ "name": "Space - Warp Pad"
+ },
+ {
+ "name": "Space - Challenge Area Dustmaid Dust 1"
+ },
+ {
+ "name": "Space - Challenge Area Dustmaid Dust 2"
+ },
+ {
+ "name": "Space - Challenge Area Lion Dust 1"
+ },
+ {
+ "name": "Space - Challenge Area Lion Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 256,
+ "y": 897
+ }
+ ],
+ "sections": [
+ {
+ "name": "Street - Key Chest"
+ },
+ {
+ "name": "Street - Broom Chest"
+ },
+ {
+ "name": "Street - Secret Chest"
+ },
+ {
+ "name": "Street - After Bridge Dust 1"
+ },
+ {
+ "name": "Street - After Bridge Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 352,
+ "y": 657
+ }
+ ],
+ "sections": [
+ {
+ "name": "Crossing - Broken Bridge Chest"
+ },
+ {
+ "name": "Crossing - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 448,
+ "y": 657
+ }
+ ],
+ "sections": [
+ {
+ "name": "Windmill - Post Activation Chest"
+ },
+ {
+ "name": "Windmill - Activation"
+ },
+ {
+ "name": "Windmill - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 512,
+ "y": 240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Reward Chest"
+ },
+ {
+ "name": "Boss Rush - Before Red Boss Dust"
+ },
+ {
+ "name": "Boss Rush - Red Boss Dust 1"
+ },
+ {
+ "name": "Boss Rush - Red Boss Dust 2"
+ },
+ {
+ "name": "Boss Rush - Red Boss Dust 3"
+ },
+ {
+ "name": "Boss Rush - Red Boss Dust 4"
+ },
+ {
+ "name": "Boss Rush - Manager Phase 1 Dust"
+ },
+ {
+ "name": "Boss Rush - Manager Phase 2 Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 352,
+ "y": 513
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blue - Warp Pad"
+ },
+ {
+ "name": "Blue - Completion Reward"
+ },
+ {
+ "name": "Blue - Laser Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 448,
+ "y": 513
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red - Warp Pad"
+ },
+ {
+ "name": "Red - Completion Reward"
+ },
+ {
+ "name": "Red - Final Room Dust"
+ },
+ {
+ "name": "Red - Dustmaid Dust"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 567,
+ "y": 897
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Seer"
+ },
+ {
+ "name": "Grab Green Key"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 567,
+ "y": 561
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat The Wall"
+ },
+ {
+ "name": "Grab Blue Key"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 423,
+ "y": 657
+ }
+ ],
+ "sections": [
+ {
+ "name": "Windmill activated"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 567,
+ "y": 497
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Manager"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 167,
+ "y": 497
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Servants"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 567,
+ "y": 833
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Watcher"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 327,
+ "y": 657
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Sage"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 327,
+ "y": 577
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Briar"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 327,
+ "y": 513
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blue Completion"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 423,
+ "y": 513
+ }
+ ],
+ "sections": [
+ {
+ "name": "Happy Completion"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 400,
+ "y": 32
+ }
+ ],
+ "sections": [
+ {
+ "name": "Open final gate"
+ }
+ ]
+ },
+ {
+ "name": "Nexus",
+ "map_locations": [
+ {
+ "map": "Nexus",
+ "x": 167,
+ "y": 562
+ }
+ ],
+ "sections": [
+ {
+ "name": "Center left tentacle hit"
+ },
+ {
+ "name": "Center right tentacle hit"
+ },
+ {
+ "name": "Left tentacle hit"
+ },
+ {
+ "name": "Right tentacle hit"
+ },
+ {
+ "name": "Defeat Rogue"
+ },
+ {
+ "name": "Grab Red Key"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Beach",
+ "children": [
+ {
+ "name": "Beach",
+ "map_locations": [
+ {
+ "map": "Beach",
+ "x": 711,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Beach - Dock Chest"
+ }
+ ]
+ },
+ {
+ "name": "Beach",
+ "map_locations": [
+ {
+ "map": "Beach",
+ "x": 376,
+ "y": 56
+ }
+ ],
+ "sections": [
+ {
+ "name": "Beach - Secret Chest"
+ }
+ ]
+ },
+ {
+ "name": "Beach",
+ "map_locations": [
+ {
+ "map": "Beach",
+ "x": 712,
+ "y": 1080
+ }
+ ],
+ "sections": [
+ {
+ "name": "Beach - Out-of-bounds Chest"
+ }
+ ]
+ },
+ {
+ "name": "Beach",
+ "map_locations": [
+ {
+ "map": "Beach",
+ "x": 232,
+ "y": 408
+ }
+ ],
+ "sections": [
+ {
+ "name": "Beach - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Beach",
+ "map_locations": [
+ {
+ "map": "Beach",
+ "x": 1039,
+ "y": 400
+ }
+ ],
+ "sections": [
+ {
+ "name": "Beach - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "children": [
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 24,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Entrance Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 184,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Shieldy Room Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 456,
+ "y": 600
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Rock-Surrounded Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 552,
+ "y": 40
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Boss Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 920,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - After Statue Left Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 936,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - After Statue Right Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 184,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Dark Room Chest"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 360,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 600,
+ "y": 104
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Green Key"
+ },
+ {
+ "name": "Grab Green Key"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 672,
+ "y": 32
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 408,
+ "y": 536
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 392,
+ "y": 568
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 200,
+ "y": 344
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Room With Holes Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 296,
+ "y": 360
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Room With Holes Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 264,
+ "y": 280
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 392,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Before Boss Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 188,
+ "y": 294
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Past Shieldy Puzzle Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 286,
+ "y": 137
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Shieldy Room Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 295,
+ "y": 76
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Shieldy Room Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 402,
+ "y": 569
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 360,
+ "y": 680
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Entrance Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 440,
+ "y": 680
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Entrance Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 373,
+ "y": 297
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Before Boss Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 454,
+ "y": 508
+ }
+ ],
+ "sections": [
+ {
+ "name": "Temple of the Seeing One - Laser Room Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "map_locations": [
+ {
+ "map": "Temple of the Seeing One",
+ "x": 392,
+ "y": 59
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Seer"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Blank",
+ "children": [
+ {
+ "name": "Blank",
+ "map_locations": [
+ {
+ "map": "Blank",
+ "x": 920,
+ "y": 264
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blank - Card Chest"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Cell",
+ "children": [
+ {
+ "name": "Cell",
+ "map_locations": [
+ {
+ "map": "Cell",
+ "x": 104,
+ "y": 56
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cell - Top Left Chest"
+ }
+ ]
+ },
+ {
+ "name": "Cell",
+ "map_locations": [
+ {
+ "map": "Cell",
+ "x": 904,
+ "y": 1352
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cell - Chaser Gauntlet Chest"
+ }
+ ]
+ },
+ {
+ "name": "Cell",
+ "map_locations": [
+ {
+ "map": "Cell",
+ "x": 1000,
+ "y": 1000
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cell - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Cell",
+ "map_locations": [
+ {
+ "map": "Cell",
+ "x": 560,
+ "y": 528
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cell - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "children": [
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 360,
+ "y": 824
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Rat Maze Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 88,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Clowns Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 536,
+ "y": 1080
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Fire Pillar Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 1208,
+ "y": 824
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Arthur Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 1208,
+ "y": 334
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Javiera Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 232,
+ "y": 344
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Lion Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 616,
+ "y": 424
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Double Clowns Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 1192,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Boss Chest"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 712,
+ "y": 184
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 752,
+ "y": 1376
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 1000,
+ "y": 1192
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Dash Trap Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 344,
+ "y": 536
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Fire Pillars in Water Dust 3"
+ },
+ {
+ "name": "Circus - Fire Pillars in Water Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 280,
+ "y": 440
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Lion Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 200,
+ "y": 456
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Lion Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 440,
+ "y": 584
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Fire Pillars in Water Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 456,
+ "y": 536
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Fire Pillars in Water Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 824,
+ "y": 104
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Dog Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 1160,
+ "y": 392
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Javiera Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 1224,
+ "y": 424
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Javiera Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 696,
+ "y": 1080
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Slime and Fire Pillar Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 536,
+ "y": 520
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Small Contort Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 664,
+ "y": 520
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Save Point Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 136,
+ "y": 552
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Clown Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 120,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Clown Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 920,
+ "y": 408
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Spike Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 600,
+ "y": 1208
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Dash Pad over Hole Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 760,
+ "y": 840
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Lion and Dash Pad Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 824,
+ "y": 1240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Spike Roller in Water Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 776,
+ "y": 1320
+ }
+ ],
+ "sections": [
+ {
+ "name": "Circus - Entrance Dust"
+ }
+ ]
+ },
+ {
+ "name": "Circus",
+ "map_locations": [
+ {
+ "map": "Circus",
+ "x": 734,
+ "y": 184
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Servants"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Cliffs",
+ "children": [
+ {
+ "name": "Cliffs",
+ "map_locations": [
+ {
+ "map": "Cliffs",
+ "x": 744,
+ "y": 932
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cliffs - Upper Chest"
+ }
+ ]
+ },
+ {
+ "name": "Cliffs",
+ "map_locations": [
+ {
+ "map": "Cliffs",
+ "x": 440,
+ "y": 1224
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cliffs - Lower Chest"
+ }
+ ]
+ },
+ {
+ "name": "Cliffs",
+ "map_locations": [
+ {
+ "map": "Cliffs",
+ "x": 554,
+ "y": 688
+ }
+ ],
+ "sections": [
+ {
+ "name": "Cliffs - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "children": [
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 360,
+ "y": 344
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Crowded Ledge Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 344,
+ "y": 600
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Four Enemies Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 504,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Entrance Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 616,
+ "y": 344
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Frogs and Dog Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1416,
+ "y": 440
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 3F Roller Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1496,
+ "y": 840
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - Boss Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1208,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - Extend Upgrade Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 904,
+ "y": 712
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Frogs and Rotators Chest"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1512,
+ "y": 1016
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1544,
+ "y": 872
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - Blue Key"
+ },
+ {
+ "name": "Grab Blue Key"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1520,
+ "y": 745
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1304,
+ "y": 104
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 3F Top Center Moving Platform Dust"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1544,
+ "y": 72
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 3F Top Right Moving Platform Dust"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1368,
+ "y": 360
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 3F Roller Dust"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 24,
+ "y": 680
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Frogs and Annoyers Dust"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 216,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 184,
+ "y": 680
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Rotators and Annoyers Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 184,
+ "y": 1080
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Circular Hole Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 216,
+ "y": 1096
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Circular Hole Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 408,
+ "y": 1144
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 456,
+ "y": 1224
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Crossing Moving Platforms Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 360,
+ "y": 1048
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 472,
+ "y": 1032
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 376,
+ "y": 1096
+ }
+ ],
+ "sections": [
+ {
+ "name": "Mountain Cavern - 2F Moving Platform Crossroad Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Mountain Cavern",
+ "map_locations": [
+ {
+ "map": "Mountain Cavern",
+ "x": 1519,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat The Wall"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "children": [
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 728,
+ "y": 600
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - River Puzzles Chest"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 72,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Upper Prison Chest"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 136,
+ "y": 1080
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Lower Prison Chest"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 872,
+ "y": 56
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Jumping Chest"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 888,
+ "y": 1496
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Maze Chest"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 40,
+ "y": 360
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Moving Platform Dust"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 232,
+ "y": 264
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Whirlpool Room Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 264,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Whirlpool Room Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Debug",
+ "map_locations": [
+ {
+ "map": "Debug",
+ "x": 120,
+ "y": 200
+ }
+ ],
+ "sections": [
+ {
+ "name": "Debug - Sound Test Console Dust"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Drawer",
+ "children": [
+ {
+ "name": "Drawer",
+ "map_locations": [
+ {
+ "map": "Drawer",
+ "x": 440,
+ "y": 200
+ }
+ ],
+ "sections": [
+ {
+ "name": "Drawer - Game Over Chest"
+ }
+ ]
+ },
+ {
+ "name": "Drawer",
+ "map_locations": [
+ {
+ "map": "Drawer",
+ "x": 776,
+ "y": 1240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Drawer - Brown Area Chest"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "children": [
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1032,
+ "y": 1464
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Island Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 536,
+ "y": 1352
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Gauntlet Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1368,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1864,
+ "y": 1512
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Blocked River Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1160,
+ "y": 440
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Cardboard Box"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1192,
+ "y": 712
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Shopkeeper Trade"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 904,
+ "y": 712
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Mitra Trade"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 744,
+ "y": 264
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Near Woods Secret Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1736,
+ "y": 1176
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Secluded Glen Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 536,
+ "y": 232
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Near Crossing Secret Chest"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1040,
+ "y": 576
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1304,
+ "y": 104
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1320,
+ "y": 120
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1336,
+ "y": 72
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1384,
+ "y": 72
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1400,
+ "y": 72
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 5"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1400,
+ "y": 88
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 6"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1304,
+ "y": 120
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 7"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1368,
+ "y": 104
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 8"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1400,
+ "y": 120
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Goldman's Cave Dust 9"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1208,
+ "y": 1032
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Lake After Spikes Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1032,
+ "y": 248
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - North River Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1160,
+ "y": 1704
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Lake After Holes Floating Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1416,
+ "y": 1528
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - South East of Lake Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1352,
+ "y": 1320
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Lake Near Windmill Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 936,
+ "y": 824
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - North of Lake Rapids Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1176,
+ "y": 840
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - North East of Lake Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1304,
+ "y": 568
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Before Annoyer Maze Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 872,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Mitra House Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 680,
+ "y": 824
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Near Red Gate Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 504,
+ "y": 616
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - After Red Gate Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 184,
+ "y": 280
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Near Terminal Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 424,
+ "y": 840
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - North West of Lake Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 248,
+ "y": 920
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Near Beach Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 216,
+ "y": 1688
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - South West Corner Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 568,
+ "y": 1480
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - South East of Gauntlet Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 216,
+ "y": 1320
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Before Gauntlet Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1000,
+ "y": 1512
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Island Chest Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 856,
+ "y": 1496
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Island Start Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1096,
+ "y": 1224
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Post Whirlpool Dust"
+ }
+ ]
+ },
+ {
+ "name": "Fields",
+ "map_locations": [
+ {
+ "map": "Fields",
+ "x": 1048,
+ "y": 1256
+ }
+ ],
+ "sections": [
+ {
+ "name": "Fields - Olive Dust"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "children": [
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 536,
+ "y": 392
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Inlet Chest"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 88,
+ "y": 88
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Bunny Chest"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 240,
+ "y": 1040
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 360,
+ "y": 696
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Relaxation Pond Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 696,
+ "y": 1144
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Near Cliff Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 200,
+ "y": 1176
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Floating Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 88,
+ "y": 1144
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Thorax Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 72,
+ "y": 1416
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Carved Rock Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 296,
+ "y": 1416
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Tiny Island Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 568,
+ "y": 440
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Inlet Dust"
+ }
+ ]
+ },
+ {
+ "name": "Deep Forest",
+ "map_locations": [
+ {
+ "map": "Deep Forest",
+ "x": 600,
+ "y": 792
+ }
+ ],
+ "sections": [
+ {
+ "name": "Deep Forest - Before Inlet Chest Dust"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Garden",
+ "children": [
+ {
+ "name": "Garden",
+ "map_locations": [
+ {
+ "map": "Garden",
+ "x": 401,
+ "y": 760
+ }
+ ],
+ "sections": [
+ {
+ "name": "Garden - Swap Upgrade Chest"
+ }
+ ]
+ },
+ {
+ "name": "Garden",
+ "map_locations": [
+ {
+ "map": "Garden",
+ "x": 139,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Garden - Secret Color Puzzle Chest"
+ }
+ ]
+ },
+ {
+ "name": "Garden",
+ "map_locations": [
+ {
+ "map": "Garden",
+ "x": 400,
+ "y": 240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Garden - Defeat Briar"
+ }
+ ]
+ },
+ {
+ "name": "Garden",
+ "map_locations": [
+ {
+ "map": "Garden",
+ "x": 400,
+ "y": 720
+ }
+ ],
+ "sections": [
+ {
+ "name": "Garden - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Garden",
+ "map_locations": [
+ {
+ "map": "Garden",
+ "x": 400,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Briar"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "children": [
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 776,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Annoyers Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 616,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Dust Blower Maze Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 296,
+ "y": 1304
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Dashers Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 616,
+ "y": 1784
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Gasguy Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 616,
+ "y": 1624
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Rotators Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1576,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Dog Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1416,
+ "y": 344
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Crevice Right Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1032,
+ "y": 552
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Backrooms Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1144,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Burst Flowers Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1336,
+ "y": 376
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Crevice Left Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1352,
+ "y": 1832
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - Boss Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 936,
+ "y": 88
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - Roof Chest"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1352,
+ "y": 1688
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 448,
+ "y": 880
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1368,
+ "y": 872
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Floating Dustmaid Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 360,
+ "y": 1368
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Hallway Dustmaid Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 440,
+ "y": 1400
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Hallway Dustmaid Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 424,
+ "y": 632
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Moving Platform Crossroad 1"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1320,
+ "y": 1464
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Boss Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1368,
+ "y": 1032
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Gasguy Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1512,
+ "y": 1032
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1464,
+ "y": 1016
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1496,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1464,
+ "y": 1048
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Dustmaid and Steampipe Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1032,
+ "y": 40
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Steampipe Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1080,
+ "y": 40
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Steampipe Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1096,
+ "y": 1144
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 1F Locked Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1048,
+ "y": 456
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Dustmaid and Steampipe Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1032,
+ "y": 344
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Dustmaid and Steampipe Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1048,
+ "y": 376
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Dustmaid and Steampipe Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1480,
+ "y": 584
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 2F Dustmaid Hallway Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 248,
+ "y": 1640
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Stream Dustmaid Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 616,
+ "y": 1576
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 3F Bedroom Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 776,
+ "y": 936
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Slime Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 680,
+ "y": 824
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Slime Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 456,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Moving Platform Crossroad 2"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 616,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Dustmaid Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 376,
+ "y": 776
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Near Elevator Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 568,
+ "y": 1064
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Spring Puzzle Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 264,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Hotel - 4F Moving Platform Puzzle Dust"
+ }
+ ]
+ },
+ {
+ "name": "Hotel",
+ "map_locations": [
+ {
+ "map": "Hotel",
+ "x": 1356,
+ "y": 1661
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Manager"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Woods",
+ "children": [
+ {
+ "name": "Woods",
+ "map_locations": [
+ {
+ "map": "Woods",
+ "x": 184,
+ "y": 1544
+ }
+ ],
+ "sections": [
+ {
+ "name": "Woods - Near Gate Chest"
+ }
+ ]
+ },
+ {
+ "name": "Woods",
+ "map_locations": [
+ {
+ "map": "Woods",
+ "x": 920,
+ "y": 1240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Woods - After Temple Chest"
+ }
+ ]
+ },
+ {
+ "name": "Woods",
+ "map_locations": [
+ {
+ "map": "Woods",
+ "x": 264,
+ "y": 904
+ }
+ ],
+ "sections": [
+ {
+ "name": "Woods - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Woods",
+ "map_locations": [
+ {
+ "map": "Woods",
+ "x": 832,
+ "y": 1056
+ }
+ ],
+ "sections": [
+ {
+ "name": "Woods - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "children": [
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 24,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Slasher Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 552,
+ "y": 184
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Middle Cave Right Chest"
+ },
+ {
+ "name": "Red Grotto - Middle Cave Middle Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 520,
+ "y": 184
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Middle Cave Left Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 984,
+ "y": 264
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Boss Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 40,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Rapids Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1032,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Right Cave Slasher Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 936,
+ "y": 504
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Right Cave Four Shooter Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 280,
+ "y": 520
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Sticky Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 776,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Widen Upgrade Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 392,
+ "y": 1464
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Isaac Dungeon Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1032,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1096,
+ "y": 296
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Red Key"
+ },
+ {
+ "name": "Grab Red Key"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 840,
+ "y": 200
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Middle Cave Right Tentacle"
+ },
+ {
+ "name": "Center right tentacle hit"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 232,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Middle Cave Left Tentacle"
+ },
+ {
+ "name": "Center left tentacle hit"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 200,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Tentacle"
+ },
+ {
+ "name": "Left tentacle hit"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 872,
+ "y": 680
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Right Cave Tentacle"
+ },
+ {
+ "name": "Right tentacle hit"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1040,
+ "y": 368
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 984,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1096,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 888,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Before Boss Dust"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 72,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Slasher Dust"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 344,
+ "y": 72
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Before Slasher Dust"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1096,
+ "y": 136
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 984,
+ "y": 136
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Top Cave Boss Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 40,
+ "y": 568
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Rapids Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 40,
+ "y": 616
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Rapids Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1032,
+ "y": 712
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Right Cave Before Slasher Dust"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1080,
+ "y": 808
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Right Cave Whirlpool Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 392,
+ "y": 856
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Whirlpool Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 344,
+ "y": 888
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Left Cave Whirlpool Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 984,
+ "y": 920
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Grotto - Right Cave Whirlpool Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Red Grotto",
+ "map_locations": [
+ {
+ "map": "Red Grotto",
+ "x": 1056,
+ "y": 80
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Rogue"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Red Sea",
+ "children": [
+ {
+ "name": "Red Sea",
+ "map_locations": [
+ {
+ "map": "Red Sea",
+ "x": 392,
+ "y": 440
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Sea - Lonely Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Sea",
+ "map_locations": [
+ {
+ "map": "Red Sea",
+ "x": 40,
+ "y": 1032
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Sea - Out-of-bounds Chest"
+ }
+ ]
+ },
+ {
+ "name": "Red Sea",
+ "map_locations": [
+ {
+ "map": "Red Sea",
+ "x": 560,
+ "y": 384
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red Sea - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Young Town",
+ "children": [
+ {
+ "name": "Young Town",
+ "map_locations": [
+ {
+ "map": "Young Town",
+ "x": 264,
+ "y": 1192
+ }
+ ],
+ "sections": [
+ {
+ "name": "Young Town - Stab Reward Chest"
+ }
+ ]
+ },
+ {
+ "name": "Young Town",
+ "map_locations": [
+ {
+ "map": "Young Town",
+ "x": 392,
+ "y": 1240
+ }
+ ],
+ "sections": [
+ {
+ "name": "Young Town - Killers Chest"
+ }
+ ]
+ },
+ {
+ "name": "Young Town",
+ "map_locations": [
+ {
+ "map": "Young Town",
+ "x": 680,
+ "y": 408
+ }
+ ],
+ "sections": [
+ {
+ "name": "Young Town - Health Cicada"
+ }
+ ]
+ },
+ {
+ "name": "Young Town",
+ "map_locations": [
+ {
+ "map": "Young Town",
+ "x": 400,
+ "y": 544
+ }
+ ],
+ "sections": [
+ {
+ "name": "Young Town - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "children": [
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 88,
+ "y": 56
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Left Chest"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 1528,
+ "y": 40
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Right Chest"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 880,
+ "y": 1210
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Gauntlet Chest"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 504,
+ "y": 24
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Hidden Chest"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 880,
+ "y": 400
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 552,
+ "y": 872
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Challenge Area Dustmaid Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 568,
+ "y": 872
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Challenge Area Dustmaid Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 1144,
+ "y": 856
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Challenge Area Lion Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Space",
+ "map_locations": [
+ {
+ "map": "Space",
+ "x": 1144,
+ "y": 888
+ }
+ ],
+ "sections": [
+ {
+ "name": "Space - Challenge Area Lion Dust 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Street",
+ "children": [
+ {
+ "name": "Street",
+ "map_locations": [
+ {
+ "map": "Street",
+ "x": 24,
+ "y": 664
+ }
+ ],
+ "sections": [
+ {
+ "name": "Street - Key Chest"
+ }
+ ]
+ },
+ {
+ "name": "Street",
+ "map_locations": [
+ {
+ "map": "Street",
+ "x": 776,
+ "y": 680
+ }
+ ],
+ "sections": [
+ {
+ "name": "Street - Broom Chest"
+ }
+ ]
+ },
+ {
+ "name": "Street",
+ "map_locations": [
+ {
+ "map": "Street",
+ "x": 280,
+ "y": 1016
+ }
+ ],
+ "sections": [
+ {
+ "name": "Street - Secret Chest"
+ }
+ ]
+ },
+ {
+ "name": "Street",
+ "map_locations": [
+ {
+ "map": "Street",
+ "x": 442,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Street - After Bridge Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Street",
+ "map_locations": [
+ {
+ "map": "Street",
+ "x": 410,
+ "y": 232
+ }
+ ],
+ "sections": [
+ {
+ "name": "Street - After Bridge Dust 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Crossing",
+ "children": [
+ {
+ "name": "Crossing",
+ "map_locations": [
+ {
+ "map": "Crossing",
+ "x": 744,
+ "y": 392
+ }
+ ],
+ "sections": [
+ {
+ "name": "Crossing - Broken Bridge Chest"
+ }
+ ]
+ },
+ {
+ "name": "Crossing",
+ "map_locations": [
+ {
+ "map": "Crossing",
+ "x": 448,
+ "y": 736
+ }
+ ],
+ "sections": [
+ {
+ "name": "Crossing - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Crossing",
+ "map_locations": [
+ {
+ "map": "Crossing",
+ "x": 400,
+ "y": 522
+ }
+ ],
+ "sections": [
+ {
+ "name": "Defeat Sage"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Windmill",
+ "children": [
+ {
+ "name": "Windmill",
+ "map_locations": [
+ {
+ "map": "Windmill",
+ "x": 216,
+ "y": 376
+ }
+ ],
+ "sections": [
+ {
+ "name": "Windmill - Post Activation Chest"
+ },
+ {
+ "name": "Windmill - Activation"
+ },
+ {
+ "name": "Windmill activated"
+ }
+ ]
+ },
+ {
+ "name": "Windmill",
+ "map_locations": [
+ {
+ "map": "Windmill",
+ "x": 272,
+ "y": 1040
+ }
+ ],
+ "sections": [
+ {
+ "name": "Windmill - Warp Pad"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "children": [
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 376,
+ "y": 104
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Reward Chest"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 568,
+ "y": 1048
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Before Red Boss Dust"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 664,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Red Boss Dust 1"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 664,
+ "y": 1096
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Red Boss Dust 2"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 776,
+ "y": 1096
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Red Boss Dust 3"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 776,
+ "y": 984
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Red Boss Dust 4"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 40,
+ "y": 824
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Manager Phase 1 Dust"
+ }
+ ]
+ },
+ {
+ "name": "Boss Rush",
+ "map_locations": [
+ {
+ "map": "Boss Rush",
+ "x": 40,
+ "y": 1000
+ }
+ ],
+ "sections": [
+ {
+ "name": "Boss Rush - Manager Phase 2 Dust"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Blue",
+ "children": [
+ {
+ "name": "Blue",
+ "map_locations": [
+ {
+ "map": "Blue",
+ "x": 128,
+ "y": 128
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blue - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Blue",
+ "map_locations": [
+ {
+ "map": "Blue",
+ "x": 88,
+ "y": 40
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blue - Completion Reward"
+ }
+ ]
+ },
+ {
+ "name": "Blue",
+ "map_locations": [
+ {
+ "map": "Blue",
+ "x": 440,
+ "y": 136
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blue - Laser Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Blue",
+ "map_locations": [
+ {
+ "map": "Blue",
+ "x": 80,
+ "y": 32
+ }
+ ],
+ "sections": [
+ {
+ "name": "Blue Completion"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Red",
+ "children": [
+ {
+ "name": "Red",
+ "map_locations": [
+ {
+ "map": "Red",
+ "x": 264,
+ "y": 400
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red - Warp Pad"
+ }
+ ]
+ },
+ {
+ "name": "Red",
+ "map_locations": [
+ {
+ "map": "Red",
+ "x": 662,
+ "y": 186
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red - Completion Reward"
+ }
+ ]
+ },
+ {
+ "name": "Red",
+ "map_locations": [
+ {
+ "map": "Red",
+ "x": 616,
+ "y": 216
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red - Final Room Dust"
+ }
+ ]
+ },
+ {
+ "name": "Red",
+ "map_locations": [
+ {
+ "map": "Red",
+ "x": 184,
+ "y": 120
+ }
+ ],
+ "sections": [
+ {
+ "name": "Red - Dustmaid Dust"
+ }
+ ]
+ },
+ {
+ "name": "Red",
+ "map_locations": [
+ {
+ "map": "Red",
+ "x": 656,
+ "y": 176
+ }
+ ],
+ "sections": [
+ {
+ "name": "Happy Completion"
+ }
+ ]
+ }
+ ]
+ }
]
\ No newline at end of file
diff --git a/worlds/anodyne/tracker/maps.json b/worlds/anodyne/tracker/maps.json
index d4bad48b9dee..ab9f16fe6358 100644
--- a/worlds/anodyne/tracker/maps.json
+++ b/worlds/anodyne/tracker/maps.json
@@ -1,158 +1,158 @@
-[
- {
- "name": "Nexus",
- "img": "images/maps/NEXUS.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Apartment",
- "img": "images/maps/APARTMENT.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Beach",
- "img": "images/maps/BEACH.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Temple of the Seeing One",
- "img": "images/maps/BEDROOM.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Blank",
- "img": "images/maps/BLANK.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Blue",
- "img": "images/maps/BLUE.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Boss Rush",
- "img": "images/maps/BOSS_RUSH.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Cell",
- "img": "images/maps/CELL.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Circus",
- "img": "images/maps/CIRCUS.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Cliffs",
- "img": "images/maps/CLIFFS.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Mountain Cavern",
- "img": "images/maps/CROWD.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Debug",
- "img": "images/maps/DEBUG.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Drawer",
- "img": "images/maps/DRAWER.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Fields",
- "img": "images/maps/FIELDS.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Deep Forest",
- "img": "images/maps/FOREST.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Garden",
- "img": "images/maps/GO.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Red",
- "img": "images/maps/HAPPY.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Hotel",
- "img": "images/maps/HOTEL.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Woods",
- "img": "images/maps/OVERWORLD.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Red Grotto",
- "img": "images/maps/RED_CAVE.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Red Sea",
- "img": "images/maps/RED_SEA.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Space",
- "img": "images/maps/SPACE.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Street",
- "img": "images/maps/STREET.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Young Town",
- "img": "images/maps/SUBURB.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Crossing",
- "img": "images/maps/TERMINAL.png",
- "location_size": 20,
- "location_border_thickness": 1
- },
- {
- "name": "Windmill",
- "img": "images/maps/WINDMILL.png",
- "location_size": 20,
- "location_border_thickness": 1
- }
+[
+ {
+ "name": "Nexus",
+ "img": "images/maps/NEXUS.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Apartment",
+ "img": "images/maps/APARTMENT.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Beach",
+ "img": "images/maps/BEACH.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Temple of the Seeing One",
+ "img": "images/maps/BEDROOM.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Blank",
+ "img": "images/maps/BLANK.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Blue",
+ "img": "images/maps/BLUE.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Boss Rush",
+ "img": "images/maps/BOSS_RUSH.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Cell",
+ "img": "images/maps/CELL.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Circus",
+ "img": "images/maps/CIRCUS.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Cliffs",
+ "img": "images/maps/CLIFFS.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Mountain Cavern",
+ "img": "images/maps/CROWD.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Debug",
+ "img": "images/maps/DEBUG.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Drawer",
+ "img": "images/maps/DRAWER.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Fields",
+ "img": "images/maps/FIELDS.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Deep Forest",
+ "img": "images/maps/FOREST.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Garden",
+ "img": "images/maps/GO.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Red",
+ "img": "images/maps/HAPPY.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Hotel",
+ "img": "images/maps/HOTEL.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Woods",
+ "img": "images/maps/OVERWORLD.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Red Grotto",
+ "img": "images/maps/RED_CAVE.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Red Sea",
+ "img": "images/maps/RED_SEA.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Space",
+ "img": "images/maps/SPACE.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Street",
+ "img": "images/maps/STREET.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Young Town",
+ "img": "images/maps/SUBURB.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Crossing",
+ "img": "images/maps/TERMINAL.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ },
+ {
+ "name": "Windmill",
+ "img": "images/maps/WINDMILL.png",
+ "location_size": 20,
+ "location_border_thickness": 1
+ }
]
\ No newline at end of file
diff --git a/worlds/apquest/!READ_FIRST!.txt b/worlds/apquest/!READ_FIRST!.txt
new file mode 100644
index 000000000000..ff3d5fe51060
--- /dev/null
+++ b/worlds/apquest/!READ_FIRST!.txt
@@ -0,0 +1,46 @@
+This apworld is meant as a learning tool for new apworld devs.
+It is a completely standalone resource, but there will be links to additional resources when appropriate.
+
+#################
+# Prerequisites #
+#################
+
+APQuest will only explain how to write the generation-side code for your game, not how to write a client or mod for it.
+For a more zoomed out view of how to add a game to Archipelago, you can read this document:
+https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/adding%20games.md
+
+APQuest assumes you already vaguely know what an apworld is.
+If you don't know, read this first:
+https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/apworld%20specification.md
+
+To write an apworld, you need to be running Archipelago from source (Python) instead of using e.g. the .exe build.
+Here's an explanation for how to do that.
+https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/running%20from%20source.md
+
+#######################
+# How to read APQuest #
+#######################
+
+You'll want to start with __init__.py, then move to world.py.
+If you also want to learn how to write unit tests, go to test/__init__.py.
+
+You can ignore the game/ folder, it contains the actual game code, graphics and music.
+
+The client/ folder is NOT meant for teaching.
+While the client was written to the best of its author's ability, it does not meet the same standard as the world code.
+The client code is also lacking the explanatory comments.
+Copy from it at your own risk.
+
+###################
+# Further reading #
+###################
+
+APQuest is a very simple game, so not every edge case will be covered.
+The world API document goes a lot more in-depth on certain topics:
+https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md
+
+There is also the "APWorld dev FAQ" document with common emergent problems:
+https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/apworld_dev_faq.md
+
+In general, but especially if you want your apworld to be verified by core, you should follow our style guide:
+https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/style.md
diff --git a/worlds/apquest/__init__.py b/worlds/apquest/__init__.py
new file mode 100644
index 000000000000..6ef396ee90ab
--- /dev/null
+++ b/worlds/apquest/__init__.py
@@ -0,0 +1,12 @@
+# The first thing you should make for your world is an archipelago.json manifest file.
+# You can reference APQuest's, but you should change the "game" field (obviously),
+# and you should also change the "minimum_ap_version" - probably to the current value of Utils.__version__.
+
+# Apart from the regular apworld code that allows generating multiworld seeds with your game,
+# your apworld might have other "components" that should be launchable from the Archipelago Launcher.
+# You can ignore this for now. If you are specifically interested in components, you can read components.py.
+from . import components as components
+
+# The main thing we do in our __init__.py is importing our world class from our world.py to initialize it.
+# Obviously, this world class needs to exist first. For this, read world.py.
+from .world import APQuestWorld as APQuestWorld
diff --git a/worlds/apquest/archipelago.json b/worlds/apquest/archipelago.json
new file mode 100644
index 000000000000..6a6c3ddd02c3
--- /dev/null
+++ b/worlds/apquest/archipelago.json
@@ -0,0 +1,6 @@
+{
+ "game": "APQuest",
+ "minimum_ap_version": "0.6.4",
+ "world_version": "1.0.1",
+ "authors": ["NewSoupVi"]
+}
diff --git a/worlds/apquest/client/__init__.py b/worlds/apquest/client/__init__.py
new file mode 100644
index 000000000000..0cd1b86a4986
--- /dev/null
+++ b/worlds/apquest/client/__init__.py
@@ -0,0 +1,5 @@
+# !!! IMPORTANT !!!
+# The client implementation is *not* meant for teaching.
+# Obviously, it is written to the best of its author's abilities,
+# but it is not to the same standard as the rest of the apworld.
+# Copy things from here at your own risk.
diff --git a/worlds/apquest/client/ap_quest_client.kv b/worlds/apquest/client/ap_quest_client.kv
new file mode 100644
index 000000000000..9f4024e62a02
--- /dev/null
+++ b/worlds/apquest/client/ap_quest_client.kv
@@ -0,0 +1,56 @@
+:
+ size_hint: None, None
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+ spacing: 0
+ padding: 0
+
+:
+ cols: 12
+ rows: 11
+ spacing: 0
+ padding: 0
+ size_hint: None, None
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+
+:
+ RelativeLayout:
+ id: game_container
+
+:
+ Label:
+ markup: True
+ font_size: "20sp"
+ valign: "middle"
+ pos_hint: {"center_x": 0.5, "center_y": 0.5}
+ text:
+ """[b]Controls:[/b]
+
+ WASD or Arrow Keys to move
+ Space to attack or interact
+ C to fire available Confetti Cannons
+ Number Keys + Backspace for Math Trap\n
+
+ Rebinding controls might be added in the future :)"""
+
+:
+ orientation: "horizontal"
+ size_hint: 1, None
+ padding: 0
+ height: 50
+
+ Label:
+ size_hint: None, 1
+ text: "Volume:"
+
+ Slider:
+ id: volume_slider
+ size_hint: 1, 1
+ min: 0
+ max: 100
+ step: 1
+ value: 50
+ orientation: "horizontal"
+
+ Label:
+ size_hint: None, 1
+ text: str(int(volume_slider.value))
diff --git a/worlds/apquest/client/ap_quest_client.py b/worlds/apquest/client/ap_quest_client.py
new file mode 100644
index 000000000000..c1edc8edb4b2
--- /dev/null
+++ b/worlds/apquest/client/ap_quest_client.py
@@ -0,0 +1,290 @@
+import asyncio
+import sys
+from argparse import Namespace
+from enum import Enum
+from typing import TYPE_CHECKING, Any
+
+from CommonClient import CommonContext, gui_enabled, logger, server_loop
+from NetUtils import ClientStatus
+
+from ..game.events import ConfettiFired, LocationClearedEvent, MathProblemSolved, MathProblemStarted, VictoryEvent
+from ..game.game import Game
+from ..game.inputs import Input
+from ..game.items import Item
+from ..game.locations import Location
+from .game_manager import APQuestManager
+from .graphics import PlayerSprite
+from .item_quality import get_quality_for_network_item
+from .sounds import (
+ CONFETTI_CANNON,
+ ITEM_JINGLES,
+ MATH_PROBLEM_SOLVED_JINGLE,
+ MATH_PROBLEM_STARTED_JINGLE,
+ VICTORY_JINGLE,
+)
+
+if TYPE_CHECKING:
+ import kvui
+
+
+# !!! IMPORTANT !!!
+# The client implementation is *not* meant for teaching.
+# Obviously, it is written to the best of its author's abilities,
+# but it is not to the same standard as the rest of the apworld.
+# Copy things from here at your own risk.
+
+
+class ConnectionStatus(Enum):
+ NOT_CONNECTED = 0
+ SCOUTS_NOT_SENT = 1
+ SCOUTS_SENT = 2
+ GAME_RUNNING = 3
+
+
+class APQuestContext(CommonContext):
+ game = "APQuest"
+ items_handling = 0b111 # full remote
+
+ client_loop: asyncio.Task[None]
+
+ last_connected_slot: int | None = None
+
+ slot_data: dict[str, Any]
+
+ ap_quest_game: Game | None = None
+ hard_mode: bool = False
+ hammer: bool = False
+ extra_starting_chest: bool = False
+ player_sprite: PlayerSprite = PlayerSprite.HUMAN
+
+ connection_status: ConnectionStatus = ConnectionStatus.NOT_CONNECTED
+
+ highest_processed_item_index: int = 0
+ queued_locations: list[int]
+
+ delay_intro_song: bool
+
+ ui: APQuestManager
+
+ def __init__(
+ self, server_address: str | None = None, password: str | None = None, delay_intro_song: bool = False
+ ) -> None:
+ super().__init__(server_address, password)
+
+ self.queued_locations = []
+ self.slot_data = {}
+ self.delay_intro_song = delay_intro_song
+
+ async def server_auth(self, password_requested: bool = False) -> None:
+ if password_requested and not self.password:
+ self.ui.allow_intro_song()
+ await super().server_auth(password_requested)
+ await self.get_username()
+ await self.send_connect(game=self.game)
+
+ def handle_connection_loss(self, msg: str) -> None:
+ self.ui.allow_intro_song()
+ super().handle_connection_loss(msg)
+
+ async def connect(self, address: str | None = None) -> None:
+ self.ui.switch_to_regular_tab()
+ await super().connect(address)
+
+ async def apquest_loop(self) -> None:
+ while not self.exit_event.is_set():
+ if self.connection_status != ConnectionStatus.GAME_RUNNING:
+ if self.connection_status == ConnectionStatus.SCOUTS_NOT_SENT:
+ await self.send_msgs([{"cmd": "LocationScouts", "locations": self.server_locations}])
+ self.connection_status = ConnectionStatus.SCOUTS_SENT
+
+ await asyncio.sleep(0.1)
+ continue
+
+ if not self.ap_quest_game or not self.ap_quest_game.gameboard or not self.ap_quest_game.gameboard.ready:
+ await asyncio.sleep(0.1)
+ continue
+
+ try:
+ while self.queued_locations:
+ location = self.queued_locations.pop(0)
+ self.location_checked_side_effects(location)
+ self.locations_checked.add(location)
+ await self.check_locations({location})
+
+ rerender = False
+
+ new_items = self.items_received[self.highest_processed_item_index :]
+ for item in new_items:
+ self.highest_processed_item_index += 1
+ self.ap_quest_game.receive_item(item.item, item.location, item.player)
+ rerender = True
+
+ for new_remotely_cleared_location in self.checked_locations - self.locations_checked:
+ self.ap_quest_game.force_clear_location(new_remotely_cleared_location)
+ rerender = True
+
+ if rerender:
+ self.render()
+
+ if self.ap_quest_game.player.has_won and not self.finished_game:
+ await self.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
+ self.finished_game = True
+ except Exception as e:
+ logger.exception(e)
+
+ await asyncio.sleep(0.1)
+
+ def on_package(self, cmd: str, args: dict[str, Any]) -> None:
+ if cmd == "ConnectionRefused":
+ self.ui.allow_intro_song()
+
+ if cmd == "Connected":
+ if self.connection_status == ConnectionStatus.GAME_RUNNING:
+ # In a connection loss -> auto reconnect scenario, we can seamlessly keep going
+ return
+
+ self.last_connected_slot = self.slot
+
+ self.connection_status = ConnectionStatus.NOT_CONNECTED # for safety, it will get set again later
+
+ self.slot_data = args["slot_data"]
+ self.hard_mode = self.slot_data["hard_mode"]
+ self.hammer = self.slot_data["hammer"]
+ self.extra_starting_chest = self.slot_data["extra_starting_chest"]
+ try:
+ self.player_sprite = PlayerSprite(self.slot_data["player_sprite"])
+ except Exception as e:
+ logger.exception(e)
+ self.player_sprite = PlayerSprite.UNKNOWN
+
+ self.ap_quest_game = Game(self.hard_mode, self.hammer, self.extra_starting_chest)
+ self.highest_processed_item_index = 0
+ self.render()
+
+ self.connection_status = ConnectionStatus.SCOUTS_NOT_SENT
+ if cmd == "LocationInfo":
+ remote_item_graphic_overrides = {
+ Location(location): Item(network_item.item)
+ for location, network_item in self.locations_info.items()
+ if self.slot_info[network_item.player].game == self.game
+ }
+
+ assert self.ap_quest_game is not None
+ self.ap_quest_game.gameboard.fill_remote_location_content(remote_item_graphic_overrides)
+ self.render()
+ self.ui.game_view.bind_keyboard()
+
+ self.connection_status = ConnectionStatus.GAME_RUNNING
+ self.ui.game_started()
+
+ async def disconnect(self, *args: Any, **kwargs: Any) -> None:
+ self.finished_game = False
+ self.locations_checked = set()
+ self.connection_status = ConnectionStatus.NOT_CONNECTED
+ await super().disconnect(*args, **kwargs)
+
+ def render(self) -> None:
+ if self.ap_quest_game is None:
+ raise RuntimeError("Tried to render before self.ap_quest_game was initialized.")
+
+ self.ui.render(self.ap_quest_game, self.player_sprite)
+ self.handle_game_events()
+
+ def location_checked_side_effects(self, location: int) -> None:
+ network_item = self.locations_info[location]
+
+ if network_item.player == self.slot and network_item.item == Item.MATH_TRAP.value:
+ # In case of a local math trap, we only play the math trap trigger jingle
+ return
+
+ item_quality = get_quality_for_network_item(network_item)
+ self.play_jingle(ITEM_JINGLES[item_quality])
+
+ def play_jingle(self, audio_filename: str) -> None:
+ self.ui.play_jingle(audio_filename)
+
+ def handle_game_events(self) -> None:
+ if self.ap_quest_game is None:
+ return
+
+ while self.ap_quest_game.queued_events:
+ event = self.ap_quest_game.queued_events.pop(0)
+
+ if isinstance(event, LocationClearedEvent):
+ self.queued_locations.append(event.location_id)
+ continue
+
+ if isinstance(event, VictoryEvent):
+ self.play_jingle(VICTORY_JINGLE)
+ continue
+
+ if isinstance(event, ConfettiFired):
+ gameboard_x, gameboard_y = self.ap_quest_game.gameboard.size
+ gameboard_x += 1 # vertical item column
+ x = (event.x + 0.5) / gameboard_x
+ y = 1 - (event.y + 0.5) / gameboard_y # Kivy's y is bottom to top (ew)
+
+ self.ui.play_jingle(CONFETTI_CANNON)
+ self.ui.add_confetti((x, y), (self.slot_data["confetti_explosiveness"] + 1) * 5)
+ continue
+
+ if isinstance(event, MathProblemStarted):
+ self.play_jingle(MATH_PROBLEM_STARTED_JINGLE)
+ continue
+
+ if isinstance(event, MathProblemSolved):
+ self.play_jingle(MATH_PROBLEM_SOLVED_JINGLE)
+ continue
+
+ def input_and_rerender(self, input_key: Input) -> None:
+ if self.ap_quest_game is None:
+ return
+ if not self.ap_quest_game.gameboard.ready:
+ return
+ self.ap_quest_game.input(input_key)
+ self.render()
+
+ def make_gui(self) -> "type[kvui.GameManager]":
+ self.load_kv()
+ return APQuestManager
+
+ def load_kv(self) -> None:
+ import pkgutil
+
+ from kivy.lang import Builder
+
+ data = pkgutil.get_data(__name__, "ap_quest_client.kv")
+ if data is None:
+ raise RuntimeError("ap_quest_client.kv could not be loaded.")
+
+ Builder.load_string(data.decode())
+
+
+async def main(args: Namespace) -> None:
+ if not gui_enabled:
+ raise RuntimeError("APQuest cannot be played without gui.")
+
+ # Assume we shouldn't play the intro song in the auto-connect scenario, because the game will instantly start.
+ delay_intro_song = args.connect and args.name
+
+ ctx = APQuestContext(args.connect, args.password, delay_intro_song=delay_intro_song)
+ ctx.auth = args.name
+ ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
+
+ ctx.run_gui()
+ ctx.run_cli()
+
+ ctx.client_loop = asyncio.create_task(ctx.apquest_loop(), name="Client Loop")
+
+ await ctx.exit_event.wait()
+ await ctx.shutdown()
+
+
+def launch(*args: str) -> None:
+ from .launch import launch_ap_quest_client
+
+ launch_ap_quest_client(*args)
+
+
+if __name__ == "__main__":
+ launch(*sys.argv[1:])
diff --git a/worlds/apquest/client/custom_views.py b/worlds/apquest/client/custom_views.py
new file mode 100644
index 000000000000..4c1d28c73250
--- /dev/null
+++ b/worlds/apquest/client/custom_views.py
@@ -0,0 +1,256 @@
+from collections.abc import Callable
+from dataclasses import dataclass
+from math import sqrt
+from random import choice, random
+from typing import Any
+
+from kivy.core.window import Keyboard, Window
+from kivy.graphics import Color, Triangle
+from kivy.graphics.instructions import Canvas
+from kivy.input import MotionEvent
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.gridlayout import GridLayout
+from kivymd.uix.recycleview import MDRecycleView
+
+from CommonClient import logger
+
+from ..game.inputs import Input
+
+
+INPUT_MAP = {
+ "up": Input.UP,
+ "w": Input.UP,
+ "down": Input.DOWN,
+ "s": Input.DOWN,
+ "right": Input.RIGHT,
+ "d": Input.RIGHT,
+ "left": Input.LEFT,
+ "a": Input.LEFT,
+ "spacebar": Input.ACTION,
+ "c": Input.CONFETTI,
+ "0": Input.ZERO,
+ "1": Input.ONE,
+ "2": Input.TWO,
+ "3": Input.THREE,
+ "4": Input.FOUR,
+ "5": Input.FIVE,
+ "6": Input.SIX,
+ "7": Input.SEVEN,
+ "8": Input.EIGHT,
+ "9": Input.NINE,
+ "backspace": Input.BACKSPACE,
+}
+
+
+class APQuestGameView(MDRecycleView):
+ _keyboard: Keyboard | None = None
+ input_function: Callable[[Input], None]
+
+ def __init__(self, input_function: Callable[[Input], None], **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.input_function = input_function
+ self.bind_keyboard()
+
+ def on_touch_down(self, touch: MotionEvent) -> None:
+ self.bind_keyboard()
+
+ def bind_keyboard(self) -> None:
+ if self._keyboard is not None:
+ return
+ self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
+ self._keyboard.bind(on_key_down=self._on_keyboard_down)
+
+ def _keyboard_closed(self) -> None:
+ if self._keyboard is None:
+ return
+ self._keyboard.unbind(on_key_down=self._on_keyboard_down)
+ self._keyboard = None
+
+ def _on_keyboard_down(self, _: Any, keycode: tuple[int, str], _1: Any, _2: Any) -> bool:
+ if keycode[1] in INPUT_MAP:
+ self.input_function(INPUT_MAP[keycode[1]])
+ return True
+
+
+class APQuestGrid(GridLayout):
+ def check_resize(self, _: int, _1: int) -> None:
+ parent_width, parent_height = self.parent.size
+
+ self_width_according_to_parent_height = parent_height * 12 / 11
+ self_height_according_to_parent_width = parent_height * 11 / 12
+
+ if self_width_according_to_parent_height > parent_width:
+ self.size = parent_width, self_height_according_to_parent_width
+ else:
+ self.size = self_width_according_to_parent_height, parent_height
+
+
+CONFETTI_COLORS = [
+ (220 / 255, 0, 212 / 255), # PINK
+ (0, 0, 252 / 255), # BLUE
+ (252 / 255, 220 / 255, 0), # YELLOW
+ (0, 184 / 255, 0), # GREEN
+ (252 / 255, 56 / 255, 0), # ORANGE
+]
+
+
+@dataclass
+class Confetti:
+ x_pos: float
+ y_pos: float
+ x_speed: float
+ y_speed: float
+ color: tuple[float, float, float]
+ life: float = 3
+
+ triangle1: Triangle | None = None
+ triangle2: Triangle | None = None
+ color_instruction: Color | None = None
+
+ def update_speed(self, dt: float) -> None:
+ if self.x_speed > 0:
+ self.x_speed -= 2.7 * dt
+ if self.x_speed < 0:
+ self.x_speed = 0
+ else:
+ self.x_speed += 2.7 * dt
+ if self.x_speed > 0:
+ self.x_speed = 0
+
+ if self.y_speed > -0.03:
+ self.y_speed -= 2.7 * dt
+ if self.y_speed < -0.03:
+ self.y_speed = -0.03
+ else:
+ self.y_speed += 2.7 * dt
+ if self.y_speed > -0.03:
+ self.y_speed = -0.03
+
+ def move(self, dt: float) -> None:
+ self.update_speed(dt)
+
+ if self.y_pos > 1:
+ self.y_pos = 1
+ self.y_speed = 0
+ if self.x_pos < 0.01:
+ self.x_pos = 0.01
+ self.x_speed = 0
+ if self.x_pos > 0.99:
+ self.x_pos = 0.99
+ self.x_speed = 0
+
+ self.x_pos += self.x_speed * dt
+ self.y_pos += self.y_speed * dt
+
+ def render(self, offset_x: float, offset_y: float, max_x: int, max_y: int) -> None:
+ if self.x_speed == 0 and self.y_speed == 0:
+ x_normalized, y_normalized = 0.0, 1.0
+ else:
+ speed_magnitude = sqrt(self.x_speed**2 + self.y_speed**2)
+ x_normalized, y_normalized = self.x_speed / speed_magnitude, self.y_speed / speed_magnitude
+
+ half_top_to_bottom = 0.006
+ half_left_to_right = 0.018
+
+ upwards_delta_x = x_normalized * half_top_to_bottom
+ upwards_delta_y = y_normalized * half_top_to_bottom
+ sideways_delta_x = y_normalized * half_left_to_right
+ sideways_delta_y = x_normalized * half_left_to_right
+
+ top_left_x, top_left_y = upwards_delta_x - sideways_delta_x, upwards_delta_y + sideways_delta_y
+ bottom_left_x, bottom_left_y = -upwards_delta_x - sideways_delta_x, -upwards_delta_y + sideways_delta_y
+ top_right_x, top_right_y = -bottom_left_x, -bottom_left_y
+ bottom_right_x, bottom_right_y = -top_left_x, -top_left_y
+
+ top_left_x, top_left_y = top_left_x + self.x_pos, top_left_y + self.y_pos
+ bottom_left_x, bottom_left_y = bottom_left_x + self.x_pos, bottom_left_y + self.y_pos
+ top_right_x, top_right_y = top_right_x + self.x_pos, top_right_y + self.y_pos
+ bottom_right_x, bottom_right_y = bottom_right_x + self.x_pos, bottom_right_y + self.y_pos
+
+ top_left_x, top_left_y = top_left_x * max_x + offset_x, top_left_y * max_y + offset_y
+ bottom_left_x, bottom_left_y = bottom_left_x * max_x + offset_x, bottom_left_y * max_y + offset_y
+ top_right_x, top_right_y = top_right_x * max_x + offset_x, top_right_y * max_y + offset_y
+ bottom_right_x, bottom_right_y = bottom_right_x * max_x + offset_x, bottom_right_y * max_y + offset_y
+
+ points1 = (top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y)
+ points2 = (bottom_right_x, bottom_right_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y)
+
+ if self.color_instruction is None:
+ self.color_instruction = Color(*self.color)
+
+ if self.triangle1 is None:
+ self.triangle1 = Triangle(points=points1)
+ else:
+ self.triangle1.points = points1
+
+ if self.triangle2 is None:
+ self.triangle2 = Triangle(points=points2)
+ else:
+ self.triangle2.points = points2
+
+ def reduce_life(self, dt: float, canvas: Canvas) -> bool:
+ self.life -= dt
+
+ if self.life <= 0:
+ if self.color_instruction is not None:
+ canvas.remove(self.color_instruction)
+ if self.triangle1 is not None:
+ canvas.remove(self.triangle1)
+ if self.triangle2 is not None:
+ canvas.remove(self.triangle2)
+ return False
+
+ return True
+
+
+class ConfettiView(MDRecycleView):
+ confetti: list[Confetti]
+
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(**kwargs)
+ self.confetti = []
+
+ def check_resize(self, _: int, _1: int) -> None:
+ parent_width, parent_height = self.parent.size
+
+ self_width_according_to_parent_height = parent_height * 12 / 11
+ self_height_according_to_parent_width = parent_height * 11 / 12
+
+ if self_width_according_to_parent_height > parent_width:
+ self.size = parent_width, self_height_according_to_parent_width
+ else:
+ self.size = self_width_according_to_parent_height, parent_height
+
+ def redraw_confetti(self, dt: float) -> None:
+ try:
+ with self.canvas:
+ for confetti in self.confetti:
+ confetti.move(dt)
+
+ self.confetti = [confetti for confetti in self.confetti if confetti.reduce_life(dt, self.canvas)]
+
+ for confetti in self.confetti:
+ confetti.render(self.pos[0], self.pos[1], self.size[0], self.size[1])
+ except Exception as e:
+ logger.exception(e)
+
+ def add_confetti(self, initial_position: tuple[float, float], amount: int) -> None:
+ for i in range(amount):
+ self.confetti.append(
+ Confetti(
+ initial_position[0],
+ initial_position[1],
+ random() * 3.2 - 1.6 - (initial_position[0] - 0.5) * 1.2,
+ random() * 3.2 - 1.3 - (initial_position[1] - 0.5) * 1.2,
+ choice(CONFETTI_COLORS),
+ 3 + i * 0.05,
+ )
+ )
+
+
+class VolumeSliderView(BoxLayout):
+ pass
+
+
+class APQuestControlsView(BoxLayout):
+ pass
diff --git a/worlds/apquest/client/game_manager.py b/worlds/apquest/client/game_manager.py
new file mode 100644
index 000000000000..86f4316d12a2
--- /dev/null
+++ b/worlds/apquest/client/game_manager.py
@@ -0,0 +1,200 @@
+from __future__ import annotations
+
+# isort: off
+from kvui import GameManager, MDNavigationItemBase
+
+# isort: on
+from typing import TYPE_CHECKING, Any
+
+from kivy.clock import Clock
+from kivy.uix.gridlayout import GridLayout
+from kivy.uix.image import Image
+from kivy.uix.layout import Layout
+from kivymd.uix.recycleview import MDRecycleView
+
+from ..game.game import Game
+from .custom_views import APQuestControlsView, APQuestGameView, APQuestGrid, ConfettiView, VolumeSliderView
+from .graphics import PlayerSprite, get_texture
+from .sounds import SoundManager
+
+if TYPE_CHECKING:
+ from .ap_quest_client import APQuestContext
+
+
+class APQuestManager(GameManager):
+ base_title = "APQuest for AP version"
+ ctx: APQuestContext
+
+ lower_game_grid: GridLayout
+ upper_game_grid: GridLayout
+
+ game_view: MDRecycleView
+ game_view_tab: MDNavigationItemBase
+
+ sound_manager: SoundManager
+
+ bottom_image_grid: list[list[Image]]
+ top_image_grid: list[list[Image]]
+ confetti_view: ConfettiView
+
+ bottom_grid_is_grass: bool
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+ self.sound_manager = SoundManager()
+ self.sound_manager.allow_intro_to_play = not self.ctx.delay_intro_song
+ self.top_image_grid = []
+ self.bottom_image_grid = []
+ self.bottom_grid_is_grass = False
+
+ def allow_intro_song(self) -> None:
+ self.sound_manager.allow_intro_to_play = True
+
+ def add_confetti(self, position: tuple[float, float], amount: int) -> None:
+ self.confetti_view.add_confetti(position, amount)
+
+ def play_jingle(self, audio_filename: str) -> None:
+ self.sound_manager.play_jingle(audio_filename)
+
+ def switch_to_tab(self, desired_tab: MDNavigationItemBase) -> None:
+ if self.screens.current_tab == desired_tab:
+ return
+ self.screens.current_tab.active = False
+ self.screens.switch_screens(desired_tab)
+ desired_tab.active = True
+
+ def switch_to_game_tab(self) -> None:
+ self.switch_to_tab(self.game_view_tab)
+
+ def switch_to_regular_tab(self) -> None:
+ self.switch_to_tab(self.tabs.children[-1])
+
+ def game_started(self) -> None:
+ self.switch_to_game_tab()
+ self.sound_manager.game_started = True
+
+ def render(self, game: Game, player_sprite: PlayerSprite) -> None:
+ self.setup_game_grid_if_not_setup(game.gameboard.size)
+
+ # This calls game.render(), which needs to happen to update the state of math traps
+ self.render_gameboard(game, player_sprite)
+ # Only now can we check whether a math problem is active
+ self.render_background_game_grid(game.gameboard.size, game.active_math_problem is None)
+ self.sound_manager.math_trap_active = game.active_math_problem is not None
+
+ self.render_item_column(game)
+
+ def render_gameboard(self, game: Game, player_sprite: PlayerSprite) -> None:
+ rendered_gameboard = game.render()
+
+ for gameboard_row, image_row in zip(rendered_gameboard, self.top_image_grid, strict=False):
+ for graphic, image in zip(gameboard_row, image_row[:11], strict=False):
+ texture = get_texture(graphic, player_sprite)
+
+ if texture is None:
+ image.opacity = 0
+ image.texture = None
+ continue
+
+ image.texture = texture
+ image.opacity = 1
+
+ def render_item_column(self, game: Game) -> None:
+ rendered_item_column = game.render_health_and_inventory(vertical=True)
+ for item_graphic, image_row in zip(rendered_item_column, self.top_image_grid, strict=False):
+ image = image_row[-1]
+
+ texture = get_texture(item_graphic)
+ if texture is None:
+ image.opacity = 0
+ image.texture = None
+ continue
+
+ image.texture = texture
+ image.opacity = 1
+
+ def render_background_game_grid(self, size: tuple[int, int], grass: bool) -> None:
+ if grass == self.bottom_grid_is_grass:
+ return
+
+ for row in range(size[1]):
+ for column in range(size[0]):
+ image = self.bottom_image_grid[row][column]
+
+ if not grass:
+ image.color = (0.3, 0.3, 0.3)
+ image.texture = None
+ continue
+
+ boss_room = (row in (0, 1, 2) and (size[1] - column) in (1, 2, 3)) or (row, column) == (3, size[1] - 2)
+ if boss_room:
+ image.color = (0.45, 0.35, 0.1)
+ image.texture = None
+ continue
+ image.texture = get_texture("Grass")
+ image.color = (1.0, 1.0, 1.0)
+
+ self.bottom_grid_is_grass = grass
+
+ def setup_game_grid_if_not_setup(self, size: tuple[int, int]) -> None:
+ if self.upper_game_grid.children:
+ return
+
+ self.top_image_grid = []
+ self.bottom_image_grid = []
+
+ for _row in range(size[1]):
+ self.top_image_grid.append([])
+ self.bottom_image_grid.append([])
+
+ for _column in range(size[0]):
+ bottom_image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
+ self.lower_game_grid.add_widget(bottom_image)
+ self.bottom_image_grid[-1].append(bottom_image)
+
+ top_image = Image(fit_mode="fill")
+ self.upper_game_grid.add_widget(top_image)
+ self.top_image_grid[-1].append(top_image)
+
+ # Right side: Inventory
+ image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
+ self.lower_game_grid.add_widget(image)
+
+ image2 = Image(fit_mode="fill", opacity=0)
+ self.upper_game_grid.add_widget(image2)
+
+ self.top_image_grid[-1].append(image2)
+
+ def build(self) -> Layout:
+ container = super().build()
+
+ self.game_view = APQuestGameView(self.ctx.input_and_rerender)
+
+ self.game_view_tab = self.add_client_tab("APQuest", self.game_view)
+
+ controls = APQuestControlsView()
+
+ self.add_client_tab("Controls", controls)
+
+ game_container = self.game_view.ids["game_container"]
+ self.lower_game_grid = APQuestGrid()
+ self.upper_game_grid = APQuestGrid()
+ self.confetti_view = ConfettiView()
+ game_container.add_widget(self.lower_game_grid)
+ game_container.add_widget(self.upper_game_grid)
+ game_container.add_widget(self.confetti_view)
+
+ game_container.bind(size=self.lower_game_grid.check_resize)
+ game_container.bind(size=self.upper_game_grid.check_resize)
+ game_container.bind(size=self.confetti_view.check_resize)
+
+ volume_slider_container = VolumeSliderView()
+ volume_slider = volume_slider_container.ids["volume_slider"]
+ volume_slider.value = self.sound_manager.volume_percentage
+ volume_slider.bind(value=lambda _, new_volume: self.sound_manager.set_volume_percentage(new_volume))
+
+ self.grid.add_widget(volume_slider_container, index=3)
+
+ Clock.schedule_interval(lambda dt: self.confetti_view.redraw_confetti(dt), 1 / 60)
+
+ return container
diff --git a/worlds/apquest/client/graphics.py b/worlds/apquest/client/graphics.py
new file mode 100644
index 000000000000..535bf84ee852
--- /dev/null
+++ b/worlds/apquest/client/graphics.py
@@ -0,0 +1,180 @@
+import pkgutil
+from collections.abc import Buffer
+from enum import Enum
+from io import BytesIO
+from typing import Literal, NamedTuple, Protocol, cast
+
+from kivy.uix.image import CoreImage
+
+from CommonClient import logger
+
+from .. import game
+from ..game.graphics import Graphic
+
+
+# The import "from kivy.graphics.texture import Texture" does not work correctly.
+# We never need the class directly, so we need to use a protocol.
+class Texture(Protocol):
+ mag_filter: Literal["nearest"]
+
+ def get_region(self, x: int, y: int, w: int, h: int) -> "Texture": ...
+
+
+class RelatedTexture(NamedTuple):
+ base_texture_file: str
+ x: int
+ y: int
+ width: int
+ height: int
+
+
+IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
+ Graphic.WALL: RelatedTexture("inanimates.png", 16, 32, 16, 16),
+ Graphic.BREAKABLE_BLOCK: RelatedTexture("inanimates.png", 32, 32, 16, 16),
+ Graphic.CHEST: RelatedTexture("inanimates.png", 0, 16, 16, 16),
+ Graphic.BUSH: RelatedTexture("inanimates.png", 16, 16, 16, 16),
+ Graphic.KEY_DOOR: RelatedTexture("inanimates.png", 32, 16, 16, 16),
+ Graphic.BUTTON_NOT_ACTIVATED: RelatedTexture("inanimates.png", 0, 0, 16, 16),
+ Graphic.BUTTON_ACTIVATED: RelatedTexture("inanimates.png", 16, 0, 16, 16),
+ Graphic.BUTTON_DOOR: RelatedTexture("inanimates.png", 32, 0, 16, 16),
+
+ Graphic.NORMAL_ENEMY_1_HEALTH: RelatedTexture("normal_enemy.png", 0, 0, 16, 16),
+ Graphic.NORMAL_ENEMY_2_HEALTH: RelatedTexture("normal_enemy.png", 16, 0, 16, 16),
+
+ Graphic.BOSS_5_HEALTH: RelatedTexture("boss.png", 16, 16, 16, 16),
+ Graphic.BOSS_4_HEALTH: RelatedTexture("boss.png", 0, 16, 16, 16),
+ Graphic.BOSS_3_HEALTH: RelatedTexture("boss.png", 32, 32, 16, 16),
+ Graphic.BOSS_2_HEALTH: RelatedTexture("boss.png", 16, 32, 16, 16),
+ Graphic.BOSS_1_HEALTH: RelatedTexture("boss.png", 0, 32, 16, 16),
+
+ Graphic.EMPTY_HEART: RelatedTexture("hearts.png", 0, 0, 16, 16),
+ Graphic.HEART: RelatedTexture("hearts.png", 16, 0, 16, 16),
+ Graphic.HALF_HEART: RelatedTexture("hearts.png", 32, 0, 16, 16),
+
+ Graphic.REMOTE_ITEM: RelatedTexture("items.png", 0, 16, 16, 16),
+ Graphic.CONFETTI_CANNON: RelatedTexture("items.png", 16, 16, 16, 16),
+ Graphic.HAMMER: RelatedTexture("items.png", 32, 16, 16, 16),
+ Graphic.KEY: RelatedTexture("items.png", 0, 0, 16, 16),
+ Graphic.SHIELD: RelatedTexture("items.png", 16, 0, 16, 16),
+ Graphic.SWORD: RelatedTexture("items.png", 32, 0, 16, 16),
+
+ Graphic.ITEMS_TEXT: "items_text.png",
+
+ Graphic.ZERO: RelatedTexture("numbers.png", 0, 16, 16, 16),
+ Graphic.ONE: RelatedTexture("numbers.png", 16, 16, 16, 16),
+ Graphic.TWO: RelatedTexture("numbers.png", 32, 16, 16, 16),
+ Graphic.THREE: RelatedTexture("numbers.png", 48, 16, 16, 16),
+ Graphic.FOUR: RelatedTexture("numbers.png", 64, 16, 16, 16),
+ Graphic.FIVE: RelatedTexture("numbers.png", 0, 0, 16, 16),
+ Graphic.SIX: RelatedTexture("numbers.png", 16, 0, 16, 16),
+ Graphic.SEVEN: RelatedTexture("numbers.png", 32, 0, 16, 16),
+ Graphic.EIGHT: RelatedTexture("numbers.png", 48, 0, 16, 16),
+ Graphic.NINE: RelatedTexture("numbers.png", 64, 0, 16, 16),
+
+ Graphic.LETTER_A: RelatedTexture("letters.png", 0, 16, 16, 16),
+ Graphic.LETTER_E: RelatedTexture("letters.png", 16, 16, 16, 16),
+ Graphic.LETTER_H: RelatedTexture("letters.png", 32, 16, 16, 16),
+ Graphic.LETTER_I: RelatedTexture("letters.png", 0, 0, 16, 16),
+ Graphic.LETTER_M: RelatedTexture("letters.png", 16, 0, 16, 16),
+ Graphic.LETTER_T: RelatedTexture("letters.png", 32, 0, 16, 16),
+
+ Graphic.DIVIDE: RelatedTexture("symbols.png", 0, 16, 16, 16),
+ Graphic.EQUALS: RelatedTexture("symbols.png", 16, 16, 16, 16),
+ Graphic.MINUS: RelatedTexture("symbols.png", 32, 16, 16, 16),
+ Graphic.PLUS: RelatedTexture("symbols.png", 0, 0, 16, 16),
+ Graphic.TIMES: RelatedTexture("symbols.png", 16, 0, 16, 16),
+ Graphic.NO: RelatedTexture("symbols.png", 32, 0, 16, 16),
+
+ Graphic.UNKNOWN: RelatedTexture("symbols.png", 32, 0, 16, 16), # Same as "No"
+}
+
+BACKGROUND_TILE = RelatedTexture("inanimates.png", 0, 32, 16, 16)
+
+
+class PlayerSprite(Enum):
+ HUMAN = 0
+ DUCK = 1
+ HORSE = 2
+ CAT = 3
+ UNKNOWN = -1
+
+
+PLAYER_GRAPHICS = {
+ Graphic.PLAYER_DOWN: {
+ PlayerSprite.HUMAN: RelatedTexture("human.png", 0, 16, 16, 16),
+ PlayerSprite.DUCK: RelatedTexture("duck.png", 0, 16, 16, 16),
+ PlayerSprite.HORSE: RelatedTexture("horse.png", 0, 16, 16, 16),
+ PlayerSprite.CAT: RelatedTexture("cat.png", 0, 16, 16, 16),
+ },
+ Graphic.PLAYER_UP: {
+ PlayerSprite.HUMAN: RelatedTexture("human.png", 16, 0, 16, 16),
+ PlayerSprite.DUCK: RelatedTexture("duck.png", 16, 0, 16, 16),
+ PlayerSprite.HORSE: RelatedTexture("horse.png", 16, 0, 16, 16),
+ PlayerSprite.CAT: RelatedTexture("cat.png", 16, 0, 16, 16),
+ },
+ Graphic.PLAYER_LEFT: {
+ PlayerSprite.HUMAN: RelatedTexture("human.png", 16, 16, 16, 16),
+ PlayerSprite.DUCK: RelatedTexture("duck.png", 16, 16, 16, 16),
+ PlayerSprite.HORSE: RelatedTexture("horse.png", 16, 16, 16, 16),
+ PlayerSprite.CAT: RelatedTexture("cat.png", 16, 16, 16, 16),
+ },
+ Graphic.PLAYER_RIGHT: {
+ PlayerSprite.HUMAN: RelatedTexture("human.png", 0, 0, 16, 16),
+ PlayerSprite.DUCK: RelatedTexture("duck.png", 0, 0, 16, 16),
+ PlayerSprite.HORSE: RelatedTexture("horse.png", 0, 0, 16, 16),
+ PlayerSprite.CAT: RelatedTexture("cat.png", 0, 0, 16, 16),
+ },
+}
+
+ALL_GRAPHICS = [
+ BACKGROUND_TILE,
+ *IMAGE_GRAPHICS.values(),
+ *[graphic for sub_dict in PLAYER_GRAPHICS.values() for graphic in sub_dict.values()],
+]
+
+_textures: dict[str | RelatedTexture, Texture] = {}
+
+
+def get_texture_by_identifier(texture_identifier: str | RelatedTexture) -> Texture:
+ if texture_identifier in _textures:
+ return _textures[texture_identifier]
+
+ if isinstance(texture_identifier, str):
+ image_data = pkgutil.get_data(game.__name__, f"graphics/{texture_identifier}")
+ if image_data is None:
+ raise RuntimeError(f'Could not find file "graphics/{texture_identifier}" for texture {texture_identifier}')
+
+ image_bytes = BytesIO(cast(Buffer, image_data))
+ texture = cast(Texture, CoreImage(image_bytes, ext="png").texture)
+ texture.mag_filter = "nearest"
+ _textures[texture_identifier] = texture
+ return texture
+
+ base_texture_filename, x, y, w, h = texture_identifier
+
+ base_texture = get_texture_by_identifier(base_texture_filename)
+
+ sub_texture = base_texture.get_region(x, y, w, h)
+ sub_texture.mag_filter = "nearest"
+ _textures[texture_identifier] = sub_texture
+ return sub_texture
+
+
+def get_texture(graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None) -> Texture | None:
+ if graphic == Graphic.EMPTY:
+ return None
+
+ if graphic == "Grass":
+ return get_texture_by_identifier(BACKGROUND_TILE)
+
+ if graphic in IMAGE_GRAPHICS:
+ return get_texture_by_identifier(IMAGE_GRAPHICS[graphic])
+
+ if graphic in PLAYER_GRAPHICS:
+ if player_sprite is None:
+ raise ValueError("Tried to load a player graphic without specifying a player_sprite")
+
+ return get_texture_by_identifier(PLAYER_GRAPHICS[graphic][player_sprite])
+
+ logger.exception(f"Tried to load unknown graphic {graphic}.")
+ return get_texture(Graphic.UNKNOWN)
diff --git a/worlds/apquest/client/item_quality.py b/worlds/apquest/client/item_quality.py
new file mode 100644
index 000000000000..35a975d88604
--- /dev/null
+++ b/worlds/apquest/client/item_quality.py
@@ -0,0 +1,25 @@
+from enum import Enum
+
+from BaseClasses import ItemClassification
+from NetUtils import NetworkItem
+
+
+class ItemQuality(Enum):
+ FILLER = 0
+ TRAP = 1
+ USEFUL = 2
+ PROGRESSION = 3
+ PROGUSEFUL = 4
+
+
+def get_quality_for_network_item(network_item: NetworkItem) -> ItemQuality:
+ flags = ItemClassification(network_item.flags)
+ if ItemClassification.progression in flags:
+ if ItemClassification.useful in flags:
+ return ItemQuality.PROGUSEFUL
+ return ItemQuality.PROGRESSION
+ if ItemClassification.useful in flags:
+ return ItemQuality.USEFUL
+ if ItemClassification.trap in flags:
+ return ItemQuality.TRAP
+ return ItemQuality.FILLER
diff --git a/worlds/apquest/client/launch.py b/worlds/apquest/client/launch.py
new file mode 100644
index 000000000000..515cf395bedf
--- /dev/null
+++ b/worlds/apquest/client/launch.py
@@ -0,0 +1,27 @@
+import asyncio
+from collections.abc import Sequence
+
+import colorama
+
+from CommonClient import get_base_parser, handle_url_arg
+
+# !!! IMPORTANT !!!
+# The client implementation is *not* meant for teaching.
+# Obviously, it is written to the best of its author's abilities,
+# but it is not to the same standard as the rest of the apworld.
+# Copy things from here at your own risk.
+
+
+def launch_ap_quest_client(*args: Sequence[str]) -> None:
+ from .ap_quest_client import main
+
+ parser = get_base_parser()
+ parser.add_argument("--name", default=None, help="Slot Name to connect as.")
+ parser.add_argument("url", nargs="?", help="Archipelago connection url")
+
+ launch_args = handle_url_arg(parser.parse_args(args))
+
+ colorama.just_fix_windows_console()
+
+ asyncio.run(main(launch_args))
+ colorama.deinit()
diff --git a/worlds/apquest/client/sounds.py b/worlds/apquest/client/sounds.py
new file mode 100644
index 000000000000..f4c4ab769dc0
--- /dev/null
+++ b/worlds/apquest/client/sounds.py
@@ -0,0 +1,249 @@
+import asyncio
+import pkgutil
+from asyncio import Task
+from collections.abc import Buffer
+from pathlib import Path
+from typing import cast
+
+from kivy import Config
+from kivy.core.audio import Sound, SoundLoader
+
+from CommonClient import logger
+
+from .. import game
+from .item_quality import ItemQuality
+from .utils import make_data_directory
+
+ITEM_JINGLES = {
+ ItemQuality.PROGUSEFUL: "8bit ProgUseful.ogg",
+ ItemQuality.PROGRESSION: "8bit Progression.ogg",
+ ItemQuality.USEFUL: "8bit Useful.ogg",
+ ItemQuality.TRAP: "8bit Trap.ogg",
+ ItemQuality.FILLER: "8bit Filler.ogg",
+}
+
+CONFETTI_CANNON = "APQuest Confetti Cannon.ogg"
+MATH_PROBLEM_STARTED_JINGLE = "APQuest Math Problem Starter Jingle.ogg"
+MATH_PROBLEM_SOLVED_JINGLE = "APQuest Math Problem Solved Jingle.ogg"
+VICTORY_JINGLE = "8bit Victory.ogg"
+
+ALL_JINGLES = [
+ MATH_PROBLEM_SOLVED_JINGLE,
+ MATH_PROBLEM_STARTED_JINGLE,
+ CONFETTI_CANNON,
+ VICTORY_JINGLE,
+ *ITEM_JINGLES.values(),
+]
+
+BACKGROUND_MUSIC_INTRO = "APQuest Intro.ogg"
+BACKGROUND_MUSIC = "APQuest BGM.ogg"
+MATH_TIME_BACKGROUND_MUSIC = "APQuest Math BGM.ogg"
+
+ALL_BGM = [
+ BACKGROUND_MUSIC_INTRO,
+ BACKGROUND_MUSIC,
+ MATH_TIME_BACKGROUND_MUSIC,
+]
+
+ALL_SOUNDS = [
+ *ALL_JINGLES,
+ *ALL_BGM,
+]
+
+
+class SoundManager:
+ sound_paths: dict[str, Path]
+
+ jingles: dict[str, Sound]
+ bgm_songs: dict[str, Sound]
+
+ active_bgm_song: str = BACKGROUND_MUSIC_INTRO
+
+ current_background_music_volume: float = 1.0
+ background_music_target_volume: float = 0.0
+
+ background_music_task: Task[None] | None = None
+ background_music_last_position: int = 0
+
+ volume_percentage: int = 0
+
+ game_started: bool
+ math_trap_active: bool
+ allow_intro_to_play: bool
+
+ def __init__(self) -> None:
+ self.extract_sounds()
+ self.populate_sounds()
+
+ self.game_started = False
+ self.allow_intro_to_play = False
+ self.math_trap_active = False
+
+ self.ensure_config()
+
+ self.background_music_task = asyncio.create_task(self.sound_manager_loop())
+
+ def ensure_config(self) -> None:
+ Config.adddefaultsection("APQuest")
+ Config.setdefault("APQuest", "volume", 50)
+ self.set_volume_percentage(Config.getint("APQuest", "volume"))
+
+ async def sound_manager_loop(self) -> None:
+ while True:
+ self.update_background_music()
+ self.do_fade()
+ await asyncio.sleep(0.02)
+
+ def extract_sounds(self) -> None:
+ # Kivy appears to have no good way of loading audio from bytes.
+ # So, we have to extract it out of the .apworld first
+
+ sound_paths = {}
+
+ sound_directory = make_data_directory("sounds")
+
+ for sound in ALL_SOUNDS:
+ sound_file_location = sound_directory / sound
+
+ sound_paths[sound] = sound_file_location
+
+ if sound_file_location.exists():
+ continue
+
+ with open(sound_file_location, "wb") as sound_file:
+ data = pkgutil.get_data(game.__name__, f"audio/{sound}")
+ if data is None:
+ logger.exception(f"Unable to extract sound {sound} to Archipelago/data")
+ continue
+ sound_file.write(cast(Buffer, data))
+
+ self.sound_paths = sound_paths
+
+ def load_audio(self, sound_filename: str) -> Sound:
+ audio_path = self.sound_paths[sound_filename]
+
+ sound_object = SoundLoader.load(str(audio_path.absolute()))
+ sound_object.seek(0)
+ return sound_object
+
+ def populate_sounds(self) -> None:
+ try:
+ self.jingles = {sound_filename: self.load_audio(sound_filename) for sound_filename in ALL_JINGLES}
+ except Exception as e:
+ logger.exception(e)
+
+ try:
+ self.bgm_songs = {sound_filename: self.load_audio(sound_filename) for sound_filename in ALL_BGM}
+ for bgm_song in self.bgm_songs.values():
+ bgm_song.loop = True
+ bgm_song.seek(0)
+ except Exception as e:
+ logger.exception(e)
+
+ def play_jingle(self, audio_filename: str) -> None:
+ higher_priority_sound_is_playing = False
+
+ for sound_name, sound in self.jingles.items():
+ if higher_priority_sound_is_playing: # jingles are ordered by priority, lower priority gets eaten
+ sound.stop()
+ continue
+
+ if sound_name == audio_filename:
+ sound.play()
+ self.update_background_music()
+ higher_priority_sound_is_playing = True
+
+ elif sound.state == "play":
+ higher_priority_sound_is_playing = True
+
+ def update_background_music(self) -> None:
+ self.update_active_song()
+ if any(sound.state == "play" for sound in self.jingles.values()):
+ self.play_background_music(False)
+ else:
+ if self.math_trap_active:
+ # Don't fade math trap song, it ends up feeling better
+ self.play_background_music(True)
+ else:
+ self.fade_background_music(True)
+
+ def play_background_music(self, play: bool = True) -> None:
+ if play:
+ self.background_music_target_volume = 1
+ self.set_background_music_volume(1)
+ else:
+ self.background_music_target_volume = 0
+ self.set_background_music_volume(0)
+
+ def set_background_music_volume(self, volume: float) -> None:
+ self.current_background_music_volume = volume
+
+ for song_filename, song in self.bgm_songs.items():
+ if song_filename != self.active_bgm_song:
+ song.volume = 0
+ continue
+ song.volume = volume * self.volume_percentage / 100
+
+ def fade_background_music(self, fade_in: bool = True) -> None:
+ if fade_in:
+ self.background_music_target_volume = 1
+ else:
+ self.background_music_target_volume = 0
+
+ def set_volume_percentage(self, volume_percentage: float) -> None:
+ volume_percentage_int = int(volume_percentage)
+ if self.volume_percentage != volume_percentage:
+ self.volume_percentage = volume_percentage_int
+ Config.set("APQuest", "volume", volume_percentage_int)
+ Config.write()
+ self.set_background_music_volume(self.current_background_music_volume)
+
+ for jingle in self.jingles.values():
+ jingle.volume = self.volume_percentage / 100
+
+ def do_fade(self) -> None:
+ if self.current_background_music_volume > self.background_music_target_volume:
+ self.set_background_music_volume(max(0.0, self.current_background_music_volume - 0.02))
+ if self.current_background_music_volume < self.background_music_target_volume:
+ self.set_background_music_volume(min(1.0, self.current_background_music_volume + 0.02))
+
+ for song_filename, song in self.bgm_songs.items():
+ if song_filename != self.active_bgm_song:
+ if song_filename == BACKGROUND_MUSIC:
+ # It ends up feeling better if this just always continues playing quietly after being started.
+ # Even "fading in at a random spot" is better than restarting the song after a jingle / math trap.
+ if self.game_started and song.state == "stop":
+ song.play()
+ song.seek(0)
+ continue
+
+ song.stop()
+ song.seek(0)
+ continue
+
+ if self.active_bgm_song == BACKGROUND_MUSIC_INTRO and not self.allow_intro_to_play:
+ song.stop()
+ song.seek(0)
+ continue
+
+ if self.current_background_music_volume != 0:
+ if song.state == "stop":
+ song.play()
+ song.seek(0)
+
+ def update_active_song(self) -> None:
+ new_active_song = self.determine_correct_song()
+ if new_active_song == self.active_bgm_song:
+ return
+ self.active_bgm_song = new_active_song
+ # reevaluate song volumes
+ self.set_background_music_volume(self.current_background_music_volume)
+
+ def determine_correct_song(self) -> str:
+ if not self.game_started:
+ return BACKGROUND_MUSIC_INTRO
+
+ if self.math_trap_active:
+ return MATH_TIME_BACKGROUND_MUSIC
+
+ return BACKGROUND_MUSIC
diff --git a/worlds/apquest/client/utils.py b/worlds/apquest/client/utils.py
new file mode 100644
index 000000000000..362e5fed1188
--- /dev/null
+++ b/worlds/apquest/client/utils.py
@@ -0,0 +1,25 @@
+from pathlib import Path
+
+from Utils import user_path
+
+
+def make_data_directory(dir_name: str) -> Path:
+ root_directory = Path(user_path())
+ if not root_directory.exists():
+ raise FileNotFoundError(f"Unable to find AP directory {root_directory.absolute()}.")
+
+ data_directory = root_directory / "data"
+
+ specific_data_directory = data_directory / "apquest" / dir_name
+ specific_data_directory.mkdir(parents=True, exist_ok=True)
+
+ gitignore = specific_data_directory / ".gitignore"
+
+ with open(gitignore, "w") as f:
+ f.write(
+ """*
+!.gitignore
+"""
+ )
+
+ return specific_data_directory
diff --git a/worlds/apquest/components.py b/worlds/apquest/components.py
new file mode 100644
index 000000000000..eb25450d8ccc
--- /dev/null
+++ b/worlds/apquest/components.py
@@ -0,0 +1,33 @@
+from worlds.LauncherComponents import Component, Type, components, launch
+
+
+# The most common type of component is a client, but there are other components, such as sprite/palette adjusters.
+# (Note: Some worlds distribute their clients as separate, standalone programs,
+# while others include them in the apworld itself. Standalone clients are not an apworld component,
+# although you could make a component that e.g. auto-installs and launches the standalone client for the user.)
+# APQuest has a Python client inside the apworld that contains the entire game. This is a component.
+# APQuest will not teach you how to make a client or any other type of component.
+# However, let's quickly talk about how you register a component to be launchable from the Archipelago Launcher.
+# First, you'll need a function that takes a list of args (e.g. from the command line) that launches your component.
+def run_client(*args: str) -> None:
+ # Ideally, you should lazily import your component code so that it doesn't have to be loaded until necessary.
+ from .client.launch import launch_ap_quest_client
+
+ # Also, if your component has its own lifecycle, like if it is its own window that can be interacted with,
+ # you should use the LauncherComponents.launch helper (which itself calls launch_subprocess).
+ # This will create a subprocess for your component, launching it in a separate window from the Archipelago Launcher.
+ launch(launch_ap_quest_client, name="APQuest Client", args=args)
+
+
+# You then add this function as a component by appending a Component instance to LauncherComponents.components.
+# Now, it will show up in the Launcher with its display name,
+# and when the user clicks on the "Open" button, your function will be run.
+components.append(
+ Component(
+ "APQuest Client",
+ func=run_client,
+ game_name="APQuest",
+ component_type=Type.CLIENT,
+ supports_uri=True,
+ )
+)
diff --git a/worlds/apquest/docs/de_APQuest.md b/worlds/apquest/docs/de_APQuest.md
new file mode 100644
index 000000000000..82e383fa1600
--- /dev/null
+++ b/worlds/apquest/docs/de_APQuest.md
@@ -0,0 +1,78 @@
+# APQuest
+
+## Wo ist die Seite für die Einstellungen?
+
+Die [Seite für die Spielereinstellungen dieses Spiels](../player-options) enthält alle Optionen die man benötigt, um
+eine YAML-Datei zu konfigurieren und zu exportieren.
+
+## Was ist APQuest?
+
+APQuest ist ein Spiel, welches von NewSoupVi für Archipelago entwickelt wurde.
+Es ist ein minimalistisches 8bit-inspiriertes Abenteuerspiel mit gitterförmiger Bewegungssteuerung.
+APQuest ist ungefähr 20 Sekunden lang. Der Client kann aber nahtlos zwischen mehreren APQuest-Slots wechseln.
+Wenn du 10 APQuest-Slots in einer Multiworld haben willst, sollte das also problemlos möglich sein.if you want to have 10 of them, that should work pretty well.
+
+Ausschlaggebend ist bei APQuest, dass das gesamte Spiel in der .apworld enthalten ist.
+Wenn du also die .apworld in deine
+[Archipelago-Installation](https://github.com/ArchipelagoMW/Archipelago/releases/latest) installiert hast,
+kannst du APQuest spielen.
+
+## Warum existiert APQuest?
+
+APQuest ist als Beispiel-.apworld geschrieben, mit welchem neue .apworld-Entwickler lernen können, wie man eine
+.apworld schreibt.
+Der [APQuest-Quellcode](https://github.com/NewSoupVi/Archipelago/tree/apquest/worlds/apquest) enthält unzählige Kommentare und Beispiele, die erklären,
+wie jeder Teil der World-API funktioniert.
+Dabei nutzt er nur die modernsten API-Funktionen (Stand: 2025-08-24).
+
+Das sekundäre Ziel von APQuest ist, eine semi-minimale, generische .apworld zu sein, die Archipelago selbst gehört.
+Damit kann sie für Archipelagos Unit-Tests benutzt werden,
+ohne dass sich die Archipelago-Entwickler davor fürchten müssen, dass APQuest irgendwann gelöscht wird.
+
+Das dritte Ziel von APQuest ist, das erste "Spiel in einer .apworld" zu sein,
+wobei das ganze Spiel in Python und Kivy programmiert ist
+und innerhalb seines CommonClient-basierten Clients spielbar ist.
+Ich bin mir nicht ganz sicher, dass es wirklich das erste Spiel dieser Art ist, aber ich kenne bis jetzt keine anderen.
+
+## Wenn ich mich im APQuest-Client angemeldet habe, wie spiele ich dann das Spiel?
+
+WASD oder Pfeiltasten zum Bewegen.
+Leertaste, um dein Schwert zu schwingen (wenn du es hast) und um mit Objekten zu interagieren.
+C, um die Konfettikanone zu feuern.
+
+Öffne Kisten, zerhacke Büsche, öffne Türen, aktiviere Knöpfe, besiege Gegner.
+Sobald du den Drachen im oberen rechten Raum bezwingst, gewinnst du das Spiel.
+Das ist alles! Viel Spaß!
+
+## Ein Statement zum Besitz von APQuest
+
+APQuest ist mit der [MIT-Lizenz](https://opensource.org/license/mit) lizenziert,
+was heißt, dass es von jedem für jeden Zweck modifiziert und verbreitet werden kann.
+Archipelago hat jedoch seine eigenen Besitztumsstrukturen, die über der MIT-Lizenz stehen.
+Diese Strukturen machen es unklar,
+ob eine .apworld-Implementierung überhaupt permanent verlässlich in Archipelago bleibt.
+
+Im Zusammenhang mit diesen unverbindlichen, nicht gesetzlich verpflichtenden Besitztumsstrukturen
+mache ich die folgende Aussage.
+
+Ich, NewSoupVi, verzichte hiermit auf alle Rechte, APQuest aus Archipelago zu entfernen.
+Dies bezieht sich auf alle Teile von APQuest mit der Ausnahme der Musik und der Soundeffekte.
+Wenn ich die Töne entfernt haben möchte, muss ich dafür selbst einen PR öffnen.
+Dieser PR darf nur die Töne entfernen und muss APQuest intakt und spielbar halten.
+
+Solang ich der Maintainer von APQuest bin, möchte ich als solcher agieren.
+Das heißt, dass jegliche Änderungen an APQuest zuerst von mir genehmigt werden müssen.
+
+Wenn ich jedoch aufhöre, der Maintainer von APQuest zu sein,
+egal ob es mein eigener Wunsch war oder ich meinen Maintainer-Verantwortungen nicht mehr nachkomme,
+dann wird APQuest automatisch Eigentum der Core-Maintainer von Archipelago,
+die dann frei entscheiden können, was mit APQuest passieren soll.
+Es wäre mein Wunsch, dass wenn APQuest an eine andere Einzelperson übergeben wird,
+diese Person sich an ähnliche Eigentumsregelungen hält wie ich.
+
+Hoffentlich stellt dieses Statement sicher, dass APQuest für immer eine .apworld sein kann,
+auf die Archipelago sich verlassen kann.
+Wenn die Besitztumsstrukturen von Archipelago geändert werden,
+vertraue ich den Core-Maintainern (bzw. den Eigentümern von Archipelago generell) damit,
+angemessene Entscheidungen darüber zu treffen,
+wie dieses Statement im Kontext der neuen Regeln interpretiert werden sollte.
diff --git a/worlds/apquest/docs/en_APQuest.md b/worlds/apquest/docs/en_APQuest.md
new file mode 100644
index 000000000000..64eb1fa35a6d
--- /dev/null
+++ b/worlds/apquest/docs/en_APQuest.md
@@ -0,0 +1,69 @@
+# APQuest
+
+## 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.
+
+## What is APQuest?
+
+APQuest is an original game made entirely by NewSoupVi.
+It is a minimal 8bit-era inspired adventure game with grid-like movement.
+It is about 20 seconds long. However, the client can seamlessly switch between different slots,
+so if you want to have 10 of them, that should work pretty well.
+
+Crucially, this game is entirely integrated into the client sitting inside its .apworld.
+If you have the .apworld installed into your [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
+install, you can play APQuest.
+
+## Why does APQuest exist?
+
+APQuest is implemented to be an example .apworld that can be used as a learning tool for new .apworld developers.
+Its [source code](https://github.com/NewSoupVi/Archipelago/tree/apquest/worlds/apquest)
+contains countless comments explaining how each part of the World API works.
+Also, as of the writing of this setup guide (2025-08-24), it is up to date with all the modern Archipelago APIs.
+
+The secondary goal of APQuest is to be a semi-minimal generic world that is owned by Archipelago.
+This means it can be used for Archipelago's unit tests without fear of eventual removal.
+
+Finally, APQuest was designed to be the first ever "game inside an .apworld",
+where the entire game is coded in Python and Kivy and is playable from within its CommonClient-based Client.
+I'm not actually sure if it's the first, but I'm not aware of any others.
+
+## Once I'm inside the APQuest client, how do I actually play APQuest?
+
+WASD or Arrow Keys for movement.
+Space to swing your sword (if you have it) or interact with objects.
+C to fire the Confetti Cannon.
+
+Open chests, slash bushes, open doors, press buttons, defeat enemies.
+Once you beat the dragon in the top right room, you win.
+That's all there is! Have fun!
+
+## A statement on the ownership over APQuest
+
+APQuest is licensed using the [MIT license](https://opensource.org/license/mit),
+meaning it can be modified and redistributed by anyone for any purpose.
+However, Archipelago has its own ownership structures built ontop of the license.
+These ownership structures call into question whether any world implementation can permanently be relied on.
+
+In terms of these non-binding, non-legal Archipelago ownership structures, I will make the following statement.
+
+I, NewSoupVi, hereby relinquish any and all rights to remove APQuest from Archipelago.
+This applies to all parts of APQuest with the sole exception of the music and sounds.
+If I want the sounds to be removed, I must do so via a PR to the Archipelago repository myself.
+Said PR must keep APQuest intact and playable, just with the music removed.
+
+As long as I am the maintainer of APQuest, I wish to act as such.
+This means that any updates to APQuest must go through me.
+
+However, if I ever cease to be the maintainer of APQuest,
+due to my own wishes or because I fail to uphold the maintainership "contract",
+the maintainership of APQuest will go to the Core Maintainers of Archipelago, who may then decide what to do with it.
+They can decide freely, but if the maintainership goes to another singular person,
+it is my wish that this person adheres to a similar set of rules that I've laid out here for myself.
+
+Hopefully, this set of commitments should ensure that APQuest will forever be an apworld that can be relied on in Core.
+If the ownership structures of Archipelago change,
+I trust the Core Maintainers (or the owners in general) of Archipelago to make reasonable assumptions
+about how this statement should be reinterpreted to fit the new rules.
diff --git a/worlds/apquest/docs/setup_de.md b/worlds/apquest/docs/setup_de.md
new file mode 100644
index 000000000000..425d57d9e7d5
--- /dev/null
+++ b/worlds/apquest/docs/setup_de.md
@@ -0,0 +1,43 @@
+# APQuest Randomizer Setup-Anleitung
+
+## Benötigte Software
+
+- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
+- Die [APQuest-apworld](https://github.com/NewSoupVi/Archipelago/releases),
+ falls diese nicht mit deiner Version von Archipelago gebündelt ist.
+
+## Wie man spielt
+
+Zuerst brauchst du einen Raum, mit dem du dich verbinden kannst.
+Dafür musst du oder jemand den du kennst ein Spiel generieren.
+Dieser Schritt wird hier nicht erklärt, aber du kannst den
+[Archipelago Setup Guide](/tutorial/Archipelago/setup_en#generating-a-game) lesen.
+
+Du musst außerdem [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest) installiert haben
+und die [APQuest apworld](https://github.com/NewSoupVi/Archipelago/releases) darin installieren.
+
+Von hier ist es einfach, dich mit deinem Slot zu verbinden.
+
+### Webhost-Raum
+
+Wenn dein Raum auf einem WebHost läuft (z.B. [archipelago.gg](https://archipelago.gg))
+kannst du einfach auf deinen Namen in der Spielerliste klicken.
+Dies öffnet den Archipelago Launcher, welcher dich dann fragt,
+ob du den Text Client oder den APQuest Client öffnen willst.
+Wähle hier den APQuest Client. Der Rest sollte automatisch passieren, sodass du APQuest direkt spielen kannst.
+
+### Lokaler Server
+
+Falls für deinen Raum keine WebHost-Raumseite verfügbar ist, kannst du APQuest manuell starten.
+
+Öffne den Archipelago Launcher und finde den APQuest Client in der Komponentenliste. Klicke auf "Open".
+Nach einer kurzen Wartezeit sollte sich der APQuest Client öffnen.
+Tippe in der oberen Zeile die Server-Adresse ein und klicke dann auf "Connect".
+Gib deinen Spielernamen ein. Wenn ein Passwort existiert, tippe dieses auch ein.
+Du solltest jetzt verbunden sein und kannst APQuest spielen.
+
+## Slotwechsel
+
+Der APQuest Client kann zwischen verschiedenen Slots wechseln, ohne neugestartet werden zu müssen,
+
+Klicke einfach den "Disconnect"-Knopf. Dann verbinde dich mit dem anderen Raum / Slot.
diff --git a/worlds/apquest/docs/setup_en.md b/worlds/apquest/docs/setup_en.md
new file mode 100644
index 000000000000..2b66fa93b80a
--- /dev/null
+++ b/worlds/apquest/docs/setup_en.md
@@ -0,0 +1,42 @@
+# APQuest Randomizer Setup Guide
+
+## Required Software
+
+- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
+- [The APQuest apworld](https://github.com/NewSoupVi/Archipelago/releases),
+ if not bundled with your version of Archipelago
+
+## How to play
+
+First, you need a room to connect to. For this, you or someone you know has to generate a game.
+This will not be explained here,
+but you can check the [Archipelago Setup Guide](/tutorial/Archipelago/setup_en#generating-a-game).
+
+You also need to have [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest) installed
+and the [The APQuest apworld](https://github.com/NewSoupVi/Archipelago/releases) installed into Archipelago.
+
+From here, connecting to your APQuest slot is easy. There are two scenarios.
+
+### Webhost Room
+
+If your room is hosted on a WebHost (e.g. [archipelago.gg](https://archipelago.gg)),
+you should be able to simply click on your name in the player list.
+This will open the Archipelago Launcher
+and ask you whether you want to connect with the Text Client or the APQuest Client.
+Choose "APQuest Client". The rest should happen completely automatically and you should be able to play APQuest.
+
+### Locally hosted room
+
+If your room does not have a WebHost room page available, you can launch APQuest manually.
+
+Open the Archipelago Launcher, and then select the APQuest Client from the list.
+After a short while, the APQuest client should open.
+Enter the server address at the top and click "Connect".
+Then, enter your name. If a password exists, enter the password.
+You should now be connected and able to play APQuest.
+
+## Switching Rooms
+
+The APQuest Client can seamlessly switch rooms without restarting.
+
+Simply click the "Disconnect" button, then connect to a different slot/room.
diff --git a/worlds/apquest/game/__init__.py b/worlds/apquest/game/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/worlds/apquest/game/audio/8bit Filler.ogg b/worlds/apquest/game/audio/8bit Filler.ogg
new file mode 100644
index 000000000000..d16cfc7aba8b
Binary files /dev/null and b/worlds/apquest/game/audio/8bit Filler.ogg differ
diff --git a/worlds/apquest/game/audio/8bit ProgUseful.ogg b/worlds/apquest/game/audio/8bit ProgUseful.ogg
new file mode 100644
index 000000000000..bc092f771ad0
Binary files /dev/null and b/worlds/apquest/game/audio/8bit ProgUseful.ogg differ
diff --git a/worlds/apquest/game/audio/8bit Progression.ogg b/worlds/apquest/game/audio/8bit Progression.ogg
new file mode 100644
index 000000000000..a4d7ba871157
Binary files /dev/null and b/worlds/apquest/game/audio/8bit Progression.ogg differ
diff --git a/worlds/apquest/game/audio/8bit Trap.ogg b/worlds/apquest/game/audio/8bit Trap.ogg
new file mode 100644
index 000000000000..783f5fd3d3d3
Binary files /dev/null and b/worlds/apquest/game/audio/8bit Trap.ogg differ
diff --git a/worlds/apquest/game/audio/8bit Useful.ogg b/worlds/apquest/game/audio/8bit Useful.ogg
new file mode 100644
index 000000000000..ed333e9d93cf
Binary files /dev/null and b/worlds/apquest/game/audio/8bit Useful.ogg differ
diff --git a/worlds/apquest/game/audio/8bit Victory.ogg b/worlds/apquest/game/audio/8bit Victory.ogg
new file mode 100644
index 000000000000..5153af11cec7
Binary files /dev/null and b/worlds/apquest/game/audio/8bit Victory.ogg differ
diff --git a/worlds/apquest/game/audio/APQuest BGM.ogg b/worlds/apquest/game/audio/APQuest BGM.ogg
new file mode 100644
index 000000000000..8a6e542891c8
Binary files /dev/null and b/worlds/apquest/game/audio/APQuest BGM.ogg differ
diff --git a/worlds/apquest/game/audio/APQuest Confetti Cannon.ogg b/worlds/apquest/game/audio/APQuest Confetti Cannon.ogg
new file mode 100644
index 000000000000..4eafe62a734b
Binary files /dev/null and b/worlds/apquest/game/audio/APQuest Confetti Cannon.ogg differ
diff --git a/worlds/apquest/game/audio/APQuest Intro.ogg b/worlds/apquest/game/audio/APQuest Intro.ogg
new file mode 100644
index 000000000000..52c1fa89adbe
Binary files /dev/null and b/worlds/apquest/game/audio/APQuest Intro.ogg differ
diff --git a/worlds/apquest/game/audio/APQuest Math BGM.ogg b/worlds/apquest/game/audio/APQuest Math BGM.ogg
new file mode 100644
index 000000000000..5708f9cafb84
Binary files /dev/null and b/worlds/apquest/game/audio/APQuest Math BGM.ogg differ
diff --git a/worlds/apquest/game/audio/APQuest Math Problem Solved Jingle.ogg b/worlds/apquest/game/audio/APQuest Math Problem Solved Jingle.ogg
new file mode 100644
index 000000000000..f20e7a909e7d
Binary files /dev/null and b/worlds/apquest/game/audio/APQuest Math Problem Solved Jingle.ogg differ
diff --git a/worlds/apquest/game/audio/APQuest Math Problem Starter Jingle.ogg b/worlds/apquest/game/audio/APQuest Math Problem Starter Jingle.ogg
new file mode 100644
index 000000000000..7af05ee2a8ea
Binary files /dev/null and b/worlds/apquest/game/audio/APQuest Math Problem Starter Jingle.ogg differ
diff --git a/worlds/apquest/game/entities.py b/worlds/apquest/game/entities.py
new file mode 100644
index 000000000000..64b89206f6f6
--- /dev/null
+++ b/worlds/apquest/game/entities.py
@@ -0,0 +1,312 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from typing import TYPE_CHECKING, ClassVar
+
+from .graphics import Graphic
+from .items import ITEM_TO_GRAPHIC, Item
+from .locations import Location
+
+if TYPE_CHECKING:
+ from .player import Player
+
+
+class Entity:
+ solid: bool
+ graphic: Graphic
+
+
+class InteractableMixin:
+ @abstractmethod
+ def interact(self, player: Player) -> None:
+ pass
+
+
+class ActivatableMixin:
+ @abstractmethod
+ def activate(self, player: Player) -> None:
+ pass
+
+
+class LocationMixin:
+ location: Location
+ content: Item | None = None
+ remote: bool = False
+ has_given_content: bool = False
+
+ def force_clear(self) -> None:
+ if self.has_given_content:
+ return
+
+ self.has_given_content = True
+ self.content_success()
+
+ def give_content(self, player: Player) -> None:
+ if self.has_given_content:
+ return
+
+ if self.content is None:
+ self.content_failure()
+ return
+
+ if self.remote:
+ player.location_cleared(self.location.value)
+ else:
+ player.receive_item(self.content)
+
+ self.has_given_content = True
+ self.content_success()
+
+ def content_success(self) -> None:
+ pass
+
+ def content_failure(self) -> None:
+ pass
+
+
+class Empty(Entity):
+ solid = False
+ graphic = Graphic.EMPTY
+
+
+class Wall(Entity):
+ solid = True
+ graphic = Graphic.WALL
+
+
+class Chest(Entity, InteractableMixin, LocationMixin):
+ solid = True
+
+ is_open: bool = False
+
+ def __init__(self, location: Location) -> None:
+ self.location = location
+
+ def update_solidity(self) -> None:
+ self.solid = not self.has_given_content
+
+ def open(self) -> None:
+ self.is_open = True
+ self.update_solidity()
+
+ def interact(self, player: Player) -> None:
+ if self.has_given_content:
+ return
+
+ if self.is_open:
+ self.give_content(player)
+ return
+
+ self.open()
+
+ def content_success(self) -> None:
+ self.update_solidity()
+
+ def content_failure(self) -> None:
+ self.update_solidity()
+
+ @property
+ def graphic(self) -> Graphic:
+ if self.has_given_content:
+ return Graphic.EMPTY
+ if self.is_open:
+ if self.content is None:
+ return Graphic.EMPTY
+ return ITEM_TO_GRAPHIC[self.content]
+ return Graphic.CHEST
+
+
+class Door(Entity):
+ solid = True
+
+ is_open: bool = False
+
+ closed_graphic: ClassVar[Graphic]
+
+ def open(self) -> None:
+ self.is_open = True
+ self.solid = False
+
+ @property
+ def graphic(self) -> Graphic:
+ if self.is_open:
+ return Graphic.EMPTY
+ return self.closed_graphic
+
+
+class KeyDoor(Door, InteractableMixin):
+ closed_graphic = Graphic.KEY_DOOR
+
+ def interact(self, player: Player) -> None:
+ if self.is_open:
+ return
+
+ if not player.has_item(Item.KEY):
+ return
+
+ player.remove_item(Item.KEY)
+
+ self.open()
+
+
+class BreakableBlock(Door, InteractableMixin):
+ closed_graphic = Graphic.BREAKABLE_BLOCK
+
+ def interact(self, player: Player) -> None:
+ if self.is_open:
+ return
+
+ if not player.has_item(Item.HAMMER):
+ return
+
+ player.remove_item(Item.HAMMER)
+
+ self.open()
+
+
+class Bush(Door, InteractableMixin):
+ closed_graphic = Graphic.BUSH
+
+ def interact(self, player: Player) -> None:
+ if self.is_open:
+ return
+
+ if not player.has_item(Item.SWORD):
+ return
+
+ self.open()
+
+
+class Button(Entity, InteractableMixin):
+ solid = True
+
+ activates: ActivatableMixin
+ activated = False
+
+ def __init__(self, activates: ActivatableMixin) -> None:
+ self.activates = activates
+
+ def interact(self, player: Player) -> None:
+ if self.activated:
+ return
+
+ self.activated = True
+ self.activates.activate(player)
+
+ @property
+ def graphic(self) -> Graphic:
+ if self.activated:
+ return Graphic.BUTTON_ACTIVATED
+ return Graphic.BUTTON_NOT_ACTIVATED
+
+
+class ButtonDoor(Door, ActivatableMixin):
+ closed_graphic = Graphic.BUTTON_DOOR
+
+ def activate(self, player: Player) -> None:
+ self.is_open = True
+ self.solid = False
+
+
+class Enemy(Entity, InteractableMixin):
+ solid = True
+
+ current_health: int
+ max_health: int
+
+ dead: bool = False
+
+ enemy_graphic_by_health: ClassVar[dict[int, Graphic]] = {
+ 2: Graphic.NORMAL_ENEMY_2_HEALTH,
+ 1: Graphic.NORMAL_ENEMY_1_HEALTH,
+ }
+ enemy_default_graphic = Graphic.NORMAL_ENEMY_1_HEALTH
+
+ def __init__(self, max_health: int) -> None:
+ self.max_health = max_health
+ self.respawn()
+
+ def die(self) -> None:
+ self.dead = True
+ self.solid = False
+
+ def respawn(self) -> None:
+ self.dead = False
+ self.solid = True
+ self.heal_if_not_dead()
+
+ def heal_if_not_dead(self) -> None:
+ if self.dead:
+ return
+ self.current_health = self.max_health
+
+ def interact(self, player: Player) -> None:
+ if self.dead:
+ return
+
+ if player.has_item(Item.SWORD):
+ self.current_health = max(0, self.current_health - 1)
+
+ if self.current_health == 0:
+ if not self.dead:
+ self.die()
+ return
+
+ player.damage(2)
+
+ @property
+ def graphic(self) -> Graphic:
+ if self.dead:
+ return Graphic.EMPTY
+ return self.enemy_graphic_by_health.get(self.current_health, self.enemy_default_graphic)
+
+
+class EnemyWithLoot(Enemy, LocationMixin):
+ def __init__(self, max_health: int, location: Location) -> None:
+ super().__init__(max_health)
+ self.location = location
+
+ def die(self) -> None:
+ self.dead = True
+ self.solid = not self.has_given_content
+
+ def interact(self, player: Player) -> None:
+ if self.dead:
+ if not self.has_given_content:
+ self.give_content(player)
+ return
+
+ super().interact(player)
+
+ @property
+ def graphic(self) -> Graphic:
+ if self.dead and not self.has_given_content:
+ if self.content is None:
+ return Graphic.EMPTY
+ return ITEM_TO_GRAPHIC[self.content]
+ return super().graphic
+
+ def content_success(self) -> None:
+ self.die()
+
+ def content_failure(self) -> None:
+ self.die()
+
+
+class FinalBoss(Enemy):
+ enemy_graphic_by_health: ClassVar[dict[int, Graphic]] = {
+ 5: Graphic.BOSS_5_HEALTH,
+ 4: Graphic.BOSS_4_HEALTH,
+ 3: Graphic.BOSS_3_HEALTH,
+ 2: Graphic.BOSS_2_HEALTH,
+ 1: Graphic.BOSS_1_HEALTH,
+ }
+ enemy_default_graphic = Graphic.BOSS_1_HEALTH
+
+ def interact(self, player: Player) -> None:
+ dead_before = self.dead
+
+ super().interact(player)
+
+ if not dead_before and self.dead:
+ player.victory()
diff --git a/worlds/apquest/game/events.py b/worlds/apquest/game/events.py
new file mode 100644
index 000000000000..0517abc69be8
--- /dev/null
+++ b/worlds/apquest/game/events.py
@@ -0,0 +1,37 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class Event:
+ pass
+
+
+@dataclass
+class LocationClearedEvent(Event):
+ location_id: int
+
+
+@dataclass
+class MathProblemStarted(Event):
+ pass
+
+
+@dataclass
+class MathProblemSolved(Event):
+ pass
+
+
+@dataclass
+class VictoryEvent(Event):
+ pass
+
+
+@dataclass
+class LocationalEvent(Event):
+ x: int
+ y: int
+
+
+@dataclass
+class ConfettiFired(LocationalEvent):
+ pass
diff --git a/worlds/apquest/game/game.py b/worlds/apquest/game/game.py
new file mode 100644
index 000000000000..47dfc8cb374f
--- /dev/null
+++ b/worlds/apquest/game/game.py
@@ -0,0 +1,203 @@
+from math import ceil
+from random import Random
+
+from .entities import InteractableMixin
+from .events import ConfettiFired, Event, MathProblemSolved, MathProblemStarted
+from .gameboard import Gameboard, create_gameboard
+from .generate_math_problem import MathProblem, generate_math_problem
+from .graphics import Graphic
+from .inputs import DIGIT_INPUTS_TO_DIGITS, Direction, Input
+from .items import ITEM_TO_GRAPHIC, Item, RemotelyReceivedItem
+from .locations import Location
+from .player import Player
+
+
+class Game:
+ player: Player
+ gameboard: Gameboard
+
+ random: Random
+
+ queued_events: list[Event]
+
+ active_math_problem: MathProblem | None
+ active_math_problem_input: list[int] | None
+
+ remotely_received_items: set[tuple[int, int, int]]
+
+ def __init__(
+ self, hard_mode: bool, hammer_exists: bool, extra_chest: bool, random_object: Random | None = None
+ ) -> None:
+ self.queued_events = []
+ self.gameboard = create_gameboard(hard_mode, hammer_exists, extra_chest)
+ self.player = Player(self.gameboard, self.queued_events.append)
+ self.active_math_problem = None
+ self.remotely_received_items = set()
+
+ if random_object is None:
+ self.random = Random()
+ else:
+ self.random = random_object
+
+ def render(self) -> tuple[tuple[Graphic, ...], ...]:
+ if self.active_math_problem is None and self.player.inventory[Item.MATH_TRAP]:
+ self.active_math_problem = generate_math_problem(self.random)
+ self.active_math_problem_input = []
+ self.player.remove_item(Item.MATH_TRAP)
+ self.queued_events.append(MathProblemStarted())
+ return self.gameboard.render_math_problem(
+ self.active_math_problem,
+ self.active_math_problem_input,
+ self.currently_typed_in_math_result,
+ )
+
+ if self.active_math_problem is not None and self.active_math_problem_input is not None:
+ return self.gameboard.render_math_problem(
+ self.active_math_problem, self.active_math_problem_input, self.currently_typed_in_math_result
+ )
+
+ return self.gameboard.render(self.player)
+
+ def render_health_and_inventory(self, vertical: bool = False) -> tuple[Graphic, ...]:
+ size = self.gameboard.size[1] if vertical else self.gameboard.size[0]
+
+ graphics_array = [Graphic.EMPTY] * size
+
+ item_back_index = size - 1
+ for item, amount in sorted(self.player.inventory.items(), key=lambda sort_item: sort_item[0].value):
+ for _ in range(amount):
+ if item_back_index == 3:
+ break
+ if item == Item.HEALTH_UPGRADE:
+ continue
+ if item == Item.MATH_TRAP:
+ continue
+
+ graphics_array[item_back_index] = ITEM_TO_GRAPHIC[item]
+ item_back_index -= 1
+ else:
+ continue
+ break
+
+ remaining_health = self.player.current_health
+ for i in range(min(item_back_index, ceil(self.player.max_health / 2))):
+ new_remaining_health = max(0, remaining_health - 2)
+ change = remaining_health - new_remaining_health
+ remaining_health = new_remaining_health
+
+ if change == 2:
+ graphics_array[i] = Graphic.HEART
+ elif change == 1:
+ graphics_array[i] = Graphic.HALF_HEART
+ elif change == 0:
+ graphics_array[i] = Graphic.EMPTY_HEART
+
+ return tuple(graphics_array)
+
+ def attempt_player_movement(self, direction: Direction) -> None:
+ self.player.facing = direction
+
+ delta_x, delta_y = direction.value
+ new_x, new_y = self.player.current_x + delta_x, self.player.current_y + delta_y
+
+ if not self.gameboard.get_entity_at(new_x, new_y).solid:
+ self.player.current_x = new_x
+ self.player.current_y = new_y
+
+ def attempt_interact(self) -> None:
+ delta_x, delta_y = self.player.facing.value
+ entity_x, entity_y = self.player.current_x + delta_x, self.player.current_y + delta_y
+
+ entity = self.gameboard.get_entity_at(entity_x, entity_y)
+
+ if isinstance(entity, InteractableMixin):
+ entity.interact(self.player)
+
+ def attempt_fire_confetti_cannon(self) -> None:
+ if self.player.has_item(Item.CONFETTI_CANNON):
+ self.player.remove_item(Item.CONFETTI_CANNON)
+ self.queued_events.append(ConfettiFired(self.player.current_x, self.player.current_y))
+
+ def math_problem_success(self) -> None:
+ self.active_math_problem = None
+ self.active_math_problem_input = None
+ self.queued_events.append(MathProblemSolved())
+
+ @property
+ def currently_typed_in_math_result(self) -> int | None:
+ if not self.active_math_problem_input:
+ return None
+
+ number = self.active_math_problem_input[-1]
+ if len(self.active_math_problem_input) == 2:
+ number += self.active_math_problem_input[0] * 10
+
+ return number
+
+ def check_math_problem_result(self) -> None:
+ if self.active_math_problem is None:
+ return
+
+ if self.currently_typed_in_math_result == self.active_math_problem.result:
+ self.math_problem_success()
+
+ def math_problem_input(self, input: int) -> None:
+ if self.active_math_problem_input is None or len(self.active_math_problem_input) >= 2:
+ return
+
+ self.active_math_problem_input.append(input)
+ self.check_math_problem_result()
+
+ def math_problem_delete(self) -> None:
+ if self.active_math_problem_input is None or len(self.active_math_problem_input) == 0:
+ return
+ self.active_math_problem_input.pop()
+ self.check_math_problem_result()
+
+ def input(self, input_key: Input) -> None:
+ if not self.gameboard.ready:
+ return
+
+ if self.active_math_problem is not None:
+ if input_key in DIGIT_INPUTS_TO_DIGITS:
+ self.math_problem_input(DIGIT_INPUTS_TO_DIGITS[input_key])
+ if input_key == Input.BACKSPACE:
+ self.math_problem_delete()
+ return
+
+ if input_key == Input.LEFT:
+ self.attempt_player_movement(Direction.LEFT)
+ return
+
+ if input_key == Input.UP:
+ self.attempt_player_movement(Direction.UP)
+ return
+
+ if input_key == Input.RIGHT:
+ self.attempt_player_movement(Direction.RIGHT)
+ return
+
+ if input_key == Input.DOWN:
+ self.attempt_player_movement(Direction.DOWN)
+ return
+
+ if input_key == Input.ACTION:
+ self.attempt_interact()
+ return
+
+ if input_key == Input.CONFETTI:
+ self.attempt_fire_confetti_cannon()
+ return
+
+ raise ValueError(f"Don't know input {input_key}")
+
+ def receive_item(self, remote_item_id: int, remote_location_id: int, remote_location_player: int) -> None:
+ remotely_received_item = RemotelyReceivedItem(remote_item_id, remote_location_id, remote_location_player)
+ if remotely_received_item in self.remotely_received_items:
+ return
+
+ self.player.receive_item(Item(remote_item_id))
+
+ def force_clear_location(self, location_id: int) -> None:
+ location = Location(location_id)
+ self.gameboard.force_clear_location(location)
diff --git a/worlds/apquest/game/gameboard.py b/worlds/apquest/game/gameboard.py
new file mode 100644
index 000000000000..ec97491c872f
--- /dev/null
+++ b/worlds/apquest/game/gameboard.py
@@ -0,0 +1,267 @@
+from __future__ import annotations
+
+import random
+from collections.abc import Iterable
+from typing import TYPE_CHECKING
+
+from .entities import (
+ BreakableBlock,
+ Bush,
+ Button,
+ ButtonDoor,
+ Chest,
+ Empty,
+ Enemy,
+ EnemyWithLoot,
+ Entity,
+ FinalBoss,
+ KeyDoor,
+ LocationMixin,
+ Wall,
+)
+from .generate_math_problem import MathProblem
+from .graphics import DIGIT_TO_GRAPHIC, DIGIT_TO_GRAPHIC_ZERO_EMPTY, MATH_PROBLEM_TYPE_TO_GRAPHIC, Graphic
+from .items import Item
+from .locations import DEFAULT_CONTENT, Location
+
+if TYPE_CHECKING:
+ from .player import Player
+
+
+class Gameboard:
+ gameboard: tuple[tuple[Entity, ...], ...]
+
+ hammer_exists: bool
+ content_filled: bool
+
+ remote_entity_by_location_id: dict[Location, LocationMixin]
+
+ def __init__(self, gameboard: tuple[tuple[Entity, ...], ...], hammer_exists: bool) -> None:
+ assert gameboard, "Gameboard is empty"
+ assert all(len(row) == len(gameboard[0]) for row in gameboard), "Not all rows have the same size"
+
+ self.gameboard = gameboard
+ self.hammer_exists = hammer_exists
+ self.content_filled = False
+ self.remote_entity_by_location_id = {}
+
+ def fill_default_location_content(self, trap_percentage: int = 0) -> None:
+ for entity in self.iterate_entities():
+ if isinstance(entity, LocationMixin):
+ if entity.location in DEFAULT_CONTENT:
+ content = DEFAULT_CONTENT[entity.location]
+ if content == Item.HAMMER and not self.hammer_exists:
+ content = Item.CONFETTI_CANNON
+
+ if content == Item.CONFETTI_CANNON:
+ if random.randrange(100) < trap_percentage:
+ content = Item.MATH_TRAP
+
+ entity.content = content
+
+ self.content_filled = True
+
+ def fill_remote_location_content(self, graphic_overrides: dict[Location, Item]) -> None:
+ for entity in self.iterate_entities():
+ if isinstance(entity, LocationMixin):
+ entity.content = graphic_overrides.get(entity.location, Item.REMOTE_ITEM)
+ entity.remote = True
+ self.remote_entity_by_location_id[entity.location] = entity
+
+ self.content_filled = True
+
+ def get_entity_at(self, x: int, y: int) -> Entity:
+ if x < 0 or x >= len(self.gameboard[0]):
+ return Wall()
+ if y < 0 or y >= len(self.gameboard):
+ return Wall()
+
+ return self.gameboard[y][x]
+
+ def iterate_entities(self) -> Iterable[Entity]:
+ for row in self.gameboard:
+ yield from row
+
+ def respawn_final_boss(self) -> None:
+ for entity in self.iterate_entities():
+ if isinstance(entity, FinalBoss):
+ entity.respawn()
+
+ def heal_alive_enemies(self) -> None:
+ for entity in self.iterate_entities():
+ if isinstance(entity, Enemy):
+ entity.heal_if_not_dead()
+
+ def render(self, player: Player) -> tuple[tuple[Graphic, ...], ...]:
+ graphics = []
+
+ for y, row in enumerate(self.gameboard):
+ graphics_row = []
+ for x, entity in enumerate(row):
+ if player.current_x == x and player.current_y == y:
+ graphics_row.append(player.render())
+ else:
+ graphics_row.append(entity.graphic)
+
+ graphics.append(tuple(graphics_row))
+
+ return tuple(graphics)
+
+ def render_math_problem(
+ self, problem: MathProblem, current_input_digits: list[int], current_input_int: int | None
+ ) -> tuple[tuple[Graphic, ...], ...]:
+ rows = len(self.gameboard)
+ columns = len(self.gameboard[0])
+
+ def pad_row(row: list[Graphic]) -> tuple[Graphic, ...]:
+ row = row.copy()
+ while len(row) < columns:
+ row = [Graphic.EMPTY, *row, Graphic.EMPTY]
+ while len(row) > columns:
+ row.pop()
+
+ return tuple(row)
+
+ empty_row = tuple([Graphic.EMPTY] * columns)
+
+ math_time_row = pad_row(
+ [
+ Graphic.LETTER_M,
+ Graphic.LETTER_A,
+ Graphic.LETTER_T,
+ Graphic.LETTER_H,
+ Graphic.EMPTY,
+ Graphic.LETTER_T,
+ Graphic.LETTER_I,
+ Graphic.LETTER_M,
+ Graphic.LETTER_E,
+ ]
+ )
+
+ num_1_first_digit = problem.num_1 // 10
+ num_1_second_digit = problem.num_1 % 10
+ num_2_first_digit = problem.num_2 // 10
+ num_2_second_digit = problem.num_2 % 10
+
+ math_problem_row = pad_row(
+ [
+ DIGIT_TO_GRAPHIC_ZERO_EMPTY[num_1_first_digit],
+ DIGIT_TO_GRAPHIC[num_1_second_digit],
+ Graphic.EMPTY,
+ MATH_PROBLEM_TYPE_TO_GRAPHIC[problem.problem_type],
+ Graphic.EMPTY,
+ DIGIT_TO_GRAPHIC_ZERO_EMPTY[num_2_first_digit],
+ DIGIT_TO_GRAPHIC[num_2_second_digit],
+ ]
+ )
+
+ display_digit_1 = None
+ display_digit_2 = None
+ if current_input_digits:
+ display_digit_1 = current_input_digits[0]
+ if len(current_input_digits) == 2:
+ display_digit_2 = current_input_digits[1]
+
+ result_row = pad_row(
+ [
+ Graphic.EQUALS,
+ Graphic.EMPTY,
+ DIGIT_TO_GRAPHIC[display_digit_1],
+ DIGIT_TO_GRAPHIC[display_digit_2],
+ Graphic.EMPTY,
+ Graphic.NO if len(current_input_digits) == 2 and current_input_int != problem.result else Graphic.EMPTY,
+ ]
+ )
+
+ output = [math_time_row, empty_row, math_problem_row, result_row]
+
+ while len(output) < rows:
+ output = [empty_row, *output, empty_row]
+ while len(output) > columns:
+ output.pop(0)
+
+ return tuple(output)
+
+ def force_clear_location(self, location: Location) -> None:
+ entity = self.remote_entity_by_location_id[location]
+ entity.force_clear()
+
+ @property
+ def ready(self) -> bool:
+ return self.content_filled
+
+ @property
+ def size(self) -> tuple[int, int]:
+ return len(self.gameboard[0]), len(self.gameboard)
+
+
+def create_gameboard(hard_mode: bool, hammer_exists: bool, extra_chest: bool) -> Gameboard:
+ boss_door = ButtonDoor()
+ boss_door_button = Button(boss_door)
+
+ key_door = KeyDoor()
+
+ top_middle_chest = Chest(Location.TOP_MIDDLE_CHEST)
+ left_room_chest = Chest(Location.TOP_LEFT_CHEST)
+ bottom_left_chest = Chest(Location.BOTTOM_LEFT_CHEST)
+ bottom_right_room_left_chest = Chest(Location.BOTTOM_RIGHT_ROOM_LEFT_CHEST)
+ bottom_right_room_right_chest = Chest(Location.BOTTOM_RIGHT_ROOM_RIGHT_CHEST)
+
+ bottom_left_extra_chest = Chest(Location.BOTTOM_LEFT_EXTRA_CHEST) if extra_chest else Empty()
+ wall_if_hammer = Wall() if hammer_exists else Empty()
+ breakable_block = BreakableBlock() if hammer_exists else Empty()
+
+ normal_enemy = EnemyWithLoot(2 if hard_mode else 1, Location.ENEMY_DROP)
+ boss = FinalBoss(5 if hard_mode else 3)
+
+ gameboard = (
+ (Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty()),
+ (
+ Empty(),
+ boss_door_button,
+ Empty(),
+ Wall(),
+ Empty(),
+ top_middle_chest,
+ Empty(),
+ Wall(),
+ Empty(),
+ boss,
+ Empty(),
+ ),
+ (Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty()),
+ (
+ Empty(),
+ left_room_chest,
+ Empty(),
+ Wall(),
+ wall_if_hammer,
+ breakable_block,
+ wall_if_hammer,
+ Wall(),
+ Wall(),
+ boss_door,
+ Wall(),
+ ),
+ (Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty()),
+ (Wall(), key_door, Wall(), Wall(), Empty(), Empty(), Empty(), Empty(), Empty(), normal_enemy, Empty()),
+ (Empty(), Empty(), Empty(), Empty(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty()),
+ (Empty(), bottom_left_extra_chest, Empty(), Empty(), Empty(), Empty(), Wall(), Wall(), Wall(), Wall(), Wall()),
+ (Empty(), Empty(), Empty(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Empty()),
+ (
+ Empty(),
+ bottom_left_chest,
+ Empty(),
+ Empty(),
+ Empty(),
+ Empty(),
+ Bush(),
+ Empty(),
+ bottom_right_room_left_chest,
+ bottom_right_room_right_chest,
+ Empty(),
+ ),
+ (Empty(), Empty(), Empty(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Empty()),
+ )
+
+ return Gameboard(gameboard, hammer_exists)
diff --git a/worlds/apquest/game/generate_math_problem.py b/worlds/apquest/game/generate_math_problem.py
new file mode 100644
index 000000000000..eb8ff0f01ec9
--- /dev/null
+++ b/worlds/apquest/game/generate_math_problem.py
@@ -0,0 +1,63 @@
+import random
+from collections.abc import Callable
+from enum import Enum
+from operator import add, mul, sub, truediv
+from typing import NamedTuple
+
+_random = random.Random()
+
+class NumberChoiceConstraints(NamedTuple):
+ num_1_min: int
+ num_1_max: int
+ num_2_min: int
+ num_2_max: int
+ commutative: bool
+ operator: Callable[[int, int], int | float]
+
+
+class MathProblemType(Enum):
+ PLUS = 1
+ MINUS = 2
+ TIMES = 3
+ DIVIDE = 4
+
+
+MATH_PROBLEM_CONSTRAINTS = {
+ MathProblemType.PLUS: NumberChoiceConstraints(1, 99, 1, 99, True, add),
+ MathProblemType.MINUS: NumberChoiceConstraints(2, 99, 1, 99, False, sub),
+ MathProblemType.TIMES: NumberChoiceConstraints(2, 10, 2, 50, True, mul),
+ MathProblemType.DIVIDE: NumberChoiceConstraints(4, 99, 2, 50, False, truediv),
+}
+
+
+class MathProblem(NamedTuple):
+ problem_type: MathProblemType
+ num_1: int
+ num_2: int
+ result: int
+
+
+def generate_math_problem(random_object: random.Random = _random) -> MathProblem:
+ problem_type: MathProblemType = random_object.choice(list(MathProblemType))
+ number_choice_constraints = MATH_PROBLEM_CONSTRAINTS[problem_type]
+
+ for _ in range(10000):
+ num_1 = random.randint(number_choice_constraints.num_1_min, number_choice_constraints.num_1_max)
+ num_2 = random.randint(number_choice_constraints.num_2_min, number_choice_constraints.num_2_max)
+
+ result = number_choice_constraints.operator(num_1, num_2)
+
+ result_int = int(result)
+ if not result_int == result:
+ continue
+
+ if result_int < 2 or result_int > 99:
+ continue
+
+ if number_choice_constraints.commutative:
+ if random.randint(0, 1):
+ num_1, num_2 = num_2, num_1
+
+ return MathProblem(problem_type, num_1, num_2, result_int)
+
+ return MathProblem(MathProblemType.PLUS, 1, 1, 2)
diff --git a/worlds/apquest/game/graphics.py b/worlds/apquest/game/graphics.py
new file mode 100644
index 000000000000..9279c6c7b4fa
--- /dev/null
+++ b/worlds/apquest/game/graphics.py
@@ -0,0 +1,99 @@
+from enum import Enum
+
+from .generate_math_problem import MathProblemType
+
+
+class Graphic(Enum):
+ EMPTY = 0
+ WALL = 1
+ BUTTON_NOT_ACTIVATED = 2
+ BUTTON_ACTIVATED = 3
+ KEY_DOOR = 4
+ BUTTON_DOOR = 5
+ CHEST = 6
+ BUSH = 7
+ BREAKABLE_BLOCK = 8
+
+ NORMAL_ENEMY_1_HEALTH = 10
+ NORMAL_ENEMY_2_HEALTH = 11
+
+ BOSS_1_HEALTH = 20
+ BOSS_2_HEALTH = 21
+ BOSS_3_HEALTH = 22
+ BOSS_4_HEALTH = 23
+ BOSS_5_HEALTH = 24
+
+ PLAYER_DOWN = 30
+ PLAYER_UP = 31
+ PLAYER_LEFT = 32
+ PLAYER_RIGHT = 33
+
+ KEY = 41
+ SWORD = 42
+ SHIELD = 43
+ HAMMER = 44
+
+ HEART = 50
+ HALF_HEART = 51
+ EMPTY_HEART = 52
+
+ CONFETTI_CANNON = 60
+
+ REMOTE_ITEM = 70
+
+ ITEMS_TEXT = 80
+
+ MATH_TRAP = CONFETTI_CANNON
+
+ ZERO = 1000
+ ONE = 1001
+ TWO = 1002
+ THREE = 1003
+ FOUR = 1004
+ FIVE = 1005
+ SIX = 1006
+ SEVEN = 1007
+ EIGHT = 1008
+ NINE = 1009
+
+ PLUS = 1100
+ MINUS = 1101
+ TIMES = 1102
+ DIVIDE = 1103
+
+ LETTER_A = 2000
+ LETTER_E = 2005
+ LETTER_H = 2008
+ LETTER_I = 2009
+ LETTER_M = 2013
+ LETTER_T = 2019
+
+ EQUALS = 2050
+ NO = 2060
+
+ UNKNOWN = -1
+
+
+DIGIT_TO_GRAPHIC = {
+ None: Graphic.EMPTY,
+ 0: Graphic.ZERO,
+ 1: Graphic.ONE,
+ 2: Graphic.TWO,
+ 3: Graphic.THREE,
+ 4: Graphic.FOUR,
+ 5: Graphic.FIVE,
+ 6: Graphic.SIX,
+ 7: Graphic.SEVEN,
+ 8: Graphic.EIGHT,
+ 9: Graphic.NINE,
+}
+
+DIGIT_TO_GRAPHIC_ZERO_EMPTY = DIGIT_TO_GRAPHIC.copy()
+DIGIT_TO_GRAPHIC_ZERO_EMPTY[0] = Graphic.EMPTY
+
+MATH_PROBLEM_TYPE_TO_GRAPHIC = {
+ MathProblemType.PLUS: Graphic.PLUS,
+ MathProblemType.MINUS: Graphic.MINUS,
+ MathProblemType.TIMES: Graphic.TIMES,
+ MathProblemType.DIVIDE: Graphic.DIVIDE,
+}
diff --git a/worlds/apquest/game/graphics/boss.png b/worlds/apquest/game/graphics/boss.png
new file mode 100644
index 000000000000..dbcda31048e2
Binary files /dev/null and b/worlds/apquest/game/graphics/boss.png differ
diff --git a/worlds/apquest/game/graphics/cat.png b/worlds/apquest/game/graphics/cat.png
new file mode 100644
index 000000000000..aa5e854753e0
Binary files /dev/null and b/worlds/apquest/game/graphics/cat.png differ
diff --git a/worlds/apquest/game/graphics/duck.png b/worlds/apquest/game/graphics/duck.png
new file mode 100644
index 000000000000..5e07b70dfc5f
Binary files /dev/null and b/worlds/apquest/game/graphics/duck.png differ
diff --git a/worlds/apquest/game/graphics/hearts.png b/worlds/apquest/game/graphics/hearts.png
new file mode 100644
index 000000000000..0ab344898f81
Binary files /dev/null and b/worlds/apquest/game/graphics/hearts.png differ
diff --git a/worlds/apquest/game/graphics/horse.png b/worlds/apquest/game/graphics/horse.png
new file mode 100644
index 000000000000..349c256688be
Binary files /dev/null and b/worlds/apquest/game/graphics/horse.png differ
diff --git a/worlds/apquest/game/graphics/human.png b/worlds/apquest/game/graphics/human.png
new file mode 100644
index 000000000000..6978bf0ac5ef
Binary files /dev/null and b/worlds/apquest/game/graphics/human.png differ
diff --git a/worlds/apquest/game/graphics/inanimates.png b/worlds/apquest/game/graphics/inanimates.png
new file mode 100644
index 000000000000..ba7c143b9822
Binary files /dev/null and b/worlds/apquest/game/graphics/inanimates.png differ
diff --git a/worlds/apquest/game/graphics/items.png b/worlds/apquest/game/graphics/items.png
new file mode 100644
index 000000000000..ce66feda7788
Binary files /dev/null and b/worlds/apquest/game/graphics/items.png differ
diff --git a/worlds/apquest/game/graphics/items_text.png b/worlds/apquest/game/graphics/items_text.png
new file mode 100644
index 000000000000..4b5a4f30ded3
Binary files /dev/null and b/worlds/apquest/game/graphics/items_text.png differ
diff --git a/worlds/apquest/game/graphics/letters.png b/worlds/apquest/game/graphics/letters.png
new file mode 100644
index 000000000000..8ec3bae065a8
Binary files /dev/null and b/worlds/apquest/game/graphics/letters.png differ
diff --git a/worlds/apquest/game/graphics/normal_enemy.png b/worlds/apquest/game/graphics/normal_enemy.png
new file mode 100644
index 000000000000..baed65d8cc33
Binary files /dev/null and b/worlds/apquest/game/graphics/normal_enemy.png differ
diff --git a/worlds/apquest/game/graphics/numbers.png b/worlds/apquest/game/graphics/numbers.png
new file mode 100644
index 000000000000..97e19bea85b6
Binary files /dev/null and b/worlds/apquest/game/graphics/numbers.png differ
diff --git a/worlds/apquest/game/graphics/symbols.png b/worlds/apquest/game/graphics/symbols.png
new file mode 100644
index 000000000000..6f9e0436994b
Binary files /dev/null and b/worlds/apquest/game/graphics/symbols.png differ
diff --git a/worlds/apquest/game/inputs.py b/worlds/apquest/game/inputs.py
new file mode 100644
index 000000000000..eb736a606f08
--- /dev/null
+++ b/worlds/apquest/game/inputs.py
@@ -0,0 +1,44 @@
+from enum import Enum
+
+
+class Direction(Enum):
+ LEFT = (-1, 0)
+ UP = (0, -1)
+ RIGHT = (1, 0)
+ DOWN = (0, 1)
+
+
+class Input(Enum):
+ LEFT = 1
+ UP = 2
+ RIGHT = 3
+ DOWN = 4
+ ACTION = 5
+ CONFETTI = 6
+
+ ZERO = 1000
+ ONE = 1001
+ TWO = 1002
+ THREE = 1003
+ FOUR = 1004
+ FIVE = 1005
+ SIX = 1006
+ SEVEN = 1007
+ EIGHT = 1008
+ NINE = 1009
+
+ BACKSPACE = 2000
+
+
+DIGIT_INPUTS_TO_DIGITS = {
+ Input.ZERO: 0,
+ Input.ONE: 1,
+ Input.TWO: 2,
+ Input.THREE: 3,
+ Input.FOUR: 4,
+ Input.FIVE: 5,
+ Input.SIX: 6,
+ Input.SEVEN: 7,
+ Input.EIGHT: 8,
+ Input.NINE: 9,
+}
diff --git a/worlds/apquest/game/items.py b/worlds/apquest/game/items.py
new file mode 100644
index 000000000000..a37d0e654f6b
--- /dev/null
+++ b/worlds/apquest/game/items.py
@@ -0,0 +1,38 @@
+from collections import defaultdict
+from enum import Enum
+from typing import NamedTuple
+
+from ..items import ITEM_NAME_TO_ID
+from .graphics import Graphic
+
+
+class Item(Enum):
+ KEY = ITEM_NAME_TO_ID["Key"]
+ SWORD = ITEM_NAME_TO_ID["Sword"]
+ SHIELD = ITEM_NAME_TO_ID["Shield"]
+ HAMMER = ITEM_NAME_TO_ID["Hammer"]
+ HEALTH_UPGRADE = ITEM_NAME_TO_ID["Health Upgrade"]
+ CONFETTI_CANNON = ITEM_NAME_TO_ID["Confetti Cannon"]
+ MATH_TRAP = ITEM_NAME_TO_ID["Math Trap"]
+ REMOTE_ITEM = -1
+
+
+class RemotelyReceivedItem(NamedTuple):
+ remote_item_id: int
+ remote_location_id: int
+ remote_location_player: int
+
+
+ITEM_TO_GRAPHIC = defaultdict(
+ lambda: Graphic.UNKNOWN,
+ {
+ Item.KEY: Graphic.KEY,
+ Item.SWORD: Graphic.SWORD,
+ Item.SHIELD: Graphic.SHIELD,
+ Item.HAMMER: Graphic.HAMMER,
+ Item.HEALTH_UPGRADE: Graphic.HEART,
+ Item.CONFETTI_CANNON: Graphic.CONFETTI_CANNON,
+ Item.REMOTE_ITEM: Graphic.REMOTE_ITEM,
+ Item.MATH_TRAP: Graphic.MATH_TRAP,
+ },
+)
diff --git a/worlds/apquest/game/locations.py b/worlds/apquest/game/locations.py
new file mode 100644
index 000000000000..b8aaa77e7c85
--- /dev/null
+++ b/worlds/apquest/game/locations.py
@@ -0,0 +1,25 @@
+from enum import Enum
+
+from ..locations import LOCATION_NAME_TO_ID
+from .items import Item
+
+
+class Location(Enum):
+ TOP_LEFT_CHEST = LOCATION_NAME_TO_ID["Top Left Room Chest"]
+ TOP_MIDDLE_CHEST = LOCATION_NAME_TO_ID["Top Middle Chest"]
+ BOTTOM_LEFT_CHEST = LOCATION_NAME_TO_ID["Bottom Left Chest"]
+ BOTTOM_LEFT_EXTRA_CHEST = LOCATION_NAME_TO_ID["Bottom Left Extra Chest"]
+ BOTTOM_RIGHT_ROOM_LEFT_CHEST = LOCATION_NAME_TO_ID["Bottom Right Room Left Chest"]
+ BOTTOM_RIGHT_ROOM_RIGHT_CHEST = LOCATION_NAME_TO_ID["Bottom Right Room Right Chest"]
+ ENEMY_DROP = LOCATION_NAME_TO_ID["Right Room Enemy Drop"]
+
+
+DEFAULT_CONTENT = {
+ Location.TOP_LEFT_CHEST: Item.HEALTH_UPGRADE,
+ Location.TOP_MIDDLE_CHEST: Item.HEALTH_UPGRADE,
+ Location.BOTTOM_LEFT_CHEST: Item.SWORD,
+ Location.BOTTOM_LEFT_EXTRA_CHEST: Item.CONFETTI_CANNON,
+ Location.BOTTOM_RIGHT_ROOM_LEFT_CHEST: Item.SHIELD,
+ Location.BOTTOM_RIGHT_ROOM_RIGHT_CHEST: Item.HAMMER,
+ Location.ENEMY_DROP: Item.KEY,
+}
diff --git a/worlds/apquest/game/play_in_console.py b/worlds/apquest/game/play_in_console.py
new file mode 100644
index 000000000000..9e07896f651c
--- /dev/null
+++ b/worlds/apquest/game/play_in_console.py
@@ -0,0 +1,143 @@
+from .events import ConfettiFired, MathProblemSolved
+
+try:
+ from pynput import keyboard
+ from pynput.keyboard import Key, KeyCode
+except ImportError as e:
+ raise ImportError("In order to play APQuest from console, you have to install pynput.") from e
+
+from .game import Game
+from .graphics import Graphic
+from .inputs import Input
+from .items import ITEM_TO_GRAPHIC
+
+graphic_to_char = {
+ Graphic.EMPTY: " ",
+ Graphic.WALL: "W",
+ Graphic.BUTTON_NOT_ACTIVATED: "B",
+ Graphic.BUTTON_ACTIVATED: "A",
+ Graphic.KEY_DOOR: "D",
+ Graphic.BUTTON_DOOR: "?",
+ Graphic.CHEST: "C",
+ Graphic.BUSH: "T",
+ Graphic.BREAKABLE_BLOCK: "~",
+ Graphic.NORMAL_ENEMY_2_HEALTH: "2",
+ Graphic.NORMAL_ENEMY_1_HEALTH: "1",
+ Graphic.BOSS_5_HEALTH: "5",
+ Graphic.BOSS_4_HEALTH: "4",
+ Graphic.BOSS_3_HEALTH: "3",
+ Graphic.BOSS_2_HEALTH: "2",
+ Graphic.BOSS_1_HEALTH: "1",
+ Graphic.PLAYER_DOWN: "v",
+ Graphic.PLAYER_UP: "^",
+ Graphic.PLAYER_LEFT: "<",
+ Graphic.PLAYER_RIGHT: ">",
+ Graphic.KEY: "K",
+ Graphic.SHIELD: "X",
+ Graphic.SWORD: "S",
+ Graphic.HAMMER: "H",
+ Graphic.HEART: "♡",
+ Graphic.CONFETTI_CANNON: "?",
+ Graphic.REMOTE_ITEM: "I",
+ Graphic.UNKNOWN: "ß",
+ Graphic.ZERO: "0",
+ Graphic.ONE: "1",
+ Graphic.TWO: "2",
+ Graphic.THREE: "3",
+ Graphic.FOUR: "4",
+ Graphic.FIVE: "5",
+ Graphic.SIX: "6",
+ Graphic.SEVEN: "7",
+ Graphic.EIGHT: "8",
+ Graphic.NINE: "9",
+ Graphic.PLUS: "+",
+ Graphic.MINUS: "-",
+ Graphic.TIMES: "x",
+ Graphic.DIVIDE: "/",
+ Graphic.LETTER_A: "A",
+ Graphic.LETTER_E: "E",
+ Graphic.LETTER_H: "H",
+ Graphic.LETTER_I: "I",
+ Graphic.LETTER_M: "M",
+ Graphic.LETTER_T: "T",
+ Graphic.EQUALS: "=",
+ Graphic.NO: "X",
+}
+
+KEY_CONVERSION = {
+ keyboard.KeyCode.from_char("w"): Input.UP,
+ Key.up: Input.UP,
+ keyboard.KeyCode.from_char("s"): Input.DOWN,
+ Key.down: Input.DOWN,
+ keyboard.KeyCode.from_char("a"): Input.LEFT,
+ Key.left: Input.LEFT,
+ keyboard.KeyCode.from_char("d"): Input.RIGHT,
+ Key.right: Input.RIGHT,
+ Key.space: Input.ACTION,
+ keyboard.KeyCode.from_char("c"): Input.CONFETTI,
+ keyboard.KeyCode.from_char("0"): Input.ZERO,
+ keyboard.KeyCode.from_char("1"): Input.ONE,
+ keyboard.KeyCode.from_char("2"): Input.TWO,
+ keyboard.KeyCode.from_char("3"): Input.THREE,
+ keyboard.KeyCode.from_char("4"): Input.FOUR,
+ keyboard.KeyCode.from_char("5"): Input.FIVE,
+ keyboard.KeyCode.from_char("6"): Input.SIX,
+ keyboard.KeyCode.from_char("7"): Input.SEVEN,
+ keyboard.KeyCode.from_char("8"): Input.EIGHT,
+ keyboard.KeyCode.from_char("9"): Input.NINE,
+ Key.backspace: Input.BACKSPACE,
+}
+
+
+def render_to_text(game: Game) -> str:
+ player = game.player
+ rendered_graphics = game.render()
+
+ output_string = f"Health: {player.current_health}/{player.max_health}\n"
+
+ inventory = []
+ for item, count in player.inventory.items():
+ inventory += [graphic_to_char[ITEM_TO_GRAPHIC[item]] for _ in range(count)]
+ inventory.sort()
+
+ output_string += f"Inventory: {', '.join(inventory)}\n"
+
+ if player.has_won:
+ output_string += "VICTORY!!!\n"
+
+ while game.queued_events:
+ next_event = game.queued_events.pop(0)
+ if isinstance(next_event, ConfettiFired):
+ output_string += "Confetti fired! You feel motivated :)\n"
+ if isinstance(next_event, MathProblemSolved):
+ output_string += "Math problem solved!\n"
+
+ for row in rendered_graphics:
+ output_string += " ".join(graphic_to_char[graphic] for graphic in row)
+ output_string += "\n"
+
+ return output_string
+
+
+if __name__ == "__main__":
+ hard_mode = input("Do you want to play hard mode? (Y/N)").lower().strip() in ("y", "yes")
+ hammer_exists = input("Do you want the hammer to exist in the game? (Y/N)").lower().strip() in ("y", "yes")
+ extra_chest = input("Do you want the extra starting chest to exist in the game?").lower().strip() in ("y", "yes")
+ math_trap_percentage = int(input("What should the percentage of math traps be?"))
+
+ game = Game(hard_mode, hammer_exists, extra_chest)
+ game.gameboard.fill_default_location_content(math_trap_percentage)
+
+ def input_and_rerender(input_key: Input) -> None:
+ game.input(input_key)
+ print(render_to_text(game))
+
+ def on_press(key: Key | KeyCode | None) -> None:
+ if key in KEY_CONVERSION:
+ input_and_rerender(KEY_CONVERSION[key])
+
+ print(render_to_text(game))
+
+ with keyboard.Listener(on_press=on_press) as listener:
+ while True:
+ listener.join()
diff --git a/worlds/apquest/game/player.py b/worlds/apquest/game/player.py
new file mode 100644
index 000000000000..ff0f45b367be
--- /dev/null
+++ b/worlds/apquest/game/player.py
@@ -0,0 +1,87 @@
+from collections import Counter
+from collections.abc import Callable
+
+from .events import Event, LocationClearedEvent, VictoryEvent
+from .gameboard import Gameboard
+from .graphics import Graphic
+from .inputs import Direction
+from .items import Item
+
+
+class Player:
+ current_x: int
+ current_y: int
+ current_health: int
+
+ has_won: bool = False
+
+ facing: Direction
+
+ inventory: Counter[Item]
+
+ gameboard: Gameboard
+ push_event: Callable[[Event], None]
+
+ def __init__(self, gameboard: Gameboard, push_event: Callable[[Event], None]) -> None:
+ self.gameboard = gameboard
+ self.inventory = Counter()
+ self.push_event = push_event
+ self.respawn()
+
+ def respawn(self) -> None:
+ self.current_x = 4
+ self.current_y = 9
+ self.current_health = self.max_health
+ self.facing = Direction.DOWN
+
+ @property
+ def max_health(self) -> int:
+ return 2 + 2 * self.inventory[Item.HEALTH_UPGRADE]
+
+ def render(self) -> Graphic:
+ if not self.gameboard.ready:
+ return Graphic.EMPTY
+
+ if self.facing == Direction.LEFT:
+ return Graphic.PLAYER_LEFT
+ if self.facing == Direction.UP:
+ return Graphic.PLAYER_UP
+ if self.facing == Direction.RIGHT:
+ return Graphic.PLAYER_RIGHT
+ return Graphic.PLAYER_DOWN
+
+ def receive_item(self, item: Item) -> None:
+ assert item != Item.REMOTE_ITEM, "Player should not directly receive the remote item"
+
+ self.inventory[item] += 1
+ if item == Item.HEALTH_UPGRADE:
+ self.current_health += 2
+
+ def has_item(self, item: Item) -> bool:
+ return self.inventory[item] > 0
+
+ def remove_item(self, item: Item) -> None:
+ self.inventory[item] -= 1
+
+ def damage(self, damage: int) -> None:
+ if self.has_item(Item.SHIELD):
+ damage = damage // 2
+
+ self.current_health = max(0, self.current_health - damage)
+
+ if self.current_health <= 0:
+ self.die()
+
+ def die(self) -> None:
+ self.respawn()
+ self.gameboard.respawn_final_boss()
+ self.gameboard.heal_alive_enemies()
+
+ def location_cleared(self, location_id: int) -> None:
+ event = LocationClearedEvent(location_id)
+ self.push_event(event)
+
+ def victory(self) -> None:
+ self.has_won = True
+ event = VictoryEvent()
+ self.push_event(event)
diff --git a/worlds/apquest/items.py b/worlds/apquest/items.py
new file mode 100644
index 000000000000..abe3dda70b8a
--- /dev/null
+++ b/worlds/apquest/items.py
@@ -0,0 +1,166 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from BaseClasses import Item, ItemClassification
+
+if TYPE_CHECKING:
+ from .world import APQuestWorld
+
+# Every item must have a unique integer ID associated with it.
+# We will have a lookup from item name to ID here that, in world.py, we will import and bind to the world class.
+# Even if an item doesn't exist on specific options, it must be present in this lookup.
+ITEM_NAME_TO_ID = {
+ "Key": 1,
+ "Sword": 2,
+ "Shield": 3,
+ "Hammer": 4,
+ "Health Upgrade": 5,
+ "Confetti Cannon": 6,
+ "Math Trap": 7,
+}
+
+# Items should have a defined default classification.
+# In our case, we will make a dictionary from item name to classification.
+DEFAULT_ITEM_CLASSIFICATIONS = {
+ "Key": ItemClassification.progression,
+ "Sword": ItemClassification.progression | ItemClassification.useful, # Items can have multiple classifications.
+ "Shield": ItemClassification.progression,
+ "Hammer": ItemClassification.progression,
+ "Health Upgrade": ItemClassification.useful,
+ "Confetti Cannon": ItemClassification.filler,
+ "Math Trap": ItemClassification.trap,
+}
+
+
+# Each Item instance must correctly report the "game" it belongs to.
+# To make this simple, it is common practice to subclass the basic Item class and override the "game" field.
+class APQuestItem(Item):
+ game = "APQuest"
+
+
+# Ontop of our regular itempool, our world must be able to create arbitrary amounts of filler as requested by core.
+# To do this, it must define a function called world.get_filler_item_name(), which we will define in world.py later.
+# For now, let's make a function that returns the name of a random filler item here in items.py.
+def get_random_filler_item_name(world: APQuestWorld) -> str:
+ # APQuest has an option called "trap_chance".
+ # This is the percentage chance that each filler item is a Math Trap instead of a Confetti Cannon.
+ # For this purpose, we need to use a random generator.
+
+ # IMPORTANT: Whenever you need to use a random generator, you must use world.random.
+ # This ensures that generating with the same generator seed twice yields the same output.
+ # DO NOT use a bare random object from Python's built-in random module.
+ if world.random.randint(0, 99) < world.options.trap_chance:
+ return "Math Trap"
+ return "Confetti Cannon"
+
+
+def create_item_with_correct_classification(world: APQuestWorld, name: str) -> APQuestItem:
+ # Our world class must have a create_item() function that can create any of our items by name at any time.
+ # So, we make this helper function that creates the item by name with the correct classification.
+ # Note: This function's content could just be the contents of world.create_item in world.py directly,
+ # but it seemed nicer to have it in its own function over here in items.py.
+ classification = DEFAULT_ITEM_CLASSIFICATIONS[name]
+
+ # It is perfectly normal and valid for an item's classification to differ based on the player's options.
+ # In our case, Health Upgrades are only relevant to logic (and thus labeled as "progression") in hard mode.
+ if name == "Health Upgrade" and world.options.hard_mode:
+ classification = ItemClassification.progression
+
+ return APQuestItem(name, classification, ITEM_NAME_TO_ID[name], world.player)
+
+
+# With those two helper functions defined, let's now get to actually creating and submitting our itempool.
+def create_all_items(world: APQuestWorld) -> None:
+ # This is the function in which we will create all the items that this world submits to the multiworld item pool.
+ # There must be exactly as many items as there are locations.
+ # In our case, there are either six or seven locations.
+ # We must make sure that when there are six locations, there are six items,
+ # and when there are seven locations, there are seven items.
+
+ # Creating items should generally be done via the world's create_item method.
+ # First, we create a list containing all the items that always exist.
+
+ itempool: list[Item] = [
+ world.create_item("Key"),
+ world.create_item("Sword"),
+ world.create_item("Shield"),
+ world.create_item("Health Upgrade"),
+ world.create_item("Health Upgrade"),
+ ]
+
+ # Some items may only exist if the player enables certain options.
+ # In our case, If the hammer option is enabled, the sixth item is the Hammer.
+ # Otherwise, we add a filler Confetti Cannon.
+ if world.options.hammer:
+ # Once again, it is important to stress that even though the Hammer doesn't always exist,
+ # it must be present in the worlds item_name_to_id.
+ # Whether it is actually in the itempool is determined purely by whether we create and add the item here.
+ itempool.append(world.create_item("Hammer"))
+
+ # Archipelago requires that each world submits as many locations as it submits items.
+ # This is where we can use our filler and trap items.
+ # APQuest has two of these: The Confetti Cannon and the Math Trap.
+ # (Unfortunately, Archipelago is a bit ambiguous about its terminology here:
+ # "filler" is an ItemClassification separate from "trap", but in a lot of its functions,
+ # Archipelago will use "filler" to just mean "an additional item created to fill out the itempool".
+ # "Filler" in this sense can technically have any ItemClassification,
+ # but most commonly ItemClassification.filler or ItemClassification.trap.
+ # Starting here, the word "filler" will be used to collectively refer to APQuest's Confetti Cannon and Math Trap,
+ # which are ItemClassification.filler and ItemClassification.trap respectively.)
+ # Creating filler items works the same as any other item. But there is a question:
+ # How many filler items do we actually need to create?
+ # In regions.py, we created either six or seven locations depending on the "extra_starting_chest" option.
+ # In this function, we have created five or six items depending on whether the "hammer" option is enabled.
+ # We *could* have a really complicated if-else tree checking the options again, but there is a better way.
+ # We can compare the size of our itempool so far to the number of locations in our world.
+
+ # The length of our itempool is easy to determine, since we have it as a list.
+ number_of_items = len(itempool)
+
+ # The number of locations is also easy to determine, but we have to be careful.
+ # Just calling len(world.get_locations()) would report an incorrect number, because of our *event locations*.
+ # What we actually want is the number of *unfilled* locations. Luckily, there is a helper method for this:
+ number_of_unfilled_locations = len(world.multiworld.get_unfilled_locations(world.player))
+
+ # Now, we just subtract the number of items from the number of locations to get the number of empty item slots.
+ needed_number_of_filler_items = number_of_unfilled_locations - number_of_items
+
+ # Finally, we create that many filler items and add them to the itempool.
+ # To create our filler, we could just use world.create_item("Confetti Cannon").
+ # But there is an alternative that works even better for most worlds, including APQuest.
+ # As discussed above, our world must have a get_filler_item_name() function defined,
+ # which must return the name of an infinitely repeatable filler item.
+ # Defining this function enables the use of a helper function called world.create_filler().
+ # You can just use this function directly to create as many filler items as you need to complete your itempool.
+ itempool += [world.create_filler() for _ in range(needed_number_of_filler_items)]
+
+ # But... is that the right option for your game? Let's explore that.
+ # For some games, the concepts of "regular itempool filler" and "additionally created filler" are different.
+ # These games might want / require specific amounts of specific filler items in their regular pool.
+ # To achieve this, they will have to intentionally create the correct quantities using world.create_item().
+ # They may still use world.create_filler() to fill up the rest of their itempool with "repeatable filler",
+ # after creating their "specific quantity" filler and still having room left over.
+
+ # But there are many other games which *only* have infinitely repeatable filler items.
+ # They don't care about specific amounts of specific filler items, instead only caring about the proportions.
+ # In this case, world.create_filler() can just be used for the entire filler itempool.
+ # APQuest is one of these games:
+ # Regardless of whether it's filler for the regular itempool or additional filler for item links / etc.,
+ # we always just want a Confetti Cannon or a Math Trap depending on the "trap_chance" option.
+ # We defined this behavior in our get_random_filler_item_name() function, which in world.py,
+ # we'll bind to world.get_filler_item_name(). So, we can just use world.create_filler() for all of our filler.
+
+ # Anyway. With our world's itempool finalized, we now need to submit it to the multiworld itempool.
+ # This is how the generator actually knows about the existence of our items.
+ world.multiworld.itempool += itempool
+
+ # Sometimes, you might want the player to start with certain items already in their inventory.
+ # These items are called "precollected items".
+ # They will be sent as soon as they connect for the first time (depending on your client's item handling flag).
+ # Players can add precollected items themselves via the generic "start_inventory" option.
+ # If you want to add your own precollected items, you can do so via world.push_precollected().
+ if world.options.start_with_one_confetti_cannon:
+ # We're adding a filler item, but you can also add progression items to the player's precollected inventory.
+ starting_confetti_cannon = world.create_item("Confetti Cannon")
+ world.push_precollected(starting_confetti_cannon)
diff --git a/worlds/apquest/locations.py b/worlds/apquest/locations.py
new file mode 100644
index 000000000000..553519fa6c8e
--- /dev/null
+++ b/worlds/apquest/locations.py
@@ -0,0 +1,136 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from BaseClasses import ItemClassification, Location
+
+from . import items
+
+if TYPE_CHECKING:
+ from .world import APQuestWorld
+
+# Every location must have a unique integer ID associated with it.
+# We will have a lookup from location name to ID here that, in world.py, we will import and bind to the world class.
+# Even if a location doesn't exist on specific options, it must be present in this lookup.
+LOCATION_NAME_TO_ID = {
+ "Top Left Room Chest": 1,
+ "Top Middle Chest": 2,
+ "Bottom Left Chest": 3,
+ "Bottom Left Extra Chest": 4,
+ "Bottom Right Room Left Chest": 5,
+ "Bottom Right Room Right Chest": 6,
+ # Location IDs don't need to be sequential, as long as they're unique and greater than 0.
+ "Right Room Enemy Drop": 10,
+}
+
+
+# Each Location instance must correctly report the "game" it belongs to.
+# To make this simple, it is common practice to subclass the basic Location class and override the "game" field.
+class APQuestLocation(Location):
+ game = "APQuest"
+
+
+# Let's make one more helper method before we begin actually creating locations.
+# Later on in the code, we'll want specific subsections of LOCATION_NAME_TO_ID.
+# To reduce the chance of copy-paste errors writing something like {"Chest": LOCATION_NAME_TO_ID["Chest"]},
+# let's make a helper method that takes a list of location names and returns them as a dict with their IDs.
+# Note: There is a minor typing quirk here. Some functions want location addresses to be an "int | None",
+# so while our function here only ever returns dict[str, int], we annotate it as dict[str, int | None].
+def get_location_names_with_ids(location_names: list[str]) -> dict[str, int | None]:
+ return {location_name: LOCATION_NAME_TO_ID[location_name] for location_name in location_names}
+
+
+def create_all_locations(world: APQuestWorld) -> None:
+ create_regular_locations(world)
+ create_events(world)
+
+
+def create_regular_locations(world: APQuestWorld) -> None:
+ # Finally, we need to put the Locations ("checks") into their regions.
+ # Once again, before we do anything, we can grab our regions we created by using world.get_region()
+ overworld = world.get_region("Overworld")
+ top_left_room = world.get_region("Top Left Room")
+ bottom_right_room = world.get_region("Bottom Right Room")
+ right_room = world.get_region("Right Room")
+
+ # One way to create locations is by just creating them directly via their constructor.
+ bottom_left_chest = APQuestLocation(
+ world.player, "Bottom Left Chest", world.location_name_to_id["Bottom Left Chest"], overworld
+ )
+
+ # You can then add them to the region.
+ overworld.locations.append(bottom_left_chest)
+
+ # A simpler way to do this is by using the region.add_locations helper.
+ # For this, you need to have a dict of location names to their IDs (i.e. a subset of location_name_to_id)
+ # Aha! So that's why we made that "get_location_names_with_ids" helper method earlier.
+ # You also need to pass your overridden Location class.
+ bottom_right_room_locations = get_location_names_with_ids(
+ ["Bottom Right Room Left Chest", "Bottom Right Room Right Chest"]
+ )
+ bottom_right_room.add_locations(bottom_right_room_locations, APQuestLocation)
+
+ top_left_room_locations = get_location_names_with_ids(["Top Left Room Chest"])
+ top_left_room.add_locations(top_left_room_locations, APQuestLocation)
+
+ right_room_locations = get_location_names_with_ids(["Right Room Enemy Drop"])
+ right_room.add_locations(right_room_locations, APQuestLocation)
+
+ # Locations may be in different regions depending on the player's options.
+ # In our case, the hammer option puts the Top Middle Chest into its own room called Top Middle Room.
+ top_middle_room_locations = get_location_names_with_ids(["Top Middle Chest"])
+ if world.options.hammer:
+ top_middle_room = world.get_region("Top Middle Room")
+ top_middle_room.add_locations(top_middle_room_locations, APQuestLocation)
+ else:
+ overworld.add_locations(top_middle_room_locations, APQuestLocation)
+
+ # Locations may exist only if the player enables certain options.
+ # In our case, the extra_starting_chest option adds the Bottom Left Extra Chest location.
+ if world.options.extra_starting_chest:
+ # Once again, it is important to stress that even though the Bottom Left Extra Chest location doesn't always
+ # exist, it must still always be present in the world's location_name_to_id.
+ # Whether the location actually exists in the seed is purely determined by whether we create and add it here.
+ bottom_left_extra_chest = get_location_names_with_ids(["Bottom Left Extra Chest"])
+ overworld.add_locations(bottom_left_extra_chest, APQuestLocation)
+
+
+def create_events(world: APQuestWorld) -> None:
+ # Sometimes, the player may perform in-game actions that allow them to progress which are not related to Items.
+ # In our case, the player must press a button in the top left room to open the final boss door.
+ # AP has something for this purpose: "Event locations" and "Event items".
+ # An event location is no different than a regular location, except it has the address "None".
+ # It is treated during generation like any other location, but then it is discarded.
+ # This location cannot be "sent" and its item cannot be "received", but the item can be used in logic rules.
+ # Since we are creating more locations and adding them to regions, we need to grab those regions again first.
+ top_left_room = world.get_region("Top Left Room")
+ final_boss_room = world.get_region("Final Boss Room")
+
+ # One way to create an event is simply to use one of the normal methods of creating a location.
+ button_in_top_left_room = APQuestLocation(world.player, "Top Left Room Button", None, top_left_room)
+ top_left_room.locations.append(button_in_top_left_room)
+
+ # We then need to put an event item onto the location.
+ # An event item is an item whose code is "None" (same as the event location's address),
+ # and whose classification is "progression". Item creation will be discussed more in items.py.
+ # Note: Usually, items are created in world.create_items(), which for us happens in items.py.
+ # However, when the location of an item is known ahead of time (as is the case with an event location/item pair),
+ # it is common practice to create the item when creating the location.
+ # Since locations also have to be finalized after world.create_regions(), which runs before world.create_items(),
+ # we'll create both the event location and the event item in our locations.py code.
+ button_item = items.APQuestItem("Top Left Room Button Pressed", ItemClassification.progression, None, world.player)
+ button_in_top_left_room.place_locked_item(button_item)
+
+ # A way simpler way to do create an event location/item pair is by using the region.create_event helper.
+ # Luckily, we have another event we want to create: The Victory event.
+ # We will use this event to track whether the player can win the game.
+ # The Victory event is a completely optional abstraction - This will be discussed more in set_rules().
+ final_boss_room.add_event(
+ "Final Boss Defeated", "Victory", location_type=APQuestLocation, item_type=items.APQuestItem
+ )
+
+ # If you create all your regions and locations line-by-line like this,
+ # the length of your create_regions might get out of hand.
+ # Many worlds use more data-driven approaches using dataclasses or NamedTuples.
+ # However, it is worth understanding how the actual creation of regions and locations works,
+ # That way, we're not just mindlessly copy-pasting! :)
diff --git a/worlds/apquest/options.py b/worlds/apquest/options.py
new file mode 100644
index 000000000000..00fe9ed34eaf
--- /dev/null
+++ b/worlds/apquest/options.py
@@ -0,0 +1,152 @@
+from dataclasses import dataclass
+
+from Options import Choice, OptionGroup, PerGameCommonOptions, Range, Toggle
+
+# In this file, we define the options the player can pick.
+# The most common types of options are Toggle, Range and Choice.
+
+# Options will be in the game's template yaml.
+# They will be represented by checkboxes, sliders etc. on the game's options page on the website.
+# (Note: Options can also be made invisible from either of these places by overriding Option.visibility.
+# APQuest doesn't have an example of this, but this can be used for secret / hidden / advanced options.)
+
+# For further reading on options, you can also read the Options API Document:
+# https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/options%20api.md
+
+
+# The first type of Option we'll discuss is the Toggle.
+# A toggle is an option that can either be on or off. This will be represented by a checkbox on the website.
+# The default for a toggle is "off".
+# If you want a toggle to be on by default, you can use the "DefaultOnToggle" class instead of the "Toggle" class.
+class HardMode(Toggle):
+ """
+ In hard mode, the basic enemy and the final boss will have more health.
+ The Health Upgrades become progression, as they are now required to beat the final boss.
+ """
+
+ # The docstring of an option is used as the description on the website and in the template yaml.
+
+ # You'll also want to set a display name, which will determine what the option is called on the website.
+ display_name = "Hard Mode"
+
+
+class Hammer(Toggle):
+ """
+ Adds another item to the itempool: The Hammer.
+ The top middle chest will now be locked behind a breakable wall, requiring the Hammer.
+ """
+
+ display_name = "Hammer"
+
+
+class ExtraStartingChest(Toggle):
+ """
+ Adds an extra chest in the bottom left, making room for an extra Confetti Cannon.
+ """
+
+ display_name = "Extra Starting Chest"
+
+
+class TrapChance(Range):
+ """
+ Percentage chance that any given Confetti Cannon will be replaced by a Math Trap.
+ """
+
+ display_name = "Trap Chance"
+
+ range_start = 0
+ range_end = 100
+ default = 0
+
+
+class StartWithOneConfettiCannon(Toggle):
+ """
+ Start with a confetti cannon already in your inventory.
+ Why? Because you deserve it. You get to celebrate yourself without doing any work first.
+ """
+
+ display_name = "Start With One Confetti Cannon"
+
+
+# A Range is a numeric option with a min and max value. This will be represented by a slider on the website.
+class ConfettiExplosiveness(Range):
+ """
+ How much confetti each use of a confetti cannon will fire.
+ """
+
+ display_name = "Confetti Explosiveness"
+
+ range_start = 0
+ range_end = 10
+
+ # Range options must define an explicit default value.
+ default = 3
+
+
+# A Choice is an option with multiple discrete choices. This will be represented by a dropdown on the website.
+class PlayerSprite(Choice):
+ """
+ The sprite that the player will have.
+ """
+
+ display_name = "Player Sprite"
+
+ option_human = 0
+ option_duck = 1
+ option_horse = 2
+ option_cat = 3
+
+ # Choice options must define an explicit default value.
+ default = option_human
+
+ # For choices, you can also define aliases.
+ # For example, we could make it so "player_sprite: kitty" resolves to "player_sprite: cat" like this:
+ alias_kitty = option_cat
+
+
+# We must now define a dataclass inheriting from PerGameCommonOptions that we put all our options in.
+# This is in the format "option_name_in_snake_case: OptionClassName".
+@dataclass
+class APQuestOptions(PerGameCommonOptions):
+ hard_mode: HardMode
+ hammer: Hammer
+ extra_starting_chest: ExtraStartingChest
+ start_with_one_confetti_cannon: StartWithOneConfettiCannon
+ trap_chance: TrapChance
+ confetti_explosiveness: ConfettiExplosiveness
+ player_sprite: PlayerSprite
+
+
+# If we want to group our options by similar type, we can do so as well. This looks nice on the website.
+option_groups = [
+ OptionGroup(
+ "Gameplay Options",
+ [HardMode, Hammer, ExtraStartingChest, StartWithOneConfettiCannon, TrapChance],
+ ),
+ OptionGroup(
+ "Aesthetic Options",
+ [ConfettiExplosiveness, PlayerSprite],
+ ),
+]
+
+# Finally, we can define some option presets if we want the player to be able to quickly choose a specific "mode".
+option_presets = {
+ "boring": {
+ "hard_mode": False,
+ "hammer": False,
+ "extra_starting_chest": False,
+ "start_with_one_confetti_cannon": False,
+ "trap_chance": 0,
+ "confetti_explosiveness": ConfettiExplosiveness.range_start,
+ "player_sprite": PlayerSprite.option_human,
+ },
+ "the true way to play": {
+ "hard_mode": True,
+ "hammer": True,
+ "extra_starting_chest": True,
+ "start_with_one_confetti_cannon": True,
+ "trap_chance": 50,
+ "confetti_explosiveness": ConfettiExplosiveness.range_end,
+ "player_sprite": PlayerSprite.option_duck,
+ },
+}
diff --git a/worlds/apquest/regions.py b/worlds/apquest/regions.py
new file mode 100644
index 000000000000..b002f551ffa1
--- /dev/null
+++ b/worlds/apquest/regions.py
@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from BaseClasses import Entrance, Region
+
+if TYPE_CHECKING:
+ from .world import APQuestWorld
+
+# A region is a container for locations ("checks"), which connects to other regions via "Entrance" objects.
+# Many games will model their Regions after physical in-game places, but you can also have more abstract regions.
+# For a location to be in logic, its containing region must be reachable.
+# The Entrances connecting regions can have rules - more on that in rules.py.
+# This makes regions especially useful for traversal logic ("Can the player reach this part of the map?")
+
+# Every location must be inside a region, and you must have at least one region.
+# This is why we create regions first, and then later we create the locations (in locations.py).
+
+
+def create_and_connect_regions(world: APQuestWorld) -> None:
+ create_all_regions(world)
+ connect_regions(world)
+
+
+def create_all_regions(world: APQuestWorld) -> None:
+ # Creating a region is as simple as calling the constructor of the Region class.
+ overworld = Region("Overworld", world.player, world.multiworld)
+ top_left_room = Region("Top Left Room", world.player, world.multiworld)
+ bottom_right_room = Region("Bottom Right Room", world.player, world.multiworld)
+ right_room = Region("Right Room", world.player, world.multiworld)
+ final_boss_room = Region("Final Boss Room", world.player, world.multiworld)
+
+ # Let's put all these regions in a list.
+ regions = [overworld, top_left_room, bottom_right_room, right_room, final_boss_room]
+
+ # Some regions may only exist if the player enables certain options.
+ # In our case, the Hammer locks the top middle chest in its own room if the hammer option is enabled.
+ if world.options.hammer:
+ top_middle_room = Region("Top Middle Room", world.player, world.multiworld)
+ regions.append(top_middle_room)
+
+ # We now need to add these regions to multiworld.regions so that AP knows about their existence.
+ world.multiworld.regions += regions
+
+
+def connect_regions(world: APQuestWorld) -> None:
+ # We have regions now, but still need to connect them to each other.
+ # But wait, we no longer have access to the region variables we created in create_all_regions()!
+ # Luckily, once you've submitted your regions to multiworld.regions,
+ # you can get them at any time using world.get_region(...).
+ overworld = world.get_region("Overworld")
+ top_left_room = world.get_region("Top Left Room")
+ bottom_right_room = world.get_region("Bottom Right Room")
+ right_room = world.get_region("Right Room")
+ final_boss_room = world.get_region("Final Boss Room")
+
+ # Okay, now we can get connecting. For this, we need to create Entrances.
+ # Entrances are inherently one-way, but crucially, AP assumes you can always return to the origin region.
+ # One way to create an Entrance is by calling the Entrance constructor.
+ overworld_to_bottom_right_room = Entrance(world.player, "Overworld to Bottom Right Room", parent=overworld)
+ overworld.exits.append(overworld_to_bottom_right_room)
+
+ # You can then connect the Entrance to the target region.
+ overworld_to_bottom_right_room.connect(bottom_right_room)
+
+ # An even easier way is to use the region.connect helper.
+ overworld.connect(right_room, "Overworld to Right Room")
+ right_room.connect(final_boss_room, "Right Room to Final Boss Room")
+
+ # The region.connect helper even allows adding a rule immediately.
+ # We'll talk more about rule creation in the set_all_rules() function in rules.py.
+ overworld.connect(top_left_room, "Overworld to Top Left Room", lambda state: state.has("Key", world.player))
+
+ # Some Entrances may only exist if the player enables certain options.
+ # In our case, the Hammer locks the top middle chest in its own room if the hammer option is enabled.
+ # In this case, we previously created an extra "Top Middle Room" region that we now need to connect to Overworld.
+ if world.options.hammer:
+ top_middle_room = world.get_region("Top Middle Room")
+ overworld.connect(top_middle_room, "Overworld to Top Middle Room")
diff --git a/worlds/apquest/rules.py b/worlds/apquest/rules.py
new file mode 100644
index 000000000000..533c33d5eac2
--- /dev/null
+++ b/worlds/apquest/rules.py
@@ -0,0 +1,131 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from BaseClasses import CollectionState
+from worlds.generic.Rules import add_rule, set_rule
+
+if TYPE_CHECKING:
+ from .world import APQuestWorld
+
+
+def set_all_rules(world: APQuestWorld) -> None:
+ # In order for AP to generate an item layout that is actually possible for the player to complete,
+ # we need to define rules for our Entrances and Locations.
+ # Note: Regions do not have rules, the Entrances connecting them do!
+ # We'll do entrances first, then locations, and then finally we set our victory condition.
+
+ set_all_entrance_rules(world)
+ set_all_location_rules(world)
+ set_completion_condition(world)
+
+
+def set_all_entrance_rules(world: APQuestWorld) -> None:
+ # First, we need to actually grab our entrances. Luckily, there is a helper method for this.
+ overworld_to_bottom_right_room = world.get_entrance("Overworld to Bottom Right Room")
+ overworld_to_top_left_room = world.get_entrance("Overworld to Top Left Room")
+ right_room_to_final_boss_room = world.get_entrance("Right Room to Final Boss Room")
+
+ # An access rule is a function. We can define this function like any other function.
+ # This function must accept exactly one parameter: A "CollectionState".
+ # A CollectionState describes the current progress of the players in the multiworld, i.e. what items they have,
+ # which regions they've reached, etc.
+ # In an access rule, we can ask whether the player has a collected a certain item.
+ # We can do this via the state.has(...) function.
+ # This function takes an item name, a player number, and an optional count parameter (more on that below)
+ # Since a rule only takes a CollectionState parameter, but we also need the player number in the state.has call,
+ # our function needs to be locally defined so that it has access to the player number from the outer scope.
+ # In our case, we are inside a function that has access to the "world" parameter, so we can use world.player.
+ def can_destroy_bush(state: CollectionState) -> bool:
+ return state.has("Sword", world.player)
+
+ # Now we can set our "can_destroy_bush" rule to our entrance which requires slashing a bush to clear the path.
+ # One way to set rules is via the set_rule() function, which works on both Entrances and Locations.
+ set_rule(overworld_to_bottom_right_room, can_destroy_bush)
+
+ # Because the function has to be defined locally, most worlds prefer the lambda syntax.
+ set_rule(overworld_to_top_left_room, lambda state: state.has("Key", world.player))
+
+ # Conditions can depend on event items.
+ set_rule(right_room_to_final_boss_room, lambda state: state.has("Top Left Room Button Pressed", world.player))
+
+ # Some entrance rules may only apply if the player enabled certain options.
+ # In our case, if the hammer option is enabled, we need to add the Hammer requirement to the Entrance from
+ # Overworld to the Top Middle Room.
+ if world.options.hammer:
+ overworld_to_top_middle_room = world.get_entrance("Overworld to Top Middle Room")
+ set_rule(overworld_to_top_middle_room, lambda state: state.has("Hammer", world.player))
+
+
+def set_all_location_rules(world: APQuestWorld) -> None:
+ # Location rules work no differently from Entrance rules.
+ # Most of our locations are chests that can simply be opened by walking up to them.
+ # Thus, their logical requirements are covered by the Entrance rules of the Entrances that were required to
+ # reach the region that the chest sits in.
+ # However, our two enemies work differently.
+ # Entering the room with the enemy is not enough, you also need to have enough combat items to be able to defeat it.
+ # So, we need to set requirements on the Locations themselves.
+ # Since combat is a bit more complicated, we'll use this chance to cover some advanced access rule concepts.
+
+ # Sometimes, you may want to have different rules depending on the player's chosen options.
+ # There is a wrong way to do this, and a right way to do this. Let's do the wrong way first.
+ right_room_enemy = world.get_location("Right Room Enemy Drop")
+
+ # DON'T DO THIS!!!!
+ set_rule(
+ right_room_enemy,
+ lambda state: (
+ state.has("Sword", world.player)
+ and (not world.options.hard_mode or state.has_any(("Shield", "Health Upgrade"), world.player))
+ ),
+ )
+ # DON'T DO THIS!!!!
+
+ # Now, what's actually wrong with this? It works perfectly fine, right?
+ # If hard mode disabled, Sword is enough. If hard mode is enabled, we also need a Shield or a Health Upgrade.
+ # The access rule we just wrote does this correctly, so what's the problem?
+ # The problem is performance.
+ # Most of your world code doesn't need to be perfectly performant, since it just runs once per slot.
+ # However, access rules in particular are by far the hottest code path in Archipelago.
+ # An access rule will potentially be called thousands or even millions of times over the course of one generation.
+ # As a result, access rules are the one place where it's really worth putting in some effort to optimize.
+ # What's the performance problem here?
+ # Every time our access rule is called, it has to evaluate whether world.options.hard_mode is True or False.
+ # Wouldn't it be better if in easy mode, the access rule only checked for Sword to begin with?
+ # Wouldn't it also be better if in hard mode, it already knew it had to check Shield and Health Upgrade as well?
+ # Well, we can achieve this by doing the "if world.options.hard_mode" check outside the set_rule call,
+ # and instead having two *different* set_rule calls depending on which case we're in.
+
+ if world.options.hard_mode:
+ # If you have multiple conditions, you can obviously chain them via "or" or "and".
+ # However, there are also the nice helper functions "state.has_any" and "state.has_all".
+ set_rule(
+ right_room_enemy,
+ lambda state: (
+ state.has("Sword", world.player) and state.has_any(("Shield", "Health Upgrade"), world.player)
+ ),
+ )
+ else:
+ set_rule(right_room_enemy, lambda state: state.has("Sword", world.player))
+
+ # Another way to chain multiple conditions is via the add_rule function.
+ # This makes the access rules a bit slower though, so it should only be used if your structure justifies it.
+ # In our case, it's pretty useful because hard mode and easy mode have different requirements.
+ final_boss = world.get_location("Final Boss Defeated")
+
+ # For the "known" requirements, it's still better to chain them using a normal "and" condition.
+ add_rule(final_boss, lambda state: state.has_all(("Sword", "Shield"), world.player))
+
+ if world.options.hard_mode:
+ # You can check for multiple copies of an item by using the optional count parameter of state.has().
+ add_rule(final_boss, lambda state: state.has("Health Upgrade", world.player, 2))
+
+
+def set_completion_condition(world: APQuestWorld) -> None:
+ # Finally, we need to set a completion condition for our world, defining what the player needs to win the game.
+ # You can just set a completion condition directly like any other condition, referencing items the player receives:
+ world.multiworld.completion_condition[world.player] = lambda state: state.has_all(("Sword", "Shield"), world.player)
+
+ # In our case, we went for the Victory event design pattern (see create_events() in locations.py).
+ # So lets undo what we just did, and instead set the completion condition to:
+ world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player)
diff --git a/worlds/apquest/test/__init__.py b/worlds/apquest/test/__init__.py
new file mode 100644
index 000000000000..b2e5d0d383d7
--- /dev/null
+++ b/worlds/apquest/test/__init__.py
@@ -0,0 +1,7 @@
+# The __init__.py file of the test directory should be empty.
+# (Before you say it: Comments are fine, smart*ss ;D)
+
+# You'll want to start with reading bases.py.
+
+# If you want to read more about tests, there is also the "Tests" section of the World API document:
+# https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#tests
diff --git a/worlds/apquest/test/bases.py b/worlds/apquest/test/bases.py
new file mode 100644
index 000000000000..641a85e2d9ef
--- /dev/null
+++ b/worlds/apquest/test/bases.py
@@ -0,0 +1,26 @@
+from test.bases import WorldTestBase
+
+from ..world import APQuestWorld
+
+# Tests are a big topic.
+# The testing API and the core code in general empower you to test all kinds of complicated custom behavior.
+# However, for APQuest, we'll stick to some of the more basic tests.
+
+
+# Most of your testing will probably be done using the generic WorldTestBase.
+# WorldTestBase is a class that performs a set of generic tests on your world using a given set of options.
+# It also enables you to write custom tests with a slew of generic helper functions.
+# The first thing you'll want to do is subclass it. You'll want to override "game" And "world" like this.
+class APQuestTestBase(WorldTestBase):
+ game = "APQuest"
+ world: APQuestWorld
+
+
+# The actual tests you write should be in files whose names start with "test_".
+# Ideally, you should group similar tests together in one file, where each file has some overarching significance.
+
+# The best order to read these tests in is:
+# 1. test_easy_mode.py
+# 2. test_hard_mode.py
+# 3. test_extra_starting_chest.py
+# 4. test_hammer.py
diff --git a/worlds/apquest/test/test_easy_mode.py b/worlds/apquest/test/test_easy_mode.py
new file mode 100644
index 000000000000..baf3055bbc89
--- /dev/null
+++ b/worlds/apquest/test/test_easy_mode.py
@@ -0,0 +1,106 @@
+from .bases import APQuestTestBase
+
+
+# When writing a test, you'll first need to subclass unittest.TestCase.
+# In our case, we'll subclass the APQuestTestBase we defined in bases.py.
+class TestEasyModeLogic(APQuestTestBase):
+ # Our test base is a subclass of WorldTestBase.
+ # WorldTestBase takes a dict of options and sets up a multiworld for you with a single world of your game.
+ # The world will have the options you specified.
+ options = {
+ "hard_mode": False,
+ # Options you don't specify will use their default values.
+ # It is good practice to specify every option that has an impact on your test, even when it's the default value.
+ # As such, we'll spell out that hard_mode is meant to be False.
+ # All other options in APQuest are cosmetic, so we don't need to list them.
+ }
+
+ # At this point, we could stop, and a few default tests would be run on our world.
+ # At the time of writing (2025-09-04), this includes the following tests:
+ # - If you have every item, every location can be reached
+ # - If you have no items, you can still reach something ("Sphere 1" is not empty)
+ # - The world successfully generates (Fill does not crash)
+
+ # This is already useful, but we also want to do our own tests.
+ # A test is a function whose name starts with "test".
+ def test_easy_mode_access(self) -> None:
+ # Inside a test, we can manually collect items, check access rules, etc.
+ # For example, we could check that the two early chests are already accessible despite us having no items.
+ # For the sake of structure, let's have every test item in its own subtest.
+ with self.subTest("Test checks accessible with nothing"):
+ bottom_left_chest = self.world.get_location("Bottom Left Chest")
+ top_middle_chest = self.world.get_location("Top Middle Chest")
+
+ # Since access rules have a "state" argument, we must pass our current CollectionState.
+ # Helpfully, since we're in a WorldTestBase, we can just use "self.multiworld.state".
+ self.assertTrue(bottom_left_chest.can_reach(self.multiworld.state))
+ self.assertTrue(top_middle_chest.can_reach(self.multiworld.state))
+
+ # Next, let's test that the top left room location requires the key to unlock the door.
+ with self.subTest("Test key is required to get top left chest"):
+ top_left_room_chest = self.world.get_location("Top Left Room Chest")
+
+ # Right now, this location should *not* be accessible, as we don't have the key yet.
+ self.assertFalse(top_left_room_chest.can_reach(self.multiworld.state))
+
+ # Now, let's collect the Key.
+ # For this, there is a handy helper function to collect items from the itempool.
+ # Keep in mind that while test functions are sectioned off from one another, subtests are not.
+ # Collecting this here means that the state will have the Key for all future subtests in this function.
+ self.collect_by_name("Key")
+
+ # The top left room chest should now be accessible.
+ self.assertTrue(top_left_room_chest.can_reach(self.multiworld.state))
+
+ # Next, let's test that you need the sword to access locations that require it (bush room and enemies).
+ with self.subTest("Test sword is required for enemy and bush locations"):
+ # Manually checking the dependency in the previous function was a bit of a hassle, wasn't it?
+ # Now we are checking four locations. It would be even longer as a result.
+ # Well, there is another option. It's the assertAccessDependency function of WorldTestBase.
+ self.assertAccessDependency(
+ [
+ "Bottom Right Room Right Chest",
+ "Bottom Right Room Left Chest",
+ "Right Room Enemy Drop",
+ "Final Boss Defeated", # Reminder: APQuest's victory condition uses this event location
+ ],
+ [["Sword"]],
+ )
+
+ # The assertAccessDependency function is a bit complicated, so let's discuss what it does.
+ # By default, the locations argument must contain *every* location that *hard-depends* on the items.
+ # So, in our case: If every item except Sword is collected, *exactly* these four locations are unreachable.
+
+ # The possible_items argument is initially more intuitive, but has some complexity as well.
+ # In our case, we only care about one item. But sometimes, we care about multiple items at once.
+ # This is why we pass a list of lists. We'll discuss this more when we test hard mode logic.
+
+ # Let's do one more test: That the key is required for the Button.
+ with self.subTest("Test that the Key is required to activate the Button"):
+ # The Button is not the only thing that depends on the Key.
+ # As explained above, the locations list must be exhaustive.
+ # Thus, we would have to add the "Top Left Room Chest" as well.
+ # However, we can set "only_check_listed" if we only want the Top Left Room Button location to be checked.
+ self.assertAccessDependency(
+ ["Top Left Room Button"],
+ [["Key"]],
+ only_check_listed=True,
+ )
+
+ def test_easy_mode_health_upgrades(self) -> None:
+ # For our second test, let's make sure that we have two Health Upgrades with the correct classification.
+
+ # We can find the Health Upgrades in the itempool like this:
+ health_upgrades = self.get_items_by_name("Health Upgrade")
+
+ # First, let's verify there's two of them.
+ with self.subTest("Test that there are two Health Upgrades in the pool"):
+ self.assertEqual(len(health_upgrades), 2)
+
+ # Then, let's verify that they have the useful classification and NOT the progression classification.
+ with self.subTest("Test that the Health Upgrades in the pool are useful, but not progression."):
+ # To check whether an item has a certain classification, you can use the following helper properties:
+ # item.filler, item.trap, item.useful and... item.advancement. No, not item.progression...
+ # (Just go with it, AP is old and has had many name changes over the years :D)
+ self.assertTrue(all(health_upgrade.useful for health_upgrade in health_upgrades))
+ self.assertFalse(any(health_upgrade.advancement for health_upgrade in health_upgrades))
diff --git a/worlds/apquest/test/test_extra_starting_chest.py b/worlds/apquest/test/test_extra_starting_chest.py
new file mode 100644
index 000000000000..22c01620b019
--- /dev/null
+++ b/worlds/apquest/test/test_extra_starting_chest.py
@@ -0,0 +1,39 @@
+from .bases import APQuestTestBase
+
+
+# Sometimes, you might want to test something with a specific option disabled, then with it enabled.
+# For this purpose, we'll just have two different TestCase classes.
+class TestExtraStartingChestOff(APQuestTestBase):
+ options = {
+ "extra_starting_chest": False,
+ }
+
+ # Hmm... This is just default options again.
+ # This would run all the default WorldTestBase tests a second time on default options. That's a bit wasteful.
+ # Luckily, there is a way to turn off the default tests for a WorldTestBase subclass:
+ run_default_tests = False
+
+ # Since the extra_starting_chest option is False, we'll verify that the Extra Starting Chest location doesn't exist.
+ def test_extra_starting_chest_doesnt_exit(self) -> None:
+ # Currently, the best way to check for the existence of a location is to try using get_location,
+ # then watch for the KeyError that is raised if the location doesn't exist.
+ # In a testing context, we can do this with TestCase.assertRaises.
+ self.assertRaises(KeyError, self.world.get_location, "Bottom Left Extra Chest")
+
+
+class TestExtraStartingChestOn(APQuestTestBase):
+ options = {
+ "extra_starting_chest": True,
+ }
+
+ # In this case, running the default tests is acceptable, since this is a unique options combination.
+
+ # Since the extra_starting_chest option is True, we'll verify that the Extra Starting Chest location exists.
+ def test_extra_starting_chest_exists(self) -> None:
+ # In this case, the location *should* exist, so world.get_location() should *not* KeyError.
+ # This is a bit awkward, because unittest.TestCase doesn't have an "assertNotRaises".
+ # So, we'll catch the KeyError ourselves, and then fail in the catch block with a custom message.
+ try:
+ self.world.get_location("Bottom Left Extra Chest")
+ except KeyError:
+ self.fail("Bottom Left Extra Chest should exist, but it doesn't.")
diff --git a/worlds/apquest/test/test_hammer.py b/worlds/apquest/test/test_hammer.py
new file mode 100644
index 000000000000..4bf55523b67b
--- /dev/null
+++ b/worlds/apquest/test/test_hammer.py
@@ -0,0 +1,64 @@
+from .bases import APQuestTestBase
+
+
+class TestHammerOff(APQuestTestBase):
+ options = {
+ "hammer": False,
+ }
+
+ # Once again, this is just default settings, so running the default tests would be wasteful.
+ run_default_tests = False
+
+ # The hammer option adds the Hammer item to the itempool.
+ # Since the hammer option is off in this TestCase, we have to verify that the Hammer is *not* in the itempool.
+ def test_hammer_doesnt_exist(self) -> None:
+ # An easy way to verify that an item is or is not in the itempool is by using WorldTestBase.get_items_by_name().
+ # This will return a list of all matching items, which we can check for its length.
+ hammers_in_itempool = self.get_items_by_name("Hammer")
+ self.assertEqual(len(hammers_in_itempool), 0)
+
+ # If the hammer option is not enabled, the Top Middle Chest should just be accessible with nothing.
+ def test_hammer_is_not_required_for_top_middle_chest(self) -> None:
+ # To check whether an item is required for a location, we would use self.assertAccessDependency.
+ # However, in this case, we want to check that the Hammer *isn't* required for the Top Middle Chest location.
+ # The robust way to do this is to collect every item into the state except for the Hammer,
+ # then assert that the location is reachable.
+ # Luckily, there is a helper for this: "collect_all_but".
+ self.collect_all_but("Hammer")
+
+ # Now, we manually check that the location is accessible using location.can_reach(state):
+ top_middle_chest_player_one = self.world.get_location("Top Middle Chest")
+ self.assertTrue(top_middle_chest_player_one.can_reach(self.multiworld.state))
+
+
+class TestHammerOn(APQuestTestBase):
+ options = {
+ "hammer": True,
+ }
+
+ # When the hammer option is on, the Hammer should exist in the itempool. Let's verify that.
+ def test_hammer_exists(self) -> None:
+ # Nothing new to say here, but I do want to take this opportunity to teach you some Python magic. :D
+ # In Python, when you check for the truth value of something that isn't a bool,
+ # it will be implicitly converted to a bool automatically.
+ # Which instances of a class convert to "False" and which convert to "True" is class-specific.
+ # In the case of lists (or containers in general), empty means False, and not-empty means True.
+ # bool([]) -> False
+ # bool([1, 2, 3]) -> True
+ # So, after grabbing all instances of the Hammer item from the itempool as a list ...
+ hammers_in_itempool = self.get_items_by_name("Hammer")
+
+ # ... instead of checking that the len() is 1, we can run this absolutely beautiful statement instead:
+ self.assertTrue(hammers_in_itempool)
+
+ # I love Python <3
+
+ # When the hammer option is on, the Hammer is required for the Top Middle Chest.
+ def test_hammer_is_required_for_top_middle_chest(self) -> None:
+ # This case is simple again: Just run self.assertAccessDependency()
+ self.assertAccessDependency(["Top Middle Chest"], [["Hammer"]])
+
+ # This unit test genuinely found an error in the world code when it was first written!
+ # The Hammer logic was not actually being correctly applied even if the hammer option was enabled,
+ # and the generator thought Top Middle Chest was considered accessible without the Hammer.
+ # This is why testing can be extremely valuable.
diff --git a/worlds/apquest/test/test_hard_mode.py b/worlds/apquest/test/test_hard_mode.py
new file mode 100644
index 000000000000..fc2f7acdf29e
--- /dev/null
+++ b/worlds/apquest/test/test_hard_mode.py
@@ -0,0 +1,117 @@
+from .bases import APQuestTestBase
+
+
+class TestHardMode(APQuestTestBase):
+ options = {
+ "hard_mode": True,
+ }
+
+ def test_hard_mode_access(self) -> None:
+ # For the sake of brevity, we won't repeat anything we tested in easy mode.
+ # Instead, let's use this opportunity to talk a bit more about assertAccessDependency.
+
+ # Let's take the Enemy Drop location.
+ # In hard mode, the Enemy has two health. One swipe of the Sword does not kill it.
+ # This means that the Enemy has a chance to attack you back.
+ # If you only have the Sword, this attack kills you. After respawning, the Enemy has full health again.
+ # However, if you have a Shield, you can block the attack (resulting in half damage).
+ # Alternatively, if you have found a Health Upgrade, you can tank an extra hit.
+
+ # Why is this important?
+ # If we called assertAccessDependency with ["Right Room Enemy Drop"] and [["Shield"]], it would actually *fail*.
+ # This is because "Right Room Enemy Drop" is beatable without "Shield" - You can use "Health Upgrade" instead.
+ # However, we can call assertAccessDependency with *both* items like this:
+
+ with self.subTest("Test that you need either Shield or Health Upgrade to beat the Right Room Enemy"):
+ self.assertAccessDependency(
+ ["Right Room Enemy Drop"],
+ [["Shield"], ["Health Upgrade"]],
+ only_check_listed=True,
+ )
+
+ # This tests that:
+ # 1. No Shield & No Health Upgrades -> Right Room Enemy Drop is not reachable.
+ # 2. Shield & No Health Upgrades -> Right Room Enemy Drop is reachable.
+ # 3. No Shield & All Health Upgrades -> Right Room Enemy Drop is reachable.
+
+ # Note: Every other item that isn't the Shield nor a Health Upgrade is collected into state.
+ # This even includes pre-placed items, which notably includes any event location/item pairs you created.
+ # In our case, it means we don't have to mention the Sword. By omitting it, it's assumed that we have it.
+
+ # This explains why the possible_items parameter is a list, but not why it's a list of lists.
+ # Let's look at the Final Boss Location. This location requires Sword, Shield, and both Health Upgrades.
+ # We could implement it like this:
+ with self.subTest("Test that the final boss isn't beatable without Sword, Shield, and both Health Upgrades"):
+ self.assertAccessDependency(
+ ["Final Boss Defeated"],
+ [["Sword", "Shield", "Health Upgrade"]],
+ only_check_listed=True,
+ )
+
+ # This would now test the following:
+ # 1. Without Sword, nor Shield, nor any Health Upgrades, the final boss is not beatable.
+ # 2. With Sword, Shield, and all Health Upgrades, the final boss is beatable.
+
+ # But, it's not really advisable to do this.
+ # Think about it: If we implemented our logic incorrectly and forgot to add the Shield requirement,
+ # this call would still pass. We'd rather make sure that each item individually is required:
+ for item in ["Sword", "Shield", "Health Upgrade"]:
+ with self.subTest(f"Test that the final boss requires {item}"):
+ self.assertAccessDependency(
+ ["Final Boss Defeated"],
+ [[item]],
+ only_check_listed=True,
+ )
+
+ # This now tests that:
+ # 1. Without Sword, you can't beat the Final Boss
+ # 2. With Sword, you can beat the Final Boss (if you have everything else)
+ # 3. Without Shield, you can't beat the Final Boss
+ # 4. With Shield, you can beat the Final Boss (if you have everything else)
+ # 5. Without Health Upgrades, you can't beat the Final Boss
+ # 6. With all Health Upgrades, you can beat the Final Boss (if you have everything else)
+
+ # 2., 4., and 6. are the exact same check, so it is a bit redundant.
+ # But crucially, we are ensuring that all three items are actually required.
+
+ # So that's not really why the inner elements are lists.
+ # So we ask again: Why are they lists? When is it ever useful?
+ # Fair warning: This is probably where you should stop reading this and skip to test_hard_mode_health_upgrades.
+ # But if you really want to know why:
+
+ # Having multiple elements in the inner lists is something that only comes up in more complex scenarios.
+ # APQuest doesn't have any of these scenarios, but let's imagine one for completeness' sake.
+ # Currently, the Enemy can be beaten with these item combinations:
+ # 1. Sword and Shield
+ # 2. Sword and Health Upgrade
+ # Let's say there was also a "Shield Bash". When using the Shield Bash, you cannot use the Shield to defend.
+ # This would mean there is a third valid combination:
+ # 3. Shield + Health Upgrade
+ # We have set up a scenario where none of the three items are enough on their own,
+ # but any combination of two items works.
+ # The best way to test this would be to call assertAccessDependency with:
+ # [["Sword", "Shield"], ["Sword", "Health Upgrade"], ["Shield", "Health Upgrade"]]
+ # If we omitted any item from any of the three sub-lists, the check would fail.
+ # This is because the item is still *mentioned* in one of the other two conditions,
+ # meaning it is not collected into state.
+ # Thus, this term cannot be simplified any further without testing something different to what we want to test.
+
+ # You can kinda think of assertAccessDependency as an OR(AND(item_list_1), AND(item_list_2), ...).
+ # Except this "AND" is a special "AND" which allows reducing each list to a single representative item.
+ # And also, the "OR" is special as well in that has to be exhaustive,
+ # where the set of completely unmentioned items must *not* be able to reach the location collectively.
+ # And *also*, each "AND" must be enough to access the location *out of the mentioned items*.
+ # ... I'm not sure this explanation helps anyone, but most of the time, you really don't have to think about it.
+
+ def test_hard_mode_health_upgrades(self) -> None:
+ # We'll also repeat our Health Upgrade test from the Easy Mode test case, but modified for Hard Mode.
+ # This will not be explained again here.
+
+ health_upgrades = self.get_items_by_name("Health Upgrade")
+
+ with self.subTest("Test that there are two Health Upgrades in the pool"):
+ self.assertEqual(len(health_upgrades), 2)
+
+ with self.subTest("Test that the Health Upgrades in the pool are progression, but not useful."):
+ self.assertFalse(any(health_upgrade.useful for health_upgrade in health_upgrades))
+ self.assertTrue(all(health_upgrade.advancement for health_upgrade in health_upgrades))
diff --git a/worlds/apquest/web_world.py b/worlds/apquest/web_world.py
new file mode 100644
index 000000000000..20a9a9ff18de
--- /dev/null
+++ b/worlds/apquest/web_world.py
@@ -0,0 +1,49 @@
+from BaseClasses import Tutorial
+from worlds.AutoWorld import WebWorld
+
+from .options import option_groups, option_presets
+
+
+# For our game to display correctly on the website, we need to define a WebWorld subclass.
+class APQuestWebWorld(WebWorld):
+ # We need to override the "game" field of the WebWorld superclass.
+ # This must be the same string as the regular World class.
+ game = "APQuest"
+
+ # Your game pages will have a visual theme (affecting e.g. the background image).
+ # You can choose between dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, and stone.
+ theme = "grassFlowers"
+
+ # A WebWorld can have any number of tutorials, but should always have at least an English setup guide.
+ # Many WebWorlds just have one setup guide, but some have multiple, e.g. for different languages.
+ # We need to create a Tutorial object for every setup guide.
+ # In order, we need to provide a title, a description, a language, a filepath, a link, and authors.
+ # The filepath is relative to a "/docs/" directory in the root folder of your apworld.
+ # The "link" parameter is unused, but we still need to provide it.
+ setup_en = Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up APQuest for MultiWorld.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["NewSoupVi"],
+ )
+ # Let's have our setup guide in German as well.
+ # Do not translate the title and description!
+ # WebHost needs them to be the same to identify that it is the same tutorial.
+ # This lets it display the tutorials more compactly.
+ setup_de = Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up APQuest for MultiWorld.",
+ "German",
+ "setup_de.md",
+ "setup/de",
+ ["NewSoupVi"],
+ )
+
+ # We add these tutorials to our WebWorld by overriding the "tutorials" field.
+ tutorials = [setup_en, setup_de]
+
+ # If we have option groups and/or option presets, we need to specify these here as well.
+ option_groups = option_groups
+ options_presets = option_presets
diff --git a/worlds/apquest/world.py b/worlds/apquest/world.py
new file mode 100644
index 000000000000..b38cb0913a3b
--- /dev/null
+++ b/worlds/apquest/world.py
@@ -0,0 +1,84 @@
+from collections.abc import Mapping
+from typing import Any
+
+# Imports of base Archipelago modules must be absolute.
+from worlds.AutoWorld import World
+
+# Imports of your world's files must be relative.
+from . import items, locations, options, regions, rules, web_world
+
+# APQuest will go through all the parts of the world api one step at a time,
+# with many examples and comments across multiple files.
+# If you'd rather read one continuous document, or just like reading multiple sources,
+# we also have this document specifying the entire world api:
+# https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md
+
+
+# The world class is the heart and soul of an apworld implementation.
+# It holds all the data and functions required to build the world and submit it to the multiworld generator.
+# You could have all your world code in just this one class, but for readability and better structure,
+# it is common to split up world functionality into multiple files.
+# This implementation in particular has the following additional files, each covering one topic:
+# regions.py, locations.py, rules.py, items.py, options.py and web_world.py.
+# It is recommended that you read these in that specific order, then come back to the world class.
+class APQuestWorld(World):
+ """
+ APQuest is a minimal 8bit-era inspired adventure game with grid-like movement.
+ Good games don't need more than six checks.
+ """
+
+ # The docstring should contain a description of the game, to be displayed on the WebHost.
+
+ # You must override the "game" field to say the name of the game.
+ game = "APQuest"
+
+ # The WebWorld is a definition class that governs how this world will be displayed on the website.
+ web = web_world.APQuestWebWorld()
+
+ # This is how we associate the options defined in our options.py with our world.
+ options_dataclass = options.APQuestOptions
+ options: options.APQuestOptions # Common mistake: This has to be a colon (:), not an equals sign (=).
+
+ # Our world class must have a static location_name_to_id and item_name_to_id defined.
+ # We define these in regions.py and items.py respectively, so we just set them here.
+ location_name_to_id = locations.LOCATION_NAME_TO_ID
+ item_name_to_id = items.ITEM_NAME_TO_ID
+
+ # There is always one region that the generator starts from & assumes you can always go back to.
+ # This defaults to "Menu", but you can change it by overriding origin_region_name.
+ origin_region_name = "Overworld"
+
+ # Our world class must have certain functions ("steps") that get called during generation.
+ # The main ones are: create_regions, set_rules, create_items.
+ # For better structure and readability, we put each of these in their own file.
+ def create_regions(self) -> None:
+ regions.create_and_connect_regions(self)
+ locations.create_all_locations(self)
+
+ def set_rules(self) -> None:
+ rules.set_all_rules(self)
+
+ def create_items(self) -> None:
+ items.create_all_items(self)
+
+ # Our world class must also have a create_item function that can create any one of our items by name at any time.
+ # We also put this in a different file, the same one that create_items is in.
+ def create_item(self, name: str) -> items.APQuestItem:
+ return items.create_item_with_correct_classification(self, name)
+
+ # For features such as item links and panic-method start inventory, AP may ask your world to create extra filler.
+ # The way it does this is by calling get_filler_item_name.
+ # For this purpose, your world *must* have at least one infinitely repeatable item (usually filler).
+ # You must override this function and return this infinitely repeatable item's name.
+ # In our case, we defined a function called get_random_filler_item_name for this purpose in our items.py.
+ def get_filler_item_name(self) -> str:
+ return items.get_random_filler_item_name(self)
+
+ # There may be data that the game client will need to modify the behavior of the game.
+ # This is what slot_data exists for. Upon every client connection, the slot's slot_data is sent to the client.
+ # slot_data is just a dictionary using basic types, that will be converted to json when sent to the client.
+ def fill_slot_data(self) -> Mapping[str, Any]:
+ # If you need access to the player's chosen options on the client side, there is a helper for that.
+ return self.options.as_dict(
+ "hard_mode", "hammer", "extra_starting_chest", "confetti_explosiveness", "player_sprite"
+ )
diff --git a/worlds/apsudoku/docs/en_Sudoku.md b/worlds/apsudoku/docs/en_Sudoku.md
index e81f773e0291..b56af0de79f3 100644
--- a/worlds/apsudoku/docs/en_Sudoku.md
+++ b/worlds/apsudoku/docs/en_Sudoku.md
@@ -11,3 +11,5 @@ Play Sudoku puzzles of varying difficulties, earning a hint for each puzzle corr
## Where is the options page?
There is no options page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld.
+
+By using the connected room's Admin Password on the Admin Panel tab, you can configure some settings at any time to affect the entire room. This allows disabling hints entirely, as well as altering the hint odds for each difficulty.
diff --git a/worlds/apsudoku/docs/setup_en.md b/worlds/apsudoku/docs/setup_en.md
index ef5a87e0b058..f80cd4333fe1 100644
--- a/worlds/apsudoku/docs/setup_en.md
+++ b/worlds/apsudoku/docs/setup_en.md
@@ -11,7 +11,11 @@ Does not need to be added at the start of a seed, as it does not create any slot
## Installation Procedures
-Go to the latest release from the [APSudoku Releases page](https://github.com/APSudoku/APSudoku/releases/latest). Download and extract the appropriate file for your platform.
+### Windows / Linux
+Go to the latest release from the [github APSudoku Releases page](https://github.com/APSudoku/APSudoku/releases/latest). Download and extract the appropriate file for your platform.
+
+### Web
+Go to the [github pages](apsudoku.github.io) or [itch.io](https://emilyv99.itch.io/apsudoku) site, and play in the browser.
## Joining a MultiWorld Game
@@ -35,6 +39,14 @@ Info:
- You can also use the `Tracking` tab to view either a basic tracker or a valid [GodotAP tracker pack](https://github.com/EmilyV99/GodotAP/blob/main/tracker_packs/GET_PACKS.md)
- While connected, the number of "unhinted" locations for your slot is shown in the upper-left of the the `Sudoku` tab. (If this reads 0, no further hints can be earned for this slot, as every locations is already hinted)
- Click the various `?` buttons for information on controls/how to play
+
+## Admin Settings
+
+By using the connected room's Admin Password on the Admin Panel tab, you can configure some settings at any time to affect the entire room.
+
+- You can disable APSudoku for the entire room, preventing any hints from being granted.
+- You can customize the reward weights for each difficulty, making progression hints more or less likely, and/or adding a chance to get "no hint" after a solve.
+
## DeathLink Support
If `DeathLink` is enabled when you click `Connect`:
diff --git a/worlds/autopelago/archipelago.json b/worlds/autopelago/archipelago.json
new file mode 100644
index 000000000000..66d2966a5240
--- /dev/null
+++ b/worlds/autopelago/archipelago.json
@@ -0,0 +1 @@
+{"game": "Autopelago", "minimum_ap_version": "0.6.4", "world_version": "0.11.0", "authors": ["airbreather", "skidznet"], "compatible_version": 7, "version": 7}
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/Client.py b/worlds/bomberman_tsa/Client.py
new file mode 100644
index 000000000000..c32e5b8a98a0
--- /dev/null
+++ b/worlds/bomberman_tsa/Client.py
@@ -0,0 +1,767 @@
+import typing
+import logging
+from typing import TYPE_CHECKING, Set, Optional, Dict, Any
+
+from NetUtils import ClientStatus
+#from .Items import id_to_string
+from base64 import b64encode
+import worlds._bizhawk as bizhawk
+from worlds._bizhawk.client import BizHawkClient
+
+from .gamemaps import *
+from .planet_select import get_planet_selection, highlight_planets
+from .com_ap_methods import randomize_multi_table, split_into_xbit_chunks, ramdomize_table_with_exclude
+import time
+import random
+
+logger = logging.getLogger("Client")
+
+if TYPE_CHECKING:
+ from worlds._bizhawk.context import BizHawkClientContext
+
+
+
+# def ramdomize_table(tbl,entrysize):
+# split_table = split_into_xbit_chunks(tbl,entrysize)
+# random.shuffle(split_table)
+# result = b''.join(split_table)
+# return result
+
+# def ramdomize_table_with_exclude(tbl,entrysize,excepts):
+# split_table = split_into_xbit_chunks(tbl,entrysize)
+# idxshuffle = []
+# new_table = []
+# for x in range(len(split_table)):
+# idxshuffle.append(x)
+# new_table.append(b'/x00/x00/x00/x00')
+# random.shuffle(idxshuffle)
+# for idx in range(len(split_table)):
+# if idx in excepts:
+# new_table[idx] = split_table[idx]
+# continue
+# randidx = idxshuffle.pop(0)
+# while randidx in excepts:
+# randidx = idxshuffle.pop(0)
+# new_table[idx] = split_table[randidx]
+# result = b''.join(new_table)
+# return result
+
+# def randomize_multi_table(tbl,size1,tb2,size2,excepts):
+# split_table1 = split_into_xbit_chunks(tbl,size1)
+# split_table2 = split_into_xbit_chunks(tb2,size2)
+# idxshuffle = []
+# new_table1 = []
+# new_table2 = []
+# for x in range(len(split_table1)):
+# idxshuffle.append(x)
+# new_table1.append(b'/x00')
+# new_table2.append(b'/x00')
+# random.shuffle(idxshuffle)
+
+# for idx in range(len(split_table1)):
+# #logger.warning(f"{idx} : {idxshuffle}")
+# if idx in excepts:
+# new_table1[idx] = split_table1[idx]
+# new_table2[idx] = split_table2[idx]
+# continue
+# randidx = idxshuffle.pop(0)
+# while randidx in excepts:
+# randidx = idxshuffle.pop(0)
+# #logger.warning(f"{idx} - {randidx}")
+# new_table1[idx] = split_table1[randidx]
+# new_table2[idx] = split_table2[randidx]
+
+# out_tbl_1 = b''.join(new_table1)
+# out_tbl_2 = b''.join(new_table2)
+# return out_tbl_1, out_tbl_2
+
+# def split_into_xbit_chunks(byte_array,size):
+# #Splits a byte array into chunks of 32 bits (4 bytes).
+
+# chunks = []
+# for i in range(0, len(byte_array), size):
+# chunk = byte_array[i:i + size]
+# # If the last chunk is less than 4 bytes, pad it with zeros
+# if len(chunk) < size:
+# chunk += b'\x00' * (size - len(chunk))
+# chunks.append(chunk)
+# return chunks
+
+
+class BombTSAClient(BizHawkClient):
+ game = "Bomberman The Second Attack"
+ system = "N64"
+ patch_suffix = ".apbombtsa"
+ #rom: typing.Optional[bytes] = None
+ local_checked_locations: Set[int]
+ rom_slot_name: Optional[str]
+
+ death_link: bool = False
+ sending_death_link: bool = True
+ pending_death_link: bool = False
+
+ game_goal = 0
+ noah_boss = 0
+ planet_required = 7
+ noah_access = 0
+ music_rando = False
+ efx_rando = False
+ sfx_rando = False
+ startbomb = 0
+
+ door_list = {}
+ firedown = False
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.local_checked_locations = set()
+ self.rom_slot_name = None
+ self.game_state = False
+ self.pommy_command = None
+ self.fire_command = None
+
+ async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
+ # Borrowed some authentication code from Kirby 64's apworld
+ from CommonClient import logger
+ rom_data = await bizhawk.read(
+ ctx.bizhawk_ctx,
+ [
+ (0x20, 0xD, "ROM"), # 0 Rom Name
+ (0x99FD0, 0x10, "ROM"), # 1 Game Options
+ (0x9A000, 0x32, "ROM"), # 2 Slot name
+ (0x99FE0, 0x20, "ROM"), # 3 Slot Data
+ (0x160664, 0x4B0, "RDRAM"), # 4 Music Data Part 1
+ (0x160404, 0x258, "RDRAM"), # 5 Music Data Part 2
+ (0x00, 0x28, "EEPROM"), # 6 Save data
+ ]
+ )
+ filedata = [0, 0, 0, 0, 0, 0, 0, 0, 134, 2, 0, 0, 146, 134, 178, 186, 0, 252, 99, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 205]
+ #game_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0x1FFF200, 21, "ROM")]))[0])
+ try:
+ # Check ROM name/patch version
+ rom_name = rom_data[0].decode("ascii")
+ if rom_name != "BOMBERMANTSAU":
+ return False # Not a Bomberman The Second Attack ROM
+ try:
+ #slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(0xB86500, 32, "ROM")]))[0]
+ slot_name_bytes = rom_data[2]
+ self.rom_slot_name = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8")
+
+ except UnicodeDecodeError:
+ logger.info("Could not read slot name from ROM. Are you sure this ROM matches this client version?")
+ return False
+ except bizhawk.RequestFailedError:
+ return False # Not able to get a response, say no for now
+
+ # This is a Bomberman The Second Attack ROM
+
+ ctx.game = self.game
+ ctx.items_handling = 0b111
+ ctx.command_processor.commands["pommy"] = cmd_pommy
+ ctx.command_processor.commands["fire"] = cmd_fire
+
+ game_options = rom_data[1]
+ self.noah_boss = rom_data[0]
+ self.game_goal = game_options[1]
+ deathlink = game_options[2]
+ self.planet_required = game_options[5]
+ self.noah_access = game_options[4]
+ self.startbomb = game_options[6]
+ self.pommyshop = game_options[8]
+
+
+ if self.pommyshop:
+ await ctx.send_msgs([{"cmd": "LocationScouts", "locations": SHOP_HINT_IDS, "create_as_hint": 1}])
+
+ if deathlink:
+ self.death_link = True
+
+ if game_options[9] & 1:
+ self.efx_rando = True
+ if game_options[9] & 2:
+ self.sfx_rando = True
+ if game_options[9] & 4:
+ self.music_rando = True
+ music_table1, music_table2 = randomize_multi_table(rom_data[4],0x10,rom_data[5],0x8,INVALID_SONGS)
+ await bizhawk.write(ctx.bizhawk_ctx, [
+ (0x160664, music_table1 , "RDRAM"),
+ (0x160404, music_table2 , "RDRAM")
+ ])
+
+ ctx.want_slot_data = True
+
+ logger.info("Start a game file to connect")
+
+ return True
+
+
+ def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: Dict[str, Any]) -> None:
+ if cmd == "Bounced":
+ if "tags" in args:
+ assert ctx.slot is not None
+ if "DeathLink" in args["tags"] and args["data"]["source"] != ctx.slot_info[ctx.slot].name:
+ self.on_deathlink(ctx)
+
+ async def send_deathlink(self, ctx: "BizHawkClientContext") -> None:
+ self.sending_death_link = True
+ ctx.last_death_link = time.time()
+ await ctx.send_death("Bomberman is dead.")
+
+ def on_deathlink(self, ctx: "BizHawkClientContext") -> None:
+ ctx.last_death_link = time.time()
+ self.pending_death_link = True
+
+ async def set_auth(self, ctx: "BizHawkClientContext") -> None:
+ ctx.auth = self.rom_slot_name
+
+ async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
+ from CommonClient import logger
+
+
+
+ try:
+ ram_data = await bizhawk.read(
+ ctx.bizhawk_ctx,
+ [
+ (0xAD700, 0x70, "RDRAM"), # 0 Stats
+ (0xAC2C0, 0x50, "RDRAM"), # 1 Flag data
+ #(0xABD47, 0x05, "RDRAM"), # 2 Stage
+ (0xABD40, 0xC, "RDRAM"), # 2 Stage/ Game Status
+ (0x255108, 0x04, "RDRAM"), # 3 In Stage
+ (0xB560C, 0x04, "RDRAM"), # 4 In Shop
+ (0xABD50, 0x20, "RDRAM"), # 5 Money / Pommy
+ (0x410000, 0x01, "RDRAM"), # 6 Expansion
+ (0xA02B8,0x20, "RDRAM"), # 7 Pad Read
+
+ (0xABCC8,0x4, "RDRAM"), # 8 Stage Sel Target
+ (0x255030,0x10, "RDRAM"), # 9 Planetsel check
+ (0x1D0140,0x20, "RDRAM"), # A Free RAM
+ (0x8F913,0x1, "RDRAM"), # B Display Health
+ (DOOR_OFFSETS[4],0x4, "RDRAM"), # C Neverland Door
+ (DOOR_OFFSETS[5],0x4, "RDRAM"), # D Epikyur Door
+ (DOOR_OFFSETS[6],0x4, "RDRAM"), # E Thantos Door
+ (DOOR_OFFSETS[7],0x4, "RDRAM"), # F Noah Door
+
+ (0x160664, 0x4B0, "RDRAM"), # 10 Music Data
+ (0x160404, 0x258, "RDRAM"), # 11 Music Data Part 2
+ (0x927D0, 0x2B0, "RDRAM"), # 12 EFX table
+ (0x182724, 0x4, "RDRAM"), # 13 SFX first entry
+ ]
+ )#0x16523F
+
+
+ outbound_writes = []
+
+ stat_data = ram_data[0]
+ flag_data = ram_data[1]
+ powerup_check = stat_data[0x40]
+ cur_stage = ram_data[2][7]
+ free_ram = ram_data[0xA]
+ game_status = int.from_bytes(ram_data[2][0:4], byteorder='big', signed = False)
+ mapid = int.from_bytes(ram_data[2][-2:], byteorder='big', signed = False)
+ instage = ram_data[3]
+ inshop = int.from_bytes(ram_data[4], byteorder='big', signed = False)
+ #gaurdian_spawn = ram_data[4]
+ money = ram_data[5][0:4]
+ pommy_form = ram_data[5][0x10]
+ pommy_food1 = ram_data[5][0x11]
+ pommy_food2 = ram_data[5][0x12]
+ pommy_food3 = ram_data[5][0x13]
+ pommy_check = ram_data[5][0x14]
+ planet_sel_check = ram_data[9]
+
+ eventbytes = flag_data[0x3C:0x40]
+ health_cur = stat_data[0x5F]
+ health_max = ram_data[0xB][0]
+ #health_max = stat_data[0x63]
+ exp_byte = ram_data[6][0]
+ pad_read = ram_data[7]
+ stage_selection = ram_data[8]
+
+ watched_intro = flag_data[0x1D] & 0x10
+ armor = flag_data[0x1D]
+ boss_clear = flag_data[0x1E]
+ stage_clear = flag_data[0x1F]
+ full_power = flag_data[0x29]
+ recv_index = free_ram[0]
+
+ bombtype = stat_data[0x3B]
+ sfx_check = int.from_bytes(ram_data[0x13], byteorder='big', signed = False)
+
+ recv_action = 0
+ # Check if game file is loaded
+ if watched_intro == 0x00:
+ return
+ self.game_state = True
+
+ # Open Stage Select TEMP
+ #stage_pointer_table = [0x60,0x00,0x50,0x28, 0x60,0x00,0x50,0x28, 0x60,0x00,0x50,0x28, 0x60,0x00,0x50,0x28,]
+ #outbound_writes.append((0x255108, bytearray(stage_pointer_table), "RDRAM"))
+
+ # Deathlink
+ if self.death_link:
+ await ctx.update_death_link(self.death_link)
+ if self.pending_death_link:
+ outbound_writes.append((0xB8887, bytearray([0x02]), "RDRAM"))
+ outbound_writes.append((0xB888F, bytearray([0x26]), "RDRAM"))
+ self.pending_death_link = False
+ self.sending_death_link = True
+ if "DeathLink" in ctx.tags and ctx.last_death_link + 1 < time.time():
+ if health_cur == 0 and not self.sending_death_link:
+ #if death_read == 0 and not self.sending_death_link:
+ await self.send_deathlink(ctx)
+ elif health_cur != 0:
+ self.sending_death_link = False
+
+ # Autoclear Horizon Puzzles
+ #outbound_writes.append((0xAD4FC, bytearray([0x01]), "RDRAM"))
+ # Patch Door code
+ #if exp_byte == 0x00:
+ # outbound_writes.append((0x410000, DOOR_PATCH, "RDRAM"))
+ # Check Locations
+ locs_to_send = set()
+
+ # Stage Clears
+
+ # Handle Locations
+ # LocationID: [ADDRESS, MASK]
+ # Flag Locations
+ for loc_id, ary in FLAG_MAP.items():
+ offset = ary[0] - 0xAC2C0
+ flag = flag_data[offset] & ary[1]
+ try:
+ mapexcept = ary[2]
+ except IndexError:
+ if (loc_id in ctx.server_locations) and flag:
+ locs_to_send.add(loc_id)
+ else:
+ if mapid == mapexcept:
+ pass
+ else:
+ if (loc_id in ctx.server_locations) and flag:
+ locs_to_send.add(loc_id)
+
+
+ # Stage Flag Locations
+ # Planet, Offset, Mask
+ for loc_id, ary in LEVEL_FLAG_MAP.items():
+ offset = (0xAC2FC + ary[1]) - 0xAC2C0
+ flag = flag_data[offset] & ary[2]
+
+ if (loc_id in ctx.server_locations) and (cur_stage == ary[0]) and flag:
+ locs_to_send.add(loc_id)
+
+ # Pommy Evolution Locations
+ if pommy_check:
+ loc_id = 0x1C3050 + (pommy_check-1)
+ if (loc_id not in ctx.checked_locations) and (loc_id in ctx.server_locations):
+ locs_to_send.add(loc_id)
+ outbound_writes.append((0xABD64, bytearray([0x00]), "RDRAM"))
+
+ # Remote Bomb Locations
+ if powerup_check:
+ #match powerup_check:
+ if powerup_check & 0x1:
+ if mapid in KICK_MAP:
+ loc_id = KICK_MAP[mapid]
+ locs_to_send.add(loc_id)
+ if powerup_check & 0x2:
+ if mapid in GLOVE_MAP:
+ loc_id = GLOVE_MAP[mapid]
+ locs_to_send.add(loc_id)
+ if powerup_check & 0x4:
+ if mapid in REMOTE_MAP:
+ loc_id = REMOTE_MAP[mapid]
+ locs_to_send.add(loc_id)
+ outbound_writes.append((0xAD740, bytearray([0x00]), "RDRAM"))
+
+ # Shop Locations
+ if inshop == 0x800B57C0:
+ for loc_id, ary in SHOP_MAP.items():
+ offset = ary[0] - 0xAC2C0
+ mask = ary[1]
+ flag = flag_data[offset] & mask
+ if flag and (loc_id not in ctx.checked_locations) and (loc_id in ctx.server_locations):
+ locs_to_send.add(loc_id)
+
+ if locs_to_send != self.local_checked_locations:
+ self.local_checked_locations = locs_to_send
+ if locs_to_send is not None:
+ await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}])
+
+
+ # Handle items
+ planets = [0x1C30010]
+ hp_bits = 0
+ hp_shift = 0
+ bomblvl = 0
+ firelvl = 1
+ speedlvl = 2
+ #genes = 0x0000
+ elemental_stone = 0
+ warkeys = 0
+ warshipmask = 0
+ warshipmaskary = [0x20, 0x40, 0x10]
+ genes = []
+ stage_item_have = []
+
+ for item in range(len(ctx.items_received)):
+ raw_item = ctx.items_received[item].item
+ # Pommy Genes
+ if raw_item >= 0x1C30020 and raw_item < 0x1C30030:
+ #shift = raw_item & 0xF
+ #mask = 1 < shift
+ #genes = genes | mask
+ genes.append(raw_item)
+ # Stage Items
+ elif raw_item >= 0x1C30018 and raw_item < 0x1C30020:
+ stage_item_have.append(raw_item)
+ else:
+ match raw_item:
+ case 0x1C30011: # Aquanet
+ planets.append(raw_item)
+ case 0x1C30012: # Horizon
+ planets.append(raw_item)
+ case 0x1C30013: # Starlight
+ planets.append(raw_item)
+ case 0x1C30014: # Neverland
+ planets.append(raw_item)
+ case 0x1C30015: # Epikyur
+ planets.append(raw_item)
+ case 0x1C30016: # Thantos
+ planets.append(raw_item)
+ case 0x1C30017: # Noah
+ planets.append(raw_item)
+ case 0x1C30000: # Fire Stone
+ elemental_stone = elemental_stone | 0x01
+ case 0x1C30001: # Ice Stone
+ elemental_stone = elemental_stone | 0x02
+ case 0x1C30002: # Wind Stone
+ elemental_stone = elemental_stone | 0x04
+ case 0x1C30003: # Earth Stone
+ elemental_stone = elemental_stone | 0x10
+ case 0x1C30004: # Electric
+ elemental_stone = elemental_stone | 0x08
+ case 0x1C30005: # Dark
+ elemental_stone = elemental_stone | 0x40
+ case 0x1C30006: # Light
+ elemental_stone = elemental_stone | 0x20
+ case 0x1C3000A: # Gaurdian Glove
+ armor = armor | 0x04
+ case 0x1C3000B: # Gaurdian Boots
+ armor = armor | 0x08
+ case 0x1C3000C: # Gaurdian Helmet
+ # Prevent a crash
+ if mapid in [0x861,0x8AB,0x8B4, 0x8AA]:
+ armor = armor & 0xFE
+ else:
+ armor = armor | 0x01
+ case 0x1C3000D: # Gaurdian Armor
+ armor = armor | 0x02
+ case 0x1C30007: #Bombup
+ bomblvl += 1
+ if bomblvl > 3:
+ bomblvl = 3
+ case 0x1C30008: #Bombup
+ firelvl += 1
+ if firelvl > 3:
+ firelvl = 3
+ case 0x1C30009: # HealthUp
+ hp_shift += 1
+ hp_bits = hp_bits | (1 << hp_shift)
+ #health_max += 1
+ #hpshift = (health_max -4)
+ #hp_bits = hp_bits | (1 << hpshift)
+ case 0x1C3000E: # Skates
+ speedlvl += 1
+ if speedlvl > 2:
+ speedlvl = 2
+ case 0x1C30050: # Warship keys
+ warkeys += 1
+ if warkeys > 3:
+ warkeys = 3
+ warshipmask = warshipmask | warshipmaskary[(warkeys-1)]
+
+
+
+ if self.noah_access == 0:
+ generator_clears = 0
+ for x in range(7):
+ mask = 1 << x
+ flag = stage_clear & mask
+ if flag:
+ generator_clears +=1
+ if (generator_clears >= self.planet_required) and 0x1C30017 not in planets:
+ planets.append(0x1C30017)
+ elif self.noah_access == 1:
+ if warkeys == 3:
+ planets.append(0x1C30017)
+
+ # Handle Planet Select
+ if instage[2] == 0x4F:
+ outbound_writes.append((0xABCC0, bytearray([0x60,0x00,0x50,0x28]), "RDRAM"))
+ if instage[2] == 0x4F and (planet_sel_check[0x3] == 0x01 and planet_sel_check[0x7] == 0xFF
+ and planet_sel_check[0xB] == 0x01 and planet_sel_check[0xF] == 0x02):
+ outbound_writes.append((0x255028, get_planet_selection(planets), "RDRAM"))
+ planethighlights = highlight_planets(planets,stage_clear,boss_clear,stage_selection[3])
+ for offset, color in planethighlights.items():
+ outbound_writes.append((offset, color.to_bytes(4, 'big'), "RDRAM"))
+ outbound_writes.append((0xB5827,bytearray([0x01]), "RDRAM")) # always display Epikyur
+ outbound_writes.append((0xB587F,bytearray([0x01]), "RDRAM")) # always display Thantos
+
+ clear_fp = full_power & 0xBF # Mask 0x40
+ if self.firedown == True:
+ firelvl = 0
+ maxbombtype = [
+ bomblvl + 1, # 0 Fire
+ bomblvl, # 1 Ice
+ bomblvl, # 2 Wind
+ 1, # 3 Earth
+ bomblvl + 1, # 4 Lightning
+ 1, # 5 Light
+ 1 # 6 Dark
+ ]
+ if bombtype < 7:
+ maxbombs = maxbombtype[bombtype]
+ else:
+ maxbombs = 1
+ if maxbombs <= 0:
+ maxbombs = 1
+ outbound_writes.append((0xAC2E8, bytearray([clear_fp]), "RDRAM"))
+ outbound_writes.append((0xAC307, bytearray([elemental_stone]), "RDRAM"))
+ outbound_writes.append((0xAC2DD, bytearray([armor]), "RDRAM"))
+
+ outbound_writes.append((0xAD733, bytearray([firelvl]), "RDRAM"))
+ outbound_writes.append((0xAD737, bytearray([bomblvl]), "RDRAM"))
+ outbound_writes.append((0xAD75B, bytearray([maxbombs]), "RDRAM"))
+ outbound_writes.append((0xAD73F, bytearray([speedlvl]), "RDRAM"))
+ #outbound_writes.append((0xAD763, bytearray([health_max]), "RDRAM"))
+ outbound_writes.append((0xAC2DB, bytearray([hp_bits]), "RDRAM"))
+ # Place Warship keys
+
+ #health_max = hp_shift + 5
+ # Handle Temp items
+ if len(ctx.items_received) > recv_index:
+ raw_item = ctx.items_received[recv_index].item
+ updated_game_state = game_status
+ #if raw_item >= 0x1C30000 and raw_item < 0x1C30030:
+ #recv_action = 0x33
+ match raw_item:
+ case 0x1C30030: # money
+ cash = int.from_bytes(money, byteorder='big', signed = True) + 200
+ outbound_writes.append((0xABD50, cash.to_bytes(4, "big") , "RDRAM"))
+ case 0x1C30031: # Heart
+ health_out = health_cur + 1
+ if health_out > health_max:
+ health_out = health_max
+ outbound_writes.append((0xAD75F, bytearray([health_out]), "RDRAM"))
+ case 0x1C30032: # Gold Heart
+ outbound_writes.append((0xAD75F, bytearray([health_max]), "RDRAM"))
+
+ case 0x1C30040: # Stun Trap
+ recv_action = 0x2B
+ case 0x1C30041: # Panic Bomb Trap
+ recv_action = 0x46
+ case 0x1C30042: # Fire Trap
+ outbound_writes.append((0xB8B17, bytearray([0x06]), "RDRAM"))
+ outbound_writes.append((0xB8B12, bytearray([0x04]), "RDRAM"))
+ outbound_writes.append((0xB887A, bytearray([0x01]), "RDRAM"))
+ case 0x1C30043: # Reverse Trap
+ outbound_writes.append((0xB8B17, bytearray([0x04]), "RDRAM"))
+ outbound_writes.append((0xB8B12, bytearray([0x04]), "RDRAM"))
+ outbound_writes.append((0xB887A, bytearray([0x01]), "RDRAM"))
+ case 0x1C30044: # Ejection trap
+ updated_game_state = updated_game_state | 0x00000200
+ outbound_writes.append((0xABD40, updated_game_state.to_bytes(4, byteorder='big'), "RDRAM"))
+ if recv_action:
+ outbound_writes.append((0xB8887, bytearray([0x02]), "RDRAM"))
+ outbound_writes.append((0xB888F, bytearray([recv_action]), "RDRAM"))
+ recv_action = 0
+ outbound_writes.append((RECV_INDEX, (recv_index +1).to_bytes(1, "big") , "RDRAM"))
+
+ # Handle Stage Item writes
+ #stage_item_have
+ #eventbytes
+ #mapid
+ flagbytes = eventbytes
+ for item_id, ary in STAGE_ITEMS.items():
+ if cur_stage == ary[0]:
+ val = int.from_bytes(flagbytes, byteorder='big', signed= False)
+ placeMapID = ary[3]
+ getMapIDs = ary[4]
+ rawmask = ary[2]
+ flags = val
+
+ if item_id in stage_item_have:
+ if mapid == placeMapID:
+ mask = ary[1]
+ flags = val | mask
+ elif mapid not in getMapIDs:
+ mask = 0xFFFFFFFF - rawmask
+ flags = val & mask
+ else:
+ if mapid == placeMapID:
+ mask = 0xFFFFFFFF - rawmask
+ flags = val & mask
+ #elif mapid not in getMapIDs:
+ # mask = 0xFFFFFFFF - rawmask
+ # flags = val & mask
+ flagbytes = flags.to_bytes(4,byteorder='big')
+ #outbound_writes.append((0xAC2FF, bytearray([(flag_data[0x3F] | warshipmask)]), "RDRAM"))
+ if cur_stage == 7:
+ val = int.from_bytes(flagbytes, byteorder='big', signed= False)
+ flags = val | warshipmask
+ flagbytes = flags.to_bytes(4,byteorder='big')
+ outbound_writes.append((0xAC2FC, flagbytes, "RDRAM"))
+
+ # Door crap
+ # if instage[2] == 0x4F:
+ # if mapid not in self.door_list:
+ # self.door_list[mapid] = ram_data[(8+cur_stage)][3]
+ # else:
+ # oldval = self.door_list[mapid]
+ # newval = ram_data[(8+cur_stage)][3]
+ # self.door_list[mapid] = min(oldval,newval)
+ # if pad_read[1] & 0x20 and self.door_list[mapid] == 0: # L Hold
+ # outbound_writes((DOOR_OFFSETS[cur_stage]+3), 0x00, "RDRAM")
+
+ # Handle Pommy Command
+ if self.pommy_command:
+ target_evo = self.pommy_command
+ evo_item = 0x1C30020 + (target_evo -1)
+ if target_evo < 0x11 and target_evo > -1:
+ #evo_val = target_evo + 1
+ #mask = 1 < target_evo
+ if evo_item in genes:
+ outbound_writes.append((0xABD60, bytearray([target_evo]), "RDRAM"))
+ else:
+ logger.warning(f"Pommy does not have this gene")
+ else:
+ outbound_writes.append((0xABD60, bytearray([0x00]), "RDRAM"))
+ self.pommy_command = None
+
+ # Handle Firepower Reduction
+ if self.fire_command:
+ if self.fire_command == 1:
+ self.firedown = not self.firedown
+ elif self.fire_command == 2:
+ self.firedown = True
+ elif self.fire_command == 3:
+ self.firedown = False
+ self.fire_command = False
+
+
+
+ # Handle Game Completion
+ if not ctx.finished_game:
+ game_cleared = False
+ match self.game_goal:
+ case 0: # Final Boss
+ if (flag_data[0x1F] & 0x80):
+ game_cleared = True
+ elif (flag_data[0x1E] & 0x80) and self.noah_boss == 1:
+ game_cleared = True
+ case 1: # Generators
+ stage_clears = flag_data[0x1F] & 0x7F
+ clear_array = []
+ for x in range(6):
+ clearflag = 1 << x
+ if stage_clears & clearflag:
+ clear_array.append(clearflag)
+ if len(clear_array) >= self.planet_required:
+ game_cleared = True
+ case 2: # Stone Hunt
+ if elemental_stone == 0x7F:
+ game_cleared = True
+ if game_cleared:
+ await ctx.send_msgs([{
+ "cmd": "StatusUpdate",
+ "status": ClientStatus.CLIENT_GOAL
+ }])
+
+ #logger.warning(f"{str(int.from_bytes(ram_data[0x10][0:4], byteorder='big', signed = False))}")
+ if self.music_rando and (int.from_bytes(ram_data[0x10][0x10:0x14], byteorder='big', signed = False) == 0x017CFFFF ):
+ music_table1, music_table2 = randomize_multi_table(ram_data[0x10],0x10,ram_data[0x11],0x8,INVALID_SONGS)
+ outbound_writes.append((0x160664, music_table1, "RDRAM"))
+ outbound_writes.append((0x160404, music_table2, "RDRAM"))
+ #await bizhawk.write(ctx.bizhawk_ctx, [(0x160664, music_table1 , "RDRAM"),(0x160404, music_table2 , "RDRAM")])
+
+ #logger.warning(f"{str(int.from_bytes(ram_data[0x12][0:4], byteorder='big', signed = False))}")
+ if self.efx_rando and (int.from_bytes(ram_data[0x12][0:4], byteorder='big', signed = False) == 0x8005D730):
+ outbound_writes.append((0x927D0, ramdomize_table_with_exclude(ram_data[0x12],4,INVALID_EFX), "RDRAM"))
+ if self.sfx_rando and sfx_check == 0x8000FF51:
+ raw_sfx_table = await bizhawk.read(ctx.bizhawk_ctx,[(0x182724, 0x20B8, "RDRAM"),])
+ outbound_writes.append((0x182724, ramdomize_table_with_exclude(raw_sfx_table[0],8,[0x374,0x375]), "RDRAM"))
+
+
+
+
+ await bizhawk.write(ctx.bizhawk_ctx, outbound_writes)
+
+
+ except bizhawk.RequestFailedError:
+ # The connector didn't respond. Exit handler and return to main loop to reconnect
+ pass
+
+def cmd_pommy(self, evo: str = ""):
+ # Thanks to Pokemon Red/Blue apworld
+ """Swap to specified Pommy Evolution provided you have recieved the proper gene
+ example: /pommy dragon"""
+ valid_evos = [
+ "knuckle", # 0
+ "animal", # 1
+ "hammer", # 2
+ "claw", # 3
+ "penguin", # 4
+ "beast", # 5
+ "mage", # 6
+ "knight", # 7
+ "devil", # 8
+ "cat", # 9
+ "bird", # A
+ "chicken", # B
+ "dragon", # C
+ "dinosaur", # D
+ "pixie", # E
+ "shadow", # F
+ "base", # 10
+ ]
+ if self.ctx.game != "Bomberman The Second Attack":
+ logger.warning(f"This command can only be used while playing Bomberman The Second Attack")
+ return
+ if (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state:
+ logger.warning(f"Must be connected to server and in game.")
+ return
+ elif not evo:
+ logger.warning(f"Please enter a valid Pommy evolution")
+ return
+ elif evo.lower() not in valid_evos:
+ logger.warning(f"Please enter a valid Pommy evolution")
+ return
+ elif evo.lower() in valid_evos:
+ self.ctx.client_handler.pommy_command = valid_evos.index(evo.lower())+1
+ else:
+ logger.warning(f"Invalid Pommy command {evo}")
+ return
+
+def cmd_fire(self, sub: str = ""):
+ """Allows you to reduce your firepower back down to 0, useful for building ice bridges.
+ 'down' to reduce, 'up' to return it back to normal or simpley /fire to toggle"""
+ if self.ctx.game != "Bomberman The Second Attack":
+ logger.warning(f"This command can only be used while playing Bomberman The Second Attack")
+ return
+ if (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state:
+ logger.warning(f"Must be connected to server and in game.")
+ return
+ elif not sub:
+ self.ctx.client_handler.fire_command = 1
+ logger.warning(f"Toggling Firepower")
+ elif sub == "down" or sub == "Down" :
+ self.ctx.client_handler.fire_command = 2
+ logger.warning(f"Reducing Firepower")
+ elif sub == "up" or sub == "Up":
+ self.ctx.client_handler.fire_command = 3
+ logger.warning(f"Incrasing Firepower")
+ else:
+ self.ctx.client_handler.fire_command = 1
+ logger.warning(f"Toggling Firepower")
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/Items.py b/worlds/bomberman_tsa/Items.py
new file mode 100644
index 000000000000..e12e8948a47b
--- /dev/null
+++ b/worlds/bomberman_tsa/Items.py
@@ -0,0 +1,393 @@
+from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING
+
+from BaseClasses import Item, ItemClassification
+
+if TYPE_CHECKING:
+ from . import BombTSAWorld
+
+
+class BombTSAItem(Item):
+ game = "Bomberman The Second Attack"
+
+
+class BombTSAItemData(NamedTuple):
+ code: Optional[int] = None
+ type: ItemClassification = ItemClassification.filler
+ num_exist: int = 1
+ can_create: Callable[["BombTSAWorld"], bool] = lambda world: True
+ item_type: Optional[str] = None
+ fillweight: Optional[float] = None
+
+
+item_data_table: Dict[str, BombTSAItemData] = {
+ "Fire Stone": BombTSAItemData(
+ code=0x1C30000,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type="Stone",
+ ),
+ "Ice Stone": BombTSAItemData(
+ code=0x1C30001,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Stone",
+ ),
+ "Wind Stone": BombTSAItemData(
+ code=0x1C30002,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Stone",
+ ),
+ "Earth Stone": BombTSAItemData(
+ code=0x1C30003,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Stone",
+ ),
+ "Lightning Stone": BombTSAItemData(
+ code=0x1C30004,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Stone",
+ ),
+ "Dark Stone": BombTSAItemData(
+ code=0x1C30005,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Stone",
+ ),
+ "Light Stone": BombTSAItemData(
+ code=0x1C30006,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Stone",
+ ),
+ "BombUp": BombTSAItemData(
+ code=0x1C30007,
+ type=ItemClassification.progression,
+ num_exist= 3,
+ item_type= "Powerup",
+ ),
+ "FireUp": BombTSAItemData(
+ code=0x1C30008,
+ type=ItemClassification.progression,
+ num_exist= 2,
+ item_type= "Powerup",
+ ),
+ "HealthUp": BombTSAItemData(
+ code=0x1C30009,
+ type=ItemClassification.useful,
+ num_exist=5,
+ item_type= "Powerup",
+ ),
+ "Guardian Glove": BombTSAItemData(
+ code=0x1C3000A,
+ type=ItemClassification.progression,
+ item_type= "Guardian",
+ ),
+ "Guardian Boots": BombTSAItemData(
+ code=0x1C3000B,
+ type=ItemClassification.progression,
+ item_type= "Guardian",
+ ),
+ "Guardian Helmet": BombTSAItemData(
+ code=0x1C3000C,
+ type=ItemClassification.progression,
+ item_type= "Guardian",
+ ),
+ "Guardian Armor": BombTSAItemData(
+ code=0x1C3000D,
+ type=ItemClassification.useful,
+ item_type= "Guardian",
+ ),
+ "Skates": BombTSAItemData(
+ code=0x1C3000E,
+ type=ItemClassification.progression,
+ item_type= "Powerup",
+ ),
+
+
+ #"Alcatraz": BombTSAItemData(
+ # code=0x1C30010,
+ # type=ItemClassification.progression,
+ #),
+ "Aquanet Coordinates": BombTSAItemData(
+ code=0x1C30011,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Coordinates",
+ ),
+ "Horizon Coordinates": BombTSAItemData(
+ code=0x1C30012,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Coordinates",
+ ),
+ "Starlight Coordinates": BombTSAItemData(
+ code=0x1C30013,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Coordinates",
+ ),
+ "Neverland Coordinates": BombTSAItemData(
+ code=0x1C30014,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Coordinates",
+ ),
+ "Epikyur Coordinates": BombTSAItemData(
+ code=0x1C30015,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Coordinates",
+ ),
+ "Thantos Coordinates": BombTSAItemData(
+ code=0x1C30016,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Coordinates",
+ ),
+ "Noah Coordinates": BombTSAItemData(
+ code=0x1C30017,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Coordinates",
+ ),
+
+
+ "Pommy Knuckle Gene": BombTSAItemData(
+ code=0x1C30020,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Animal Gene": BombTSAItemData(
+ code=0x1C30021,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Hammer Gene": BombTSAItemData(
+ code=0x1C30022,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Claw Gene": BombTSAItemData(
+ code=0x1C30023,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Penguin Gene": BombTSAItemData(
+ code=0x1C30024,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Beast Gene": BombTSAItemData(
+ code=0x1C30025,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Mage Gene": BombTSAItemData(
+ code=0x1C30026,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Knight Gene": BombTSAItemData(
+ code=0x1C30027,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Devil Gene": BombTSAItemData(
+ code=0x1C30028,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Cat Gene": BombTSAItemData(
+ code=0x1C30029,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Bird Gene": BombTSAItemData(
+ code=0x1C3002A,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Chicken Gene": BombTSAItemData(
+ code=0x1C3002B,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Dragon Gene": BombTSAItemData(
+ code=0x1C3002C,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Dinosaur Gene": BombTSAItemData(
+ code=0x1C3002D,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Pixie Gene": BombTSAItemData(
+ code=0x1C3002E,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+ "Pommy Shadow Gene": BombTSAItemData(
+ code=0x1C3002F,
+ type=ItemClassification.useful,
+ num_exist= 0,
+ item_type= "Gene",
+ ),
+
+ # Filler
+ "200 Coins": BombTSAItemData(
+ code=0x1C30030,
+ type=ItemClassification.filler,
+ num_exist=0,
+ item_type= "filler",
+ fillweight = 0.4,
+ ),
+ "Heart": BombTSAItemData(
+ code=0x1C30031,
+ type=ItemClassification.filler,
+ num_exist=0,
+ item_type= "filler",
+ fillweight = 0.8,
+ ),
+ "Gold Heart": BombTSAItemData(
+ code=0x1C30032,
+ type=ItemClassification.filler,
+ num_exist=0,
+ item_type= "filler",
+ fillweight = 0.2,
+ ),
+
+ # Traps
+ "Stun Trap": BombTSAItemData(
+ code=0x1C30040,
+ type=ItemClassification.trap,
+ num_exist=0,
+ item_type= "trap",
+ fillweight = 0.3,
+ ),
+ "Panic Bomb Trap": BombTSAItemData(
+ code=0x1C30041,
+ type=ItemClassification.trap,
+ num_exist=0,
+ item_type= "trap",
+ fillweight = 0.4,
+ ),
+ "Fire Trap": BombTSAItemData(
+ code=0x1C30042,
+ type=ItemClassification.trap,
+ num_exist=0,
+ item_type= "trap",
+ fillweight = 0.3,
+ ),
+ "Reverse Trap": BombTSAItemData(
+ code=0x1C30043,
+ type=ItemClassification.trap,
+ num_exist=0,
+ item_type= "trap",
+ fillweight = 0.4,
+ ),
+ "Ejection Trap": BombTSAItemData(
+ code=0x1C30044,
+ type=ItemClassification.trap,
+ num_exist=0,
+ item_type= "trap",
+ fillweight = 0.0,
+ ),
+
+ # Stage Key Items
+ "Blue Gems": BombTSAItemData(
+ code=0x1C30018,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+ "Green Gems": BombTSAItemData(
+ code=0x1C30019,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+ "Red Gem": BombTSAItemData(
+ code=0x1C3001A,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+
+ "Royal Straight": BombTSAItemData(
+ code=0x1C3001B,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+
+ "Haunted House Pass": BombTSAItemData(
+ code=0x1C3001C,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+ "Museum Pass": BombTSAItemData(
+ code=0x1C3001D,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+ "Coaster Battery": BombTSAItemData(
+ code=0x1C3001E,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+
+ "Train Batteries": BombTSAItemData(
+ code=0x1C3001F,
+ type=ItemClassification.progression,
+ item_type= "Event",
+ ),
+ "Warship key": BombTSAItemData(
+ code=0x1C30050,
+ type=ItemClassification.progression,
+ num_exist= 0,
+ item_type= "Warship key",
+ ),
+
+
+ #"Shop Coordinates": BombTSAItemData(
+ # code=0x1C30018,
+ # type=ItemClassification.progression,
+ #),
+
+
+
+
+
+ "Victory": BombTSAItemData(
+ code=0x1CAEE12,
+ type=ItemClassification.progression,
+ num_exist=0,
+ item_type= "Goal",
+ ),
+}
+
+item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}
+
+pommy_shop_genes = [name for name,data in item_data_table.items() if data.item_type == "Gene"]
+elemental_stones = [name for name,data in item_data_table.items() if data.item_type == "Stone"]
+planet_coord_list = [name for name,data in item_data_table.items() if data.item_type == "Coordinates"]
+
+item_filler = [name for name, data in item_data_table.items() if data.type == ItemClassification.filler and data.fillweight is not None]
+item_filler_weight = [data.fillweight for name, data in item_data_table.items() if data.type == ItemClassification.filler and data.fillweight is not None]
+trap_filler = [name for name, data in item_data_table.items() if data.type == ItemClassification.trap and data.fillweight is not None]
+trap_filler_weight = [data.fillweight for name, data in item_data_table.items() if data.type == ItemClassification.trap and data.fillweight is not None]
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/Locations.py b/worlds/bomberman_tsa/Locations.py
new file mode 100644
index 000000000000..59576051c523
--- /dev/null
+++ b/worlds/bomberman_tsa/Locations.py
@@ -0,0 +1,1059 @@
+from typing import Callable, Dict, NamedTuple, Optional, TYPE_CHECKING
+
+from BaseClasses import Location
+
+if TYPE_CHECKING:
+ from . import BombTSAWorld
+
+
+class BombTSALocation(Location):
+ game = "Bomberman The Second Attack"
+
+
+class BombTSALocationData(NamedTuple):
+ region: str
+ address: Optional[int] = None
+ can_create: Callable[["BombTSAWorld"], bool] = lambda world: True
+ locked_item: Optional[str] = None
+ loc_type: Optional[str] = None
+ base_loc:Optional[bool] = True
+
+location_data_table: Dict[str, BombTSALocationData] = {
+
+ "Alcatraz Baelfael Defeated": BombTSALocationData(
+ region="Alcatraz, Sewage Disposal",
+ address=0x1C3001,
+ loc_type= "Boss",
+ ),
+ "Aquanet Behemos Defeated": BombTSALocationData(
+ region="Aquanet, Behemos' Lair",
+ address=0x1C3002,
+ loc_type= "Boss",
+ ),
+ "Horizon Ashtarth Defeated": BombTSALocationData(
+ region="Horizon, Resting Point",
+ address=0x1C3003,
+ loc_type= "Boss",
+ ),
+ "Starlight Zhael Defeated": BombTSALocationData(
+ region="Starlight, Stage Area",
+ address=0x1C3004,
+ loc_type= "Boss",
+ ),
+ "Neverland Molok Defeated": BombTSALocationData(
+ region="Neverland, Furnace",
+ address=0x1C3005,
+ loc_type= "Boss",
+ ),
+ "Epikyur Zoniha Defeated": BombTSALocationData(
+ region="Epikyur, Coaster Finish",
+ address=0x1C3006,
+ loc_type= "Boss",
+ ),
+ "Thantos Bulzeeb Defeated": BombTSALocationData(
+ region="Thantos, Top of the Tower",
+ address=0x1C3007,
+ loc_type= "Boss",
+ ),
+ "World Saved": BombTSALocationData(
+ region="Noah Core",
+ address=0x1C3008,
+ locked_item="Victory",
+ loc_type= "Goal",
+ ),
+
+ "Neverland Guardian Armor": BombTSALocationData(
+ region="Neverland, Secret Room 1",
+ address=0x1C300A,
+ loc_type= "Guardian",
+ ),
+ "Horizon Guardian Armor": BombTSALocationData(
+ region="Horizon, Secret Room 2",
+ address=0x1C300B,
+ loc_type= "Guardian",
+ ),
+ "Starlight Guardian Armor": BombTSALocationData(
+ region="Starlight, Hidden Room",
+ address=0x1C300C,
+ loc_type= "Guardian",
+ ),
+ "Aquanet Guardian Armor": BombTSALocationData(
+ region="Aquanet, Secret Room 2",
+ address=0x1C300D,
+ loc_type= "Guardian",
+ ),
+
+ "Alcatraz Generator": BombTSALocationData(
+ region="Alcatraz, Gravity Generator Room",
+ address=0x1C3010,
+ loc_type= "Generator",
+ ),
+ "Aquanet Generator": BombTSALocationData(
+ region="Aquanet, Gravity Generator Room",
+ address=0x1C3011,
+ loc_type= "Generator",
+ ),
+ "Horizon Generator": BombTSALocationData(
+ region="Horizon, Gravity Generator Room",
+ address=0x1C3012,
+ loc_type= "Generator",
+ ),
+ "Starlight Generator": BombTSALocationData(
+ region="Starlight, Gravity Generator Room",
+ address=0x1C3013,
+ loc_type= "Generator",
+ ),
+ "Neverland Generator": BombTSALocationData(
+ region="Neverland, Gravity Generator Room",
+ address=0x1C3014,
+ loc_type= "Generator",
+ ),
+ "Epikyur Generator": BombTSALocationData(
+ region="Epikyur, Gravity Generator Room",
+ address=0x1C3015,
+ loc_type= "Generator",
+ ),
+ "Thantos Generator": BombTSALocationData(
+ region="Thantos, Gravity Generator Room",
+ address=0x1C3016,
+ loc_type= "Generator",
+ ),
+
+ "Epikyur Part Red": BombTSALocationData(
+ region="Epikyur, Haunted House Coaster Start",
+ address=0x1C3020,
+ loc_type= "Custom",
+ ),
+ "Neverland Part Red": BombTSALocationData(
+ region="Neverland, Through the Line of Fire",
+ address=0x1C3021,
+ loc_type= "Custom",
+ ),
+ "Thantos Part Red": BombTSALocationData(
+ region="Thantos, Compactor",
+ address=0x1C3022,
+ loc_type= "Custom",
+ ),
+ "Starlight Part Red": BombTSALocationData(
+ region="Starlight, Casino Entrance",
+ address=0x1C3023,
+ loc_type= "Custom",
+ ),
+ "Alcatraz Part Red": BombTSALocationData(
+ region="Alcatraz, Prison",
+ address=0x1C3024,
+ loc_type= "Custom",
+ ),
+ "Aquanet Part Red": BombTSALocationData(
+ region="Aquanet, Secret Room 1",
+ address=0x1C3025,
+ loc_type= "Custom",
+ ),
+ "Horizon Part Red": BombTSALocationData(
+ region="Horizon, First Trial",
+ address=0x1C3026,
+ loc_type= "Custom",
+ ),
+
+ "Epikyur Part Green": BombTSALocationData(
+ region="Epikyur, Haunted House Spike Pit",
+ address=0x1C3028,
+ loc_type= "Custom",
+ ),
+ "Neverland Part Green": BombTSALocationData(
+ region="Neverland, Secret Room 2",
+ address=0x1C3029,
+ loc_type= "Custom",
+ ),
+ "Thantos Part Green": BombTSALocationData(
+ region="Thantos, Secret Room 3",
+ address=0x1C302A,
+ loc_type= "Custom",
+ ),
+ "Starlight Part Green": BombTSALocationData(
+ region="Starlight, Slots Room",
+ address=0x1C302B,
+ loc_type= "Custom",
+ ),
+ "Alcatraz Part Green": BombTSALocationData(
+ region="Alcatraz, Secret Room 2",
+ address=0x1C302C,
+ loc_type= "Custom",
+ ),
+ "Aquanet Part Green": BombTSALocationData(
+ region="Aquanet, Secret Room 3",
+ address=0x1C302D,
+ loc_type= "Custom",
+ ),
+ "Horizon Part Green": BombTSALocationData(
+ region="Horizon, Resting Point",
+ address=0x1C302E,
+ loc_type= "Custom",
+ ),
+
+ "Epikyur Part Blue": BombTSALocationData(
+ region="Epikyur, Haunted House Hidden Room",
+ address=0x1C3030,
+ loc_type= "Custom",
+ ),
+ "Neverland Part Blue": BombTSALocationData(
+ region="Neverland, Carrier Works",
+ address=0x1C3031,
+ loc_type= "Custom",
+ ),
+ "Thantos Part Blue": BombTSALocationData(
+ region="Thantos, Subway Destination",
+ address=0x1C3032,
+ loc_type= "Custom",
+ ),
+ "Starlight Part Blue": BombTSALocationData(
+ region="Starlight, Parking Lot",
+ address=0x1C3033,
+ loc_type= "Custom",
+ ),
+ "Alcatraz Part Blue": BombTSALocationData(
+ region="Alcatraz, Secret Room 1",
+ address=0x1C3034,
+ loc_type= "Custom",
+ ),
+ "Aquanet Part Blue": BombTSALocationData(
+ region="Aquanet, Elevator Hub",
+ address=0x1C3035,
+ loc_type= "Custom",
+ ),
+ "Horizon Part Blue": BombTSALocationData(
+ region="Horizon, Second Trial",
+ address=0x1C3036,
+ loc_type= "Custom",
+ ),
+
+ "Epikyur Part Yellow": BombTSALocationData(
+ region="Epikyur, History Museum Military Puzzle",
+ address=0x1C3038,
+ loc_type= "Custom",
+ ),
+ "Neverland Part Yellow": BombTSALocationData(
+ region="Neverland, Safe Point",
+ address=0x1C3039,
+ loc_type= "Custom",
+ ),
+ "Thantos Part Yellow": BombTSALocationData(
+ region="Thantos, Secret Room 1",
+ address=0x1C303A,
+ loc_type= "Custom",
+ ),
+ "Starlight Part Yellow": BombTSALocationData(
+ region="Starlight, Stage Area",
+ address=0x1C303B,
+ loc_type= "Custom",
+ ),
+ "Alcatraz Part Yellow": BombTSALocationData(
+ region="Alcatraz, Final Defense Unit",
+ address=0x1C303C,
+ loc_type= "Custom",
+ ),
+ "Aquanet Part Yellow": BombTSALocationData(
+ region="Aquanet, Tower 3F",
+ address=0x1C303D,
+ loc_type= "Custom",
+ ),
+ "Horizon Part Yellow": BombTSALocationData(
+ region="Horizon, Secret Room 1",
+ address=0x1C303E,
+ loc_type= "Custom",
+ ),
+
+ # Stage Specific
+ "Horizon Left Blue Jewel": BombTSALocationData(
+ region="Horizon, Second Trial",
+ address=0x1C3040,
+ loc_type= "Event",
+ ),
+ "Horizon Right Blue Jewel": BombTSALocationData(
+ region="Horizon, First Trial",
+ address=0x1C3041,
+ loc_type= "Event",
+ ),
+ "Horizon Left Green Jewel": BombTSALocationData(
+ region="Horizon, Fourth Trial",
+ address=0x1C3042,
+ loc_type= "Event",
+ ),
+ "Horizon Right Green Jewel": BombTSALocationData(
+ region="Horizon, Third Trial",
+ address=0x1C3043,
+ loc_type= "Event",
+ ),
+ "Horizon Middle Green Jewel": BombTSALocationData(
+ region="Horizon, Last Trial",
+ address=0x1C3044,
+ loc_type= "Event",
+ ),
+ "Horizon Red Jewel": BombTSALocationData(
+ region="Horizon, Final Deposit",
+ address=0x1C3045,
+ loc_type= "Event",
+ ),
+
+ "Starlight King Of Clubs": BombTSALocationData(
+ region="Starlight, Betting Room",
+ address=0x1C3046,
+ loc_type= "Event",
+ ),
+ "Starlight Knight Of Diamonds": BombTSALocationData(
+ region="Starlight, Alleyway",
+ address=0x1C3047,
+ loc_type= "Event",
+ ),
+ "Starlight Ace Of Spaces": BombTSALocationData(
+ region="Starlight, Fountain Square",
+ address=0x1C3048,
+ loc_type= "Event",
+ ),
+ "Starlight Queen Of Hearts": BombTSALocationData(
+ region="Starlight, Parking Lot",
+ address=0x1C3049,
+ loc_type= "Event",
+ ),
+
+ "Epikyur Haunted House Pass": BombTSALocationData(
+ region="Epikyur, Haunted House Storeroom",
+ address=0x1C304A,
+ loc_type= "Event",
+ ),
+ "Epikyur Museum Pass": BombTSALocationData(
+ region="Epikyur, History Museum Military Puzzle",
+ address=0x1C304B,
+ loc_type= "Event",
+ ),
+ "Epikyur Coaster Battery": BombTSALocationData(
+ region="Epikyur, History Museum Showcase Room",
+ address=0x1C304C,
+ loc_type= "Event",
+ ),
+
+ "Thantos Lower Train Battery": BombTSALocationData(
+ region="Thantos, Battle for the Battery",
+ address=0x1C304D,
+ loc_type= "Event",
+ ),
+ "Thantos Upper Train Battery": BombTSALocationData(
+ region="Thantos, Battery Ambush",
+ address=0x1C304E,
+ loc_type= "Event",
+ ),
+
+ "Noah Card Key 1": BombTSALocationData(
+ region="Noah",
+ address=0x1C302F,
+ loc_type= "Warship Key",
+ ),
+ "Noah Card Key 2": BombTSALocationData(
+ region="Noah",
+ address=0x1C303F,
+ loc_type= "Warship Key",
+ ),
+ "Noah Card Key 3": BombTSALocationData(
+ region="Noah",
+ address=0x1C304F,
+ loc_type= "Warship Key",
+ ),
+
+
+#}
+
+#powerup_data_table: Dict[str, BombTSALocationData] = {
+ # Remote Bombs
+ "Alcatraz Remote Security Room":BombTSALocationData(
+ region="Alcatraz, Security Room A",
+ address=0x1C3060,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Remote To the Tower":BombTSALocationData(
+ region="Aquanet, To the Tower",
+ address=0x1C3061,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Remote Fountain Room":BombTSALocationData(
+ region="Aquanet, Fountain Room",
+ address=0x1C306A,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Remote Second Trial":BombTSALocationData(
+ region="Horizon, Second Trial",
+ address=0x1C3062,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Remote Final Deposit":BombTSALocationData(
+ region="Horizon, Final Deposit",
+ address=0x1C3063,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Remote Secret Room":BombTSALocationData(
+ region="Horizon, Secret Room 1",
+ address=0x1C3064,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Remote Casino Lobby":BombTSALocationData(
+ region="Starlight, Casino Lobby",
+ address=0x1C3065,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Remote Waiting Room":BombTSALocationData(
+ region="Starlight, Waiting Room",
+ address=0x1C3066,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Neverland Remote Bonus Room":BombTSALocationData(
+ region="Neverland, Bonus Room",
+ address=0x1C306B,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Neverland Remote Underground Corridor":BombTSALocationData(
+ region="Neverland, Underground Corridor",
+ address=0x1C3067,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Epikyur Remote Haunted House Yard":BombTSALocationData(
+ region="Epikyur, Haunted House Coaster Start",
+ address=0x1C3068,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Thantos Remote Crevice":BombTSALocationData(
+ region="Thantos, The Crevice",
+ address=0x1C3069,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Remote Secret Room":BombTSALocationData(
+ region="Aquanet, Secret Room 3",
+ address=0x1C3070,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+
+ # Gloves
+ "Alcatraz Glove Security Room B": BombTSALocationData(
+ region="Alcatraz, Security Room B",
+ address=0x1C30C0,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Alcatraz Glove Pipe Room": BombTSALocationData(
+ region="Alcatraz, Pipe Room A",
+ address=0x1C30C1,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Epikyur Glove Center Fountain": BombTSALocationData(
+ region="Epikyur, Center Fountain",
+ address=0x1C30C2,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Glove Landing Site": BombTSALocationData(
+ region="Aquanet, Around the Moat",
+ address=0x1C30C3,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Glove Crab Room": BombTSALocationData(
+ region="Aquanet, Water Channels",
+ address=0x1C30C4,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Glove Fountain Room": BombTSALocationData(
+ region="Aquanet, Fountain Room",
+ address=0x1C30C5,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Glove Slot Machines": BombTSALocationData(
+ region="Starlight, Slots Room",
+ address=0x1C30C6,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Glove Magician Room": BombTSALocationData(
+ region="Starlight, Lookout Point",
+ address=0x1C30C7,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Glove Eastern Tower": BombTSALocationData(
+ region="Horizon, Eastern Tower",
+ address=0x1C30C8,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Glove Basement Hub": BombTSALocationData(
+ region="Horizon, Floating Temple",
+ address=0x1C30C9,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Thantos Glove Ladder": BombTSALocationData(
+ region="Thantos, Up the Ladder",
+ address=0x1C30CA,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Thantos Glove Manhole Room": BombTSALocationData(
+ region="Thantos, Secret Room 1",
+ address=0x1C30CB,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Thantos Glove Chasm": BombTSALocationData(
+ region="Thantos, The Crevice",
+ address=0x1C30CC,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+
+
+
+ # Bomb Kicks
+ "Alcatraz Kick Twisted Sewers": BombTSALocationData(
+ region="Alcatraz, Twisted Sewers",
+ address=0x1C30D0,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Alcatraz Kick Security Room": BombTSALocationData(
+ region="Alcatraz, Security Room A",
+ address=0x1C30D1,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Alcatraz Kick Through the Pipe": BombTSALocationData(
+ region="Alcatraz, Through the Pipe",
+ address=0x1C30D2,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Alcatraz Kick Pipe Room": BombTSALocationData(
+ region="Alcatraz, Pipe Room A",
+ address=0x1C30D3,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Neverland Kick Intersection": BombTSALocationData(
+ region="Neverland, Intersection",
+ address=0x1C30D4,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Neverland Kick Cage Room": BombTSALocationData(
+ region="Neverland, Cage Room",
+ address=0x1C30D5,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Neverland Kick Generator Passageway": BombTSALocationData(
+ region="Neverland, Third Passageway",
+ address=0x1C30D6,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Epikyur Kick Center Fountain": BombTSALocationData(
+ region="Epikyur, Center Fountain",
+ address=0x1C30D7,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Kick Pool room": BombTSALocationData(
+ region="Aquanet, Swimming Pool Spa",
+ address=0x1C30D8,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Aquanet Kick Top of Elevator Room": BombTSALocationData(
+ region="Aquanet, Elevator Hub",
+ address=0x1C30D9,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Kick Alleyway": BombTSALocationData(
+ region="Starlight, Alleyway",
+ address=0x1C30DA,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Kick Waiting Room": BombTSALocationData(
+ region="Starlight, Waiting Room",
+ address=0x1C30DB,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Starlight Kick Betting Room": BombTSALocationData(
+ region="Starlight, Betting Room",
+ address=0x1C30DC,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Kick Leading Road": BombTSALocationData(
+ region="Horizon, Leading Road",
+ address=0x1C30DD,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Horizon Kick Last Route": BombTSALocationData(
+ region="Horizon, Last Route",
+ address=0x1C30DE,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Thantos Kick Lower Battery": BombTSALocationData(
+ region="Thantos, Battle for the Battery",
+ address=0x1C30DF,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+ "Thantos Kick Chasm": BombTSALocationData(
+ region="Thantos, The Crevice",
+ address=0x1C30E0,
+ loc_type= "Powerup",
+ base_loc=False,
+ ),
+
+#}
+
+#pommy_data_table: Dict[str, BombTSALocationData] = {
+ # Pommy
+ "Pommy Knuckle Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3050,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Animal Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3051,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Hammer Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3052,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Claw Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3053,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Penguin Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3054,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Beast Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3055,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Mage Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3056,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Knight Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3057,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Devil Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3058,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Cat Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C3059,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Bird Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C305A,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Chicken Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C305B,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Dragon Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C305C,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Dinosaur Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C305D,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Pixie Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C305E,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+ "Pommy Shadow Transformation": BombTSALocationData(
+ region="Menu",
+ address=0x1C305F,
+ loc_type= "Gene",
+ base_loc=False,
+ ),
+
+
+#}
+#shop_data_table: Dict[str, BombTSALocationData] = {
+ #"Shop Heart 1": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C3081,
+ #),
+ #"Shop Heart 2": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C3082,
+ #),
+ #"Shop Heart 3": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C3083,
+ #),
+ #"Shop Heart 4": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C3084,
+ #),
+ #"Shop Heart 5": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C3085,
+ #),
+ #"Shop Full Power": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C3086,
+ #),
+ "Shop Battle A": BombTSALocationData(
+ region="Shop",
+ address=0x1C308A,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Battle B": BombTSALocationData(
+ region="Shop",
+ address=0x1C308B,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Battle C": BombTSALocationData(
+ region="Shop",
+ address=0x1C308C,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Battle D": BombTSALocationData(
+ region="Shop",
+ address=0x1C308D,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+
+
+ "Shop Part Beard": BombTSALocationData(
+ region="Shop",
+ address=0x1C3090,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Tank": BombTSALocationData(
+ region="Shop",
+ address=0x1C3091,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Fan": BombTSALocationData(
+ region="Shop",
+ address=0x1C3092,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Bigfoot Shoes": BombTSALocationData(
+ region="Shop",
+ address=0x1C3093,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Topknot": BombTSALocationData(
+ region="Shop",
+ address=0x1C3094,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Kimono": BombTSALocationData(
+ region="Shop",
+ address=0x1C3095,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Sword": BombTSALocationData(
+ region="Shop",
+ address=0x1C3096,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Sandals": BombTSALocationData(
+ region="Shop",
+ address=0x1C3097,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Cat Ears": BombTSALocationData(
+ region="Shop Aquanet",
+ address=0x1C3098,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Cat Suit": BombTSALocationData(
+ region="Shop Aquanet",
+ address=0x1C3099,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Cat Gloves": BombTSALocationData(
+ region="Shop Aquanet",
+ address=0x1C309A,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Cat Slippers": BombTSALocationData(
+ region="Shop Aquanet",
+ address=0x1C309B,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Headgear": BombTSALocationData(
+ region="Shop Horizon",
+ address=0x1C309C,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Elephant Suit": BombTSALocationData(
+ region="Shop Horizon",
+ address=0x1C309D,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Gloves": BombTSALocationData(
+ region="Shop Horizon",
+ address=0x1C309E,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Kung Fu Shoes": BombTSALocationData(
+ region="Shop Horizon",
+ address=0x1C309F,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Ribbon": BombTSALocationData(
+ region="Shop Starlight",
+ address=0x1C30A0,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Pink Dress": BombTSALocationData(
+ region="Shop Starlight",
+ address=0x1C30A1,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Slash Claws": BombTSALocationData(
+ region="Shop Starlight",
+ address=0x1C30A2,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part High Heels": BombTSALocationData(
+ region="Shop Starlight",
+ address=0x1C30A3,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Rabbit Ears": BombTSALocationData(
+ region="Shop Neverland",
+ address=0x1C30A4,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Duck Suit": BombTSALocationData(
+ region="Shop Neverland",
+ address=0x1C30A5,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Drill": BombTSALocationData(
+ region="Shop Neverland",
+ address=0x1C30A6,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Sneakers": BombTSALocationData(
+ region="Shop Neverland",
+ address=0x1C30A7,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Bald Head": BombTSALocationData(
+ region="Shop Epikyur",
+ address=0x1C30A8,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Apron": BombTSALocationData(
+ region="Shop Epikyur",
+ address=0x1C30A9,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Hand Puppets": BombTSALocationData(
+ region="Shop Epikyur",
+ address=0x1C30AA,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Pommy Slippers": BombTSALocationData(
+ region="Shop Epikyur",
+ address=0x1C30AB,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Gold Helmet": BombTSALocationData(
+ region="Shop Thantos",
+ address=0x1C30AC,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Gold Suit": BombTSALocationData(
+ region="Shop Thantos",
+ address=0x1C30AD,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Gold Gloves": BombTSALocationData(
+ region="Shop Thantos",
+ address=0x1C30AE,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+ "Shop Part Gold Boots": BombTSALocationData(
+ region="Shop Thantos",
+ address=0x1C30AF,
+ loc_type= "Shop",
+ base_loc=False,
+ ),
+
+ # "Shop Hint Baelfael": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C30B0,
+ # ),
+ # "Shop Hint Behemos": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C30B1,
+ # ),
+ # "Shop Hint Ashtarth": BombTSALocationData(
+ # region="Shop",
+ # address=0x1C30B2,
+ # ),
+ # "Shop Hint Zhael": BombTSALocationData(
+ # region="Shop Horizon",
+ # address=0x1C30B3,
+ # ),
+ # "Shop Hint Molok": BombTSALocationData(
+ # region="Shop Horizon",
+ # address=0x1C30B4,
+ # ),
+ # "Shop Hint Zoniha": BombTSALocationData(
+ # region="Shop Neverland",
+ # address=0x1C30B5,
+ # ),
+ # "Shop Hint Epikyur": BombTSALocationData(
+ # region="Epikyur",
+ # address=0x1C30B6,
+ # ),
+ # "Shop Hint Bulzeeb": BombTSALocationData(
+ # region="Shop Epikyur",
+ # address=0x1C30B7,
+ # ),
+ # "Shop Hint Thantos 1": BombTSALocationData(
+ # region="Thantos",
+ # address=0x1C30B8,
+ # ),
+ # "Shop Hint Thantos 2": BombTSALocationData(
+ # region="Thantos",
+ # address=0x1C30B9,
+ # ),
+ # "Shop Hint Thantos 3": BombTSALocationData(
+ # region="Thantos",
+ # address=0x1C30BA,
+ # ),
+ # "Shop Hint Lilith": BombTSALocationData(
+ # region="Shop Thantos",
+ # address=0x1C30BB,
+ # ),
+ # "Shop Hint Rukifellth": BombTSALocationData(
+ # region="Shop Thantos",
+ # address=0x1C30BC,
+ # ),
+ # "Shop Hint Guardian Aquanet": BombTSALocationData(
+ # region="Noah",
+ # address=0x1C30BD,
+ # ),
+ # "Shop Hint Guardian Horizon": BombTSALocationData(
+ # region="Noah",
+ # address=0x1C30BE,
+ # ),
+ # "Shop Hint Guardian Starlight": BombTSALocationData(
+ # region="Noah",
+ # address=0x1C30BF,
+ # ),
+ # "Shop Hint Guardian Neverland": BombTSALocationData(
+ # region="Noah",
+ # address=0x1C30C0,
+ # ),
+
+}
+
+location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}
+shop_location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None and data.loc_type == "Shop"}
+pommy_location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None and data.loc_type == "Gene"}
+powerup_location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None and data.loc_type == "Powerup"}
+
+#shop_region = {name: data.region for name, data in location_data_table.items()}
+
+shop_loc_list = {name: data.address for name, data in location_data_table.items() if data.address is not None and data.loc_type == "Shop"}
+
+locked_locations = {name: data for name, data in location_data_table.items() if data.locked_item}
diff --git a/worlds/bomberman_tsa/Options.py b/worlds/bomberman_tsa/Options.py
new file mode 100644
index 000000000000..f02a53af3d89
--- /dev/null
+++ b/worlds/bomberman_tsa/Options.py
@@ -0,0 +1,136 @@
+from dataclasses import dataclass
+from Options import Choice, Range, Toggle, OptionGroup, DefaultOnToggle, Removed, PerGameCommonOptions, StartInventoryPool, DeathLink
+
+class Goal(Choice):
+ """
+ Required Goal to complete the seed
+ Noah - Clear the game by defeating the final boss, determined by the final boss option
+ Generators - Clear the game by clearing a certain number of planets
+ Stone Hunt - Clear the game by collecting all of the Elemental Stones.
+ """
+ #display_name = "Goal"
+ option_noah = 0
+ option_generators = 1
+ option_stones = 2
+ default = 0
+
+class NoahAccess(Choice):
+ """
+ Requirement to access Warship Noah, the game's final dungeon
+ Generators - Open up Noah by clearing a certain number of planets
+ Warship Keys - Open Noah by collecting 3 Warship Keys from the item pool
+ Coordinates - Opens Noah by collecting the coordinates to Noah from the item pool
+ """
+ option_generators = 0
+ option_keys = 1
+ option_coord = 2
+ default = 0
+
+class BossGoal(Choice):
+ """Determines which final boss should be fought at Noah to complete the game"""
+ option_sthertoth = 0
+ option_lilith = 1
+ option_angel = 2
+ default = 0
+
+class RequiredPlanets(Range):
+ """Number of generators which need to be destroyed to access Noah or Clear the game depending on other options"""
+ display_name = "Required Generators"
+ range_start = 1
+ range_end = 7
+ default = 5
+
+class StartingElement(Choice):
+ option_fire = 0
+ option_ice = 1
+ option_wind = 2
+ option_earth = 3
+ option_lightning = 4
+ option_light = 5
+ option_dark = 6
+ option_random_element = 7
+ default = 7
+
+class StartingPlanets(Range):
+ """Number of random planets you can visit from the start, you can always visit Alcatraz, Noah will not be included."""
+ display_name = "Starting Planets"
+ range_start = 1
+ range_end = 7
+ default = 2
+
+class IncludeKeys(Toggle):
+ """If enabled will add 3 Warship Key items into the item pool which each will autocomplete sections of Warship Noah
+ but are not necessary to progress, this option will always be set if keys are set to open Warship Noah"""
+
+class IncludeTraps(DefaultOnToggle):
+ """If enabled will add negative traps to the item pool"""
+
+class ReduceFood(Toggle):
+ """Reduces how much food is required to get Pommy's evolution checks"""
+
+class Shopsanity(Toggle):
+ """Adds shop locations into the location pool"""
+
+class Pommysanity(Toggle):
+ """Adds pommy transformation locations into the location pool"""
+
+class PommyShop(Toggle):
+ """When Shopsanity is enabled, this will place Pommy Gene transformation items onto certain shop locations"""
+
+class PowerupSanity(Toggle):
+ """When enabled will include Power Glove and Bomb Kick items as checks"""
+
+class RandomMusic(Toggle):
+ """Randomizes the music table every time the client is loaded"""
+class RandomSound(Toggle):
+ """Randomizes the Sound Effect table every time the client is loaded"""
+
+class RandomEffect(Toggle):
+ """Randomizes the Animated Effects, likely pretty unstable for now"""
+
+bomberman_tsa_option_groups = [
+ OptionGroup("Goal Options", [
+ Goal,
+ NoahAccess,
+ RequiredPlanets,
+ BossGoal,
+ IncludeKeys,
+ ]),
+ OptionGroup("Gameplay Options", [
+ StartingElement,
+ StartingPlanets,
+ ReduceFood,
+ PowerupSanity,
+ Shopsanity,
+ Pommysanity,
+ PommyShop,
+ IncludeTraps,
+ DeathLink,
+ ]),
+ OptionGroup("Randomization Options", [
+ RandomMusic,
+ RandomSound,
+ RandomEffect,
+
+ ]),
+]
+
+@dataclass
+class BombTSAOptions(PerGameCommonOptions):
+ game_goal: Goal
+ noah_open : NoahAccess
+ planet_required: RequiredPlanets
+ noah_boss: BossGoal
+ start_element: StartingElement
+ start_planet: StartingPlanets
+ death_link: DeathLink
+ random_music: RandomMusic
+ random_sound: RandomSound
+ random_efx: RandomEffect
+ reduce_food: ReduceFood
+ pommysanity: Pommysanity
+ shopsanity: Shopsanity
+ powersanity: PowerupSanity
+ pommyshop: PommyShop
+ include_traps: IncludeTraps
+ include_warkeys: IncludeKeys
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/Regions.py b/worlds/bomberman_tsa/Regions.py
new file mode 100644
index 000000000000..958ea274917b
--- /dev/null
+++ b/worlds/bomberman_tsa/Regions.py
@@ -0,0 +1,208 @@
+from typing import Dict, List, NamedTuple
+
+
+class BombTSARegionData(NamedTuple):
+ connecting_regions: List[str] = []
+
+
+region_data_table: Dict[str, BombTSARegionData] = {
+ "Menu": BombTSARegionData(["Alcatraz","Aquanet","Horizon","Starlight","Neverland","Epikyur","Thantos","Noah","Shop"]),
+ "Alcatraz": BombTSARegionData(["Alcatraz, Prison"]),
+ "Aquanet": BombTSARegionData(["Aquanet, First Room"]),
+ "Horizon": BombTSARegionData(["Horizon, First Intersection"]),
+ "Starlight": BombTSARegionData(["Starlight, Parking Lot"]),
+ "Neverland": BombTSARegionData(["Neverland, Landing Point"]),
+ "Epikyur": BombTSARegionData(["Epikyur, Entrance"]),
+ "Thantos": BombTSARegionData(["Thantos, Starting Point"]),
+ "Noah": BombTSARegionData(["Noah Core"]),
+ "Noah Core": BombTSARegionData(),
+ # "Aquanet Elevator": BombTSARegionData(["Aquanet Tower"]),
+ # "Aquanet Tower":BombTSARegionData(),
+ # "Horizon Basement": BombTSARegionData(),
+ # "Starlight Card Hunt": BombTSARegionData(),
+ # "Epikyur Haunted House": BombTSARegionData(["Epikyur Coaster"]),
+ # "Epikyur Museum": BombTSARegionData(),
+ # "Epikyur Coaster": BombTSARegionData(),
+ # "Thantos Trainside": BombTSARegionData(),
+
+ "Shop": BombTSARegionData(["Shop Aquanet","Shop Horizon","Shop Starlight","Shop Neverland","Shop Epikyur","Shop Thantos"]),
+ "Shop Aquanet": BombTSARegionData(),
+ "Shop Horizon": BombTSARegionData(),
+ "Shop Starlight": BombTSARegionData(),
+ "Shop Neverland": BombTSARegionData(),
+ "Shop Epikyur": BombTSARegionData(),
+ "Shop Thantos": BombTSARegionData(),
+
+
+ "Neverland, Entry Point": BombTSARegionData(["Neverland, Bonus Room","Neverland, Through the Line of Fire"]), #0x0822
+ "Neverland, Bonus Room": BombTSARegionData(), #0x0823
+ "Neverland, Through the Line of Fire": BombTSARegionData(["Neverland, Intersection"]), #0x0824
+ "Neverland, Intersection": BombTSARegionData(["Neverland, Conveyor Belts","Neverland, Potholes","Neverland, Secret Room 1"]), #0x0825
+ "Neverland, Conveyor Belts": BombTSARegionData(["Neverland, First Passageway"]), #0x0826
+ "Neverland, Potholes": BombTSARegionData(["Neverland, Second Passageway"]), #0x0827
+ "Neverland, Carrier Works": BombTSARegionData(["Neverland, Switch Room"]), #0x0828
+ "Neverland, Switch Room": BombTSARegionData(["Neverland, Secret Room 2"]), #0x0829
+ "Neverland, Bridge Room": BombTSARegionData(["Neverland, Third Passageway"]), #0x082A
+ "Neverland, Cage Room": BombTSARegionData(["Neverland, Warehousing"]), #0x082B
+ "Neverland, Safe Point": BombTSARegionData(["Neverland, Underground Corridor"]), #0x082C
+ "Neverland, Underground Corridor": BombTSARegionData(["Neverland, Bridge Room"]), #0x082D
+ "Neverland, Warehousing": BombTSARegionData(["Neverland, Furnace"]), #0x082E
+ "Neverland, Furnace": BombTSARegionData(["Neverland, Safe Point"]), #0x082F
+ "Neverland, First Passageway": BombTSARegionData(["Neverland, Carrier Works"]), #0x0830
+ "Neverland, Second Passageway": BombTSARegionData(["Neverland, Cage Room"]), #0x0831
+ "Neverland, Landing Point": BombTSARegionData(["Neverland, Entry Point"]), #0x0832
+ "Neverland, Third Passageway": BombTSARegionData(["Neverland, Gravity Generator Room"]), #0x0833
+ "Neverland, Gravity Generator Room": BombTSARegionData(), #0x0834
+
+ "Aquanet, Around the Moat": BombTSARegionData(["Aquanet, Beyond the Moat","Aquanet, Secret Room 2"]), #0x0835
+ "Aquanet, First Room": BombTSARegionData(["Aquanet, Second Room"]), #0x0836
+ "Aquanet, Second Room": BombTSARegionData(["Aquanet, Third Room"]), #0x0837
+ "Aquanet, Third Room": BombTSARegionData(["Aquanet, Swimming Pool Spa"]), #0x0838
+ "Aquanet, Swimming Pool Spa": BombTSARegionData(["Aquanet, Behind the Moat","Aquanet, Secret Room 1"]), #0x0839
+ "Aquanet, Behind the Moat": BombTSARegionData(["Aquanet, Around the Moat"]), #0x083A
+ "Aquanet, Beyond the Moat": BombTSARegionData(["Aquanet, Elevator Hub"]), #0x083B
+ "Aquanet, Elevator Hub": BombTSARegionData(["Aquanet, Hidden Balcony","Aquanet, Behemos' Lair"]), #0x083C
+ "Aquanet, Hidden Balcony": BombTSARegionData(["Aquanet, Water Channels"]), #0x083D
+ "Aquanet, Water Channels": BombTSARegionData(["Aquanet, Fountain Room"]), #0x083E
+ "Aquanet, Fountain Room": BombTSARegionData(["Aquanet, Secret Room 3"]), #0x083F
+ "Aquanet, Secret Room 3": BombTSARegionData(), #0x0840
+ "Aquanet, Elevator Stopping Point": BombTSARegionData(), #0x0841
+ "Aquanet, Behemos' Lair": BombTSARegionData(["Aquanet, To the Tower"]), #0x0842
+ "Aquanet, To the Tower": BombTSARegionData(["Aquanet, Tower 1F"]), #0x0843
+ "Aquanet, Tower 1F": BombTSARegionData(["Aquanet, Tower 2F"]), #0x0844
+ "Aquanet, Tower 2F": BombTSARegionData(["Aquanet, Tower 3F"]), #0x0845
+ "Aquanet, Tower 3F": BombTSARegionData(["Aquanet, Gravity Generator Room"]), #0x0846
+ "Aquanet, Gravity Generator Room": BombTSARegionData(), #0x0847
+
+ "Alcatraz, Prison": BombTSARegionData(["Alcatraz, Sewer Entrance","Alcatraz, Secret Room 1"]), #0x0848
+ "Alcatraz, Sewer Entrance": BombTSARegionData(["Alcatraz, Twisted Sewers"]), #0x0849
+ "Alcatraz, Twisted Sewers": BombTSARegionData(["Alcatraz, Security Room A","Alcatraz, Through the Pipe"]), #0x084A
+ "Alcatraz, Security Room A": BombTSARegionData(["Alcatraz, Security Room B"]), #0x084B
+ "Alcatraz, Security Room B": BombTSARegionData(["Alcatraz, Sewage Disposal"]), #0x084C
+ "Alcatraz, Sewage Disposal": BombTSARegionData(["Alcatraz, Secret Room 2"]), #0x084D
+ "Alcatraz, Through the Pipe": BombTSARegionData(["Alcatraz, Prison Bridge"]), #0x084E
+ "Alcatraz, Prison Bridge": BombTSARegionData(["Alcatraz, Pipe Room A"]), #0x084F
+ "Alcatraz, Pipe Room A": BombTSARegionData(["Alcatraz, Pipe Room B"]), #0x0850
+ "Alcatraz, Pipe Room B": BombTSARegionData(["Alcatraz, Final Defense Unit"]), #0x0851
+ "Alcatraz, Final Defense Unit": BombTSARegionData(["Alcatraz, Gravity Generator Room"]), #0x0852
+ "Alcatraz, Gravity Generator Room": BombTSARegionData(), #0x0853
+
+ "Horizon, First Intersection": BombTSARegionData(["Horizon, Push-Block Trial","Horizon, Eastern Tower","Horizon, Leading Road"]), #0x0854
+ "Horizon, Eastern Tower": BombTSARegionData(["Horizon, First Trial"]), #0x0855
+ "Horizon, Push-Block Trial": BombTSARegionData(["Horizon, Second Trial"]), #0x0856
+ "Horizon, Leading Road": BombTSARegionData(["Horizon, Resting Point"]), #0x0857
+ "Horizon, First Trial": BombTSARegionData(), #0x0858
+ "Horizon, Second Trial": BombTSARegionData(), #0x0859
+ "Horizon, Resting Point": BombTSARegionData(["Horizon, Floating Temple"]), #0x085A
+ "Horizon, Floating Temple": BombTSARegionData(["Horizon, Twin Bridges","Horizon, Secret Room 1","Horizon, Third Trial","Horizon, Last Route"]), #0x085B
+ "Horizon, Twin Bridges": BombTSARegionData(["Horizon, Final Deposit"]), #0x085C
+ "Horizon, Final Deposit": BombTSARegionData(["Horizon, Gravity Generator Room","Horizon, Last Trial"]), #0x085D
+ "Horizon, Last Route": BombTSARegionData(["Horizon, Fourth Trial"]), #0x085E
+ "Horizon, Fourth Trial": BombTSARegionData(["Horizon, Secret Room 2"]), #0x085F
+ "Horizon, Secret Room 1": BombTSARegionData(), #0x0860
+ "Horizon, Secret Room 2": BombTSARegionData(), #0x0861
+ "Horizon, Third Trial": BombTSARegionData(), #0x0862
+ "Horizon, Last Trial": BombTSARegionData(), #0x0863
+ "Horizon, Gravity Generator Room": BombTSARegionData(), #0x0864
+
+ "Starlight, Parking Lot": BombTSARegionData(["Starlight, Closed Road"]), #0x0865
+ "Starlight, Closed Road": BombTSARegionData(["Starlight, Hidden Room","Starlight, Fountain Square"]), #0x0866
+ "Starlight, Fountain Square": BombTSARegionData(["Starlight, Small Inlet","Starlight, Gravity Generator Room"]), #0x0867
+ "Starlight, Small Inlet": BombTSARegionData(["Starlight, Alleyway"]), #0x0868
+ "Starlight, Alleyway": BombTSARegionData(["Starlight, Casino Entrance"]), #0x0869
+ "Starlight, Casino Entrance": BombTSARegionData(["Starlight, Casino Lobby"]), #0x086A
+ "Starlight, Casino Lobby": BombTSARegionData(["Starlight, Betting Room","Starlight, Slots Room"]), #0x086B
+ "Starlight, Betting Room": BombTSARegionData(), #0x086C
+ "Starlight, Slots Room": BombTSARegionData(["Starlight, Lookout Point","Starlight, Waiting Room"]), #0x086D
+ "Starlight, Waiting Room": BombTSARegionData(["Starlight, Stage Area"]), #0x086E
+ "Starlight, Stage Area": BombTSARegionData(), #0x086F
+ "Starlight, Lookout Point": BombTSARegionData(["Starlight, Wheel of Fortune"]), #0x0870
+ "Starlight, Wheel of Fortune": BombTSARegionData(), #0x0871
+ "Starlight, Gravity Generator Room": BombTSARegionData(), #0x0872
+
+ "Epikyur, Entrance": BombTSARegionData(["Epikyur, Center Fountain"]), #0x0873
+ "Epikyur, Center Fountain": BombTSARegionData(["Epikyur, Tattered Bridge","Epikyur, Misaligned Bridge","Epikyur, Castle of Time First Room"]), #0x0874
+ "Epikyur, Tattered Bridge": BombTSARegionData(["Epikyur, Haunted House Yard"]), #0x0875
+ "Epikyur, Misaligned Bridge": BombTSARegionData(["Epikyur, History Museum Prehistoric Puzzle"]), #0x0876
+ "Epikyur, Haunted House Yard": BombTSARegionData(["Epikyur, Haunted House Lobby"]), #0x0877
+ "Epikyur, Haunted House Lobby": BombTSARegionData(["Epikyur, Haunted House Spike Traps","Epikyur, Haunted House Hidden Room"]), #0x0878
+ "Epikyur, Haunted House Spike Traps": BombTSARegionData(["Epikyur, Haunted House Mirror Room"]), #0x0879
+ "Epikyur, Haunted House Mirror Room": BombTSARegionData(["Epikyur, Haunted House Spike Pit"]), #0x087A
+ "Epikyur, Haunted House Spike Pit": BombTSARegionData(["Epikyur, Haunted House Storeroom"]), #0x087B
+ "Epikyur, Haunted House Storeroom": BombTSARegionData(["Epikyur, Haunted House Coaster Start"]), #0x087C
+ "Epikyur, History Museum Prehistoric Puzzle": BombTSARegionData(["Epikyur, History Museum Military Puzzle","Epikyur, History Museum Showcase Room"]), #0x087D
+ "Epikyur, History Museum Military Puzzle": BombTSARegionData(), #0x087E
+ "Epikyur, History Museum Showcase Room": BombTSARegionData(), #0x087F
+ "Epikyur, Castle of Time First Room": BombTSARegionData(["Epikyur, Castle of Time Second Room"]), #0x0880
+ "Epikyur, Castle of Time Second Room": BombTSARegionData(["Epikyur, Gravity Generator Room"]), #0x0881
+ "Epikyur, Coaster Body 1": BombTSARegionData(), #0x0882
+ "Epikyur, Haunted House Coaster Start": BombTSARegionData(["Epikyur, Coaster Finish"]), #0x0883
+ "Epikyur, Coaster Body 2": BombTSARegionData(), #0x0884
+ "Epikyur, Coaster Finish": BombTSARegionData(), #0x0885
+ "Epikyur, Gravity Generator Room": BombTSARegionData(), #0x0886
+
+ "Thantos, Starting Point": BombTSARegionData(["Thantos, Streets"]), #0x0887
+ "Thantos, Streets": BombTSARegionData(["Thantos, Up the Ladder","Thantos, Wrecked Lot","Thantos, Subway Entrance","Thantos, Hangout"]), #0x0888
+ "Thantos, Up the Ladder": BombTSARegionData(), #0x0889
+ "Thantos, Wrecked Lot": BombTSARegionData(["Thantos, Battle for the Battery","Thantos, Battery Ambush"]), #0x088A
+ "Thantos, Battery Ambush": BombTSARegionData(), #0x088B
+ "Thantos, Battle for the Battery": BombTSARegionData(["Thantos, Compactor"]), #0x088C
+ "Thantos, Compactor": BombTSARegionData(), #0x088D
+ "Thantos, Subway Entrance": BombTSARegionData(["Thantos, Aboard the Subway"]), #0x088E
+ "Thantos, Aboard the Subway": BombTSARegionData(["Thantos, Subway Destination"]), #0x088F
+ "Thantos, Subway Destination": BombTSARegionData(["Thantos, Supposed Dead End"]), #0x0890
+ "Thantos, Supposed Dead End": BombTSARegionData(["Thantos, The Crevice"]), #0x0891
+ "Thantos, The Crevice": BombTSARegionData(["Thantos, Voltage Storage Unit"]), #0x0892
+ "Thantos, Voltage Storage Unit": BombTSARegionData(["Thantos, Secret Room 3"]), #0x0893
+ "Thantos, Secret Room 3": BombTSARegionData(), #0x0894
+ "Thantos, Hangout": BombTSARegionData(["Thantos, Back Alley","Thantos, Hidden Territory","Thantos, Secret Room 1"]), #0x0895
+ "Thantos, Secret Room 1": BombTSARegionData(), #0x0896
+ "Thantos, Back Alley": BombTSARegionData(["Thantos, Gravity Generator Room"]), #0x0897
+ "Thantos, Hidden Territory": BombTSARegionData(["Thantos, Top of the Tower"]), #0x0898
+ "Thantos, Gravity Generator Room": BombTSARegionData(), #0x0899
+
+ "Warship Noah, Runway": BombTSARegionData(), #0x089A
+ "Warship Noah, Sliding Floors (West)": BombTSARegionData(), #0x089B
+ "Warship Noah, Sliding Floors (East)": BombTSARegionData(), #0x089C
+ "Warship Noah, Security Barrier Control Room": BombTSARegionData(), #0x089D
+ "Warship Noah, Card Key Room": BombTSARegionData(), #0x089E
+ "Warship Noah, Storage Area": BombTSARegionData(), #0x089F
+ "Warship Noah, Bridge Lift": BombTSARegionData(), #0x08A0
+ "Warship Noah, Western Defensive Unit": BombTSARegionData(), #0x08A1
+ "Warship Noah, Left Engine Room": BombTSARegionData(), #0x08A2
+ "Warship Noah, Eastern Defensive Unit": BombTSARegionData(), #0x08A3
+ "Warship Noah, Right Engine Room": BombTSARegionData(), #0x08A4
+ "Warship Noah, Engine Defense Mechanism Control Room": BombTSARegionData(), #0x08A5
+ "Warship Noah, Generator Room": BombTSARegionData(), #0x08A6
+ "Warship Noah, Main Reactor": BombTSARegionData(), #0x08A7
+ "Warship Noah, Power Shaft": BombTSARegionData(), #0x08A8
+
+ "Alcatraz, Secret Room 1": BombTSARegionData(), #0x08A9
+ "Starlight, Hidden Room": BombTSARegionData(), #0x08AA
+ "Neverland, Secret Room 1": BombTSARegionData(), #0x08AB
+ "Epikyur, Haunted House Hidden Room": BombTSARegionData(), #0x08AC
+ "Warship Noah, Secret Room (Normally unused.)": BombTSARegionData(), #0x08AD
+ "Aquanet, Secret Room 1": BombTSARegionData(), #0x08AE
+ #"Alcatraz, Cell Room": BombTSARegionData(), #0x08AF
+ #"Tutorial Area": BombTSARegionData(), #0x08B0
+ "Thantos, Top of the Tower": BombTSARegionData(), #0x08B1
+ "Warship Noah, Sthertoth?": BombTSARegionData(), #0x08B2
+ "Alcatraz, Secret Room 2": BombTSARegionData(), #0x08B3
+ "Aquanet, Secret Room 2": BombTSARegionData(), #0x08B4
+ "Neverland, Secret Room 2": BombTSARegionData(), #0x08B5
+ #"Alcatraz, BHB Army Ship Storeroom": BombTSARegionData(), #0x08B6
+ #"Alcatraz, Unused Secret Room (area code)?": BombTSARegionData(), #0x08B7
+
+ "Warship Noah, Below Headquarters": BombTSARegionData(), #0x08B8
+ "Warship Noah, Central Command Room": BombTSARegionData(), #0x08B9
+ "Warship Noah, The Top": BombTSARegionData(), #0x08BA
+ "Warship Noah, The Heart": BombTSARegionData(), #0x08BB
+ "Warship Noah, The God of Chaos": BombTSARegionData(), #0x08BC
+ "Warship Noah, The Angel of Light & Shadow": BombTSARegionData(), #0x08BD
+
+}
+
+def get_exit(region, exit_name):
+ for exit in region.exits:
+ if exit.connected_region.name == exit_name:
+ return exit
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/Rom.py b/worlds/bomberman_tsa/Rom.py
new file mode 100644
index 000000000000..6dfedd7c6438
--- /dev/null
+++ b/worlds/bomberman_tsa/Rom.py
@@ -0,0 +1,168 @@
+import hashlib
+import math
+import os
+import struct
+import random
+
+from settings import get_settings
+import Utils
+from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
+
+from worlds.AutoWorld import World
+
+NOP = bytes([0x00,0x00,0x00,0x00])
+MD5Hash = "aec1fdb0f1caad86c9f457989a4ce482"
+
+OPTIONS_BASE = 0x99FD0
+
+class BombTSAProcedurePatch(APProcedurePatch, APTokenMixin):
+ game = "Bomberman The Second Attack"
+ hash = MD5Hash
+ patch_file_ending = ".apbombtsa"
+ result_file_ending = ".z64"
+ #name: str = ""
+
+ @classmethod
+ def get_source_data(cls) -> bytes:
+ return get_base_rom_bytes()
+
+def write_tokens(world:World, patch:BombTSAProcedurePatch, startbomb):
+ # Write Rom name
+ for j, b in enumerate(world.romName):
+ patch.write_token(APTokenTypes.WRITE, 0x99FE0 + j, struct.pack(" bytes:
+ base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
+ if not base_rom_bytes:
+ file_name = get_base_rom_path(file_name)
+ base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb")))
+
+ basemd5 = hashlib.md5()
+ basemd5.update(base_rom_bytes)
+ md5hash = basemd5.hexdigest()
+ if MD5Hash !=md5hash:
+ raise Exception("Supplied Rom does not match known MD5 for Bobmerman The Second Attack")
+ get_base_rom_bytes.base_rom_bytes = base_rom_bytes
+ return base_rom_bytes
+
+def get_base_rom_path(file_name: str="")-> str:
+ if not file_name:
+ file_name = get_settings().bombermantsa_settings.rom_file
+ if not os.path.exists(file_name):
+ file_name= Utils.user_path(file_name)
+ return file_name
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/Rules.py b/worlds/bomberman_tsa/Rules.py
new file mode 100644
index 000000000000..35b661796575
--- /dev/null
+++ b/worlds/bomberman_tsa/Rules.py
@@ -0,0 +1,457 @@
+from typing import Callable, TYPE_CHECKING
+
+from BaseClasses import CollectionState
+from .Items import elemental_stones
+
+if TYPE_CHECKING:
+ from . import BombTSAWorld
+
+def has_all_elements(state,player): # Debug, checks to see if you have all the element stones
+ return (state.has("Fire Stone",player) and state.has("Ice Stone",player) and state.has("Wind Stone",player) and state.has("Earth Stone",player)
+ and state.has("Lightning Stone",player) and state.has("Dark Stone",player) and state.has("Light Stone",player) )
+
+def can_destroy_with_immunity(state, player, immunities):
+ #stones = ["Fire Stone","Ice Stone","Wind Stone","Earth Stone","Lightning Stone","Dark Stone","Light Stone"]
+ stones = elemental_stones.copy()
+ for badstone in immunities:
+ stones.pop(stones.index(badstone))
+ for havestone in stones:
+ if state.has(havestone, player):
+ return True
+ return False
+
+def can_open_car(state, player):
+ return lambda state: state.has("Guardian Glove", player) or (state.has("Earth Stone", player) and state.has("FireUp", player, 2))
+
+def has_all_guardian(state, player): # Debug, checks to see if you have all the guardian armor
+ return state.has("Guardian Glove", player) and state.has("Guardian Helmet", player) and state.has("Guardian Boots", player)
+
+def can_build_ice(state, player):
+ return state.has("Ice Stone",player) and can_move_bombs(state,player)
+
+def has_multibomb(state, player): # has a bomb that can place more than 1 of itself
+ return state.has("Fire Stone",player) or state.has("Ice Stone",player) or state.has("Wind Stone",player) or state.has("Lightning Stone",player)
+
+def can_destroy(state,player):
+ return state.has("Fire Stone",player) or state.has("Electric Stone",player) or state.has("Ice Stone",player) or state.has("Earth Stone", player)
+
+def can_kill_chompers(state, player):
+ return can_destroy_with_immunity(state, player, ["Light Stone","Wind Stone"])
+
+def can_bomb_jump(state,player): # Depreciated?
+ return (state.has("Fire Stone",player) or state.has("Electric Stone",player) or state.has("Ice Stone",player) or state.has("Wind Stone", player)) and state.has("BombUp", player, 2)
+
+def can_drain_aquanet_fountain(state, player):
+ return (state.has("Guardian Glove", player) and has_multibomb(state, player) and state.has("BombUp", player)) or can_build_ice(state, player),
+
+def can_hit_high_object(state,player):
+ return state.has("Guardian Glove", player) or (state.has("Ice Stone",player) and state.has("FireUp", player, 2)) or state.has("Dark Stone", player) or state.has("Light Stone", player)
+
+def starlight_card_hunt(state, player):
+ return state.can_reach_location("Starlight Zhael Defeated", player) and state.can_reach_region("Starlight, Wheel of Fortune", player),
+
+def can_hit_fountain(state, player):
+ return state.has("Ice Stone", player) and (state.has("Guardian Glove", player) or state.has("FireUp", player, 2))
+
+def can_build_ladder(state,player,bombcnt):
+ return state.has("Guardian Glove", player) and state.has("Guardian Helmet", player) and (((state.has("Fire Stone", player) or state.has("Lightning Stone", player)) and state.has("BombUp", player, bombcnt)) or (has_multibomb(state, player) and state.has("BombUp", player, (bombcnt+1))))
+
+def can_move_bombs(state,player): # Can kick or throw
+ return state.has("Guardian Glove", player) or state.has("Guardian Boots", player)
+
+def get_region_rules(player):
+ return {
+ "Menu -> Alcatraz":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone","Light Stone"]),
+ "Menu -> Aquanet":
+ lambda state: state.has("Aquanet Coordinates", player),
+ "Menu -> Horizon":
+ lambda state: state.has("Horizon Coordinates", player),
+ "Menu -> Starlight":
+ lambda state: state.has("Starlight Coordinates", player),
+ "Menu -> Neverland":
+ lambda state: state.has("Neverland Coordinates", player),
+ "Menu -> Epikyur":
+ lambda state: state.has("Epikyur Coordinates", player),
+ "Menu -> Thantos":
+ lambda state: state.has("Thantos Coordinates", player),
+ #"Aquanet -> Aquanet Elevator":
+ # lambda state: (state.has("Guardian Glove", player) and has_multibomb(state, player) and state.has("BombUp", player)) or can_build_ice(state, player),
+ #"Aquanet Elevator -> Aquanet Tower":
+ # lambda state: can_hit_fountain(state, player) and state.has("Fire Stone", player),
+
+ #"Horizon -> Horizon Basement":
+ # lambda state: state.has("Blue Gems", player) and can_destroy_with_immunity(state,player,["Wind Stone"]),
+
+ #"Starlight -> Starlight Card Hunt":
+ # lambda state: can_destroy_with_immunity(state, player, ["Fire Stone","Earth Stone","Wind Stone","Light Stone"]),
+
+ #"Epikyur -> Epikyur Haunted House":
+ # lambda state: can_hit_fountain(state, player) and state.has("Guardian Glove", player) and state.has("Wind Stone", player) and state.has("Earth Stone", player),
+ #"Epikyur -> Epikyur Museum":
+ # lambda state: state.has("Earth Stone", player),
+ #"Epikyur Haunted House -> Epikyur Coaster":
+ # lambda state: state.has("Earth Stone", player) and state.has("Museum Pass", player),
+
+ #"Thantos -> Thantos Trainside":
+ # lambda state: state.has("Train Batteries", player) and can_destroy_with_immunity(state, player, ["Dark Stone"]),
+
+ "Noah -> Noah Core":
+ lambda state: has_all_elements(state,player) and has_all_guardian(state, player),
+ "Menu -> Shop":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Shop -> Shop Aquanet":
+ lambda state: state.can_reach_location("Aquanet Generator", player),
+ "Shop -> Shop Horizon":
+ lambda state: state.can_reach_location("Horizon Generator", player),
+ "Shop -> Shop Starlight":
+ lambda state: state.can_reach_location("Starlight Generator", player),
+ "Shop -> Shop Neverland":
+ lambda state: state.can_reach_location("Neverland Generator", player),
+ "Shop -> Shop Epikyur":
+ lambda state: state.can_reach_location("Epikyur Generator", player),
+ "Shop -> Shop Thantos":
+ lambda state: state.can_reach_location("Thantos Generator", player),
+
+
+ # Alcatraz
+ "Alcatraz, Prison -> Alcatraz, Secret Room 1":
+ lambda state: state.can_reach_location("Alcatraz Baelfael Defeated", player),
+ "Alcatraz, Sewer Entrance -> Alcatraz, Twisted Sewers":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Alcatraz, Security Room A -> Alcatraz, Security Room B":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone"]),
+ "Alcatraz, Security Room B -> Alcatraz, Sewage Disposal":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone"]),
+ "Alcatraz, Sewage Disposal -> Alcatraz, Secret Room 2":
+ lambda state: state.can_reach_location("Alcatraz Baelfael Defeated", player),
+ "Alcatraz, Twisted Sewers -> Alcatraz, Through the Pipe":
+ lambda state: state.can_reach_location("Alcatraz Baelfael Defeated", player),
+
+ "Alcatraz, Through the Pipe -> Alcatraz, Prison Bridge":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Alcatraz, Pipe Room B -> Alcatraz, Final Defense Unit":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone"]),
+
+ # Aquanet
+ "Aquanet, First Room -> Aquanet, Second Room":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Aquanet, Swimming Pool Spa -> Aquanet, Behind the Moat":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Aquanet, Swimming Pool Spa -> Aquanet, Secret Room 1":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Aquanet, Around the Moat -> Aquanet, Secret Room 2":
+ lambda state: state.has("Earth Stone", player),
+ "Aquanet, Around the Moat -> Aquanet, Secret Room 2":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Aquanet, Elevator Hub -> Aquanet, Hidden Balcony":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Aquanet, Hidden Balcony -> Aquanet, Water Channels":
+ lambda state: can_destroy_with_immunity(state, player, ["Ice Stone"]),
+ "Aquanet, Water Channels -> Aquanet, Fountain Room":
+ lambda state: can_drain_aquanet_fountain(state, player),
+ "Aquanet, Elevator Hub -> Aquanet, Behemos' Lair":
+ lambda state: can_drain_aquanet_fountain(state, player) and can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Aquanet, Tower 1F -> Aquanet, Tower 2F":
+ lambda state: state.has("Fire Stone", player) and state.has("Ice Stone", player) and state.has("Guardian Glove", player),
+ "Aquanet, Tower 2F -> Aquanet, Tower 3F":
+ lambda state: can_build_ice(state, player) and can_destroy_with_immunity(state, player, ["Ice Stone"]),
+ "Aquanet, Fountain Room -> Aquanet, Secret Room 3":
+ lambda state: state.can_reach_location("Aquanet Generator", player),
+
+ # Horizon
+ "Horizon, Eastern Tower -> Horizon, First Trial":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone","Wind Stone"]),
+ "Horizon, Leading Road -> Horizon, Resting Point":
+ lambda state: state.has("Blue Gems", player) and can_destroy_with_immunity(state,player,["Wind Stone"]),
+ "Horizon, Floating Temple -> Horizon, Secret Room 1":
+ lambda state: state.has("Wind Stone", player),
+ "Horizon, Last Route -> Horizon, Fourth Trial":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone"]),
+ "Horizon, Fourth Trial -> Horizon, Secret Room 2":
+ lambda state: state.has("Lightning Stone",player) and state.has("Guardian Helmet", player) and state.has("Guardian Glove", player) and state.has("Wind Stone", player),
+ "Horizon, Final Deposit -> Horizon, Gravity Generator Room":
+ lambda state: state.has("Red Gem", player),
+
+ # Starlight
+ "Starlight, Parking Lot -> Starlight, Closed Road":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Starlight, Closed Road -> Starlight, Fountain Square":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Starlight, Closed Road -> Starlight, Hidden Room":
+ lambda state: state.can_reach_location("Starlight Zhael Defeated", player),
+ "Starlight, Fountain Square -> Starlight, Small Inlet":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone", "Wind Stone"]),
+ "Starlight, Small Inlet -> Starlight, Alleyway":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Starlight, Casino Entrance -> Starlight, Casino Lobby":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone", "Wind Stone"]),
+ "Starlight, Casino Lobby -> Starlight, Slots Room":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone"]),
+ "Starlight, Casino Lobby -> Starlight, Betting Room":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Starlight, Waiting Room -> Starlight, Stage Area":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Starlight, Slots Room -> Starlight, Lookout Point":
+ lambda state: state.can_reach_location("Starlight Zhael Defeated", player) and state.can_reach_region("Starlight, Slots Room", player),
+ "Starlight, Fountain Square -> Starlight, Gravity Generator Room":
+ lambda state: state.has("Royal Straight", player),
+
+
+ # Neverland
+ "Neverland, Entry Point -> Neverland, Through the Line of Fire":
+ lambda state: can_kill_chompers(state, player),
+ "Neverland, Intersection -> Neverland, Secret Room 1":
+ lambda state: state.has("Dark Stone", player) and ((state.has("Ice Stone", player) and can_move_bombs(state,player) )or state.has("Wind Stone", player)),
+ "Neverland, Intersection -> Neverland, Conveyor Belts":
+ lambda state: can_kill_chompers(state, player),
+ "Neverland, Intersection -> Neverland, Potholes":
+ lambda state: can_kill_chompers(state, player),
+ "Neverland, Potholes -> Neverland, Second Passageway":
+ lambda state: can_destroy_with_immunity(state, player, ["Light Stone","Wind Stone"]) and state.can_reach_region("Neverland, Conveyor Belts", player),
+ "Neverland, Carrier Works -> Neverland, Switch Room":
+ lambda state: (state.has("Skates", player) or can_move_bombs(state, player)) and can_kill_chompers(state, player),
+ "Neverland, Switch Room -> Neverland, Secret Room 2":
+ lambda state: state.has("Ice Stone", player) and can_move_bombs(state, player) and state.can_reach_location("Neverland Molok Defeated", player),
+ "Neverland, Furnace -> Neverland, Safe Point":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Neverland, Bridge Room -> Neverland, Third Passageway":
+ lambda state: state.can_reach_region("Neverland, Cage Room", player),
+
+ # Epikyur
+ "Epikyur, Center Fountain -> Epikyur, Tattered Bridge":
+ lambda state: state.has("Ice Stone",player) and (state.has("Guardian Glove", player) or state.has("FireUp", player, 3)),
+ "Epikyur, Tattered Bridge -> Epikyur, Haunted House Yard":
+ lambda state: state.has("Earth Stone", player),
+ "Epikyur, Haunted House Yard -> Epikyur, Haunted House Lobby":
+ lambda state: state.has("Guardian Glove", player),
+ "Epikyur, Haunted House Lobby -> Epikyur, Haunted House Spike Traps":
+ lambda state: state.has("Wind Stone", player),
+ "Epikyur, Haunted House Storeroom -> Epikyur, Haunted House Coaster Start":
+ lambda state: state.has("Museum Pass", player),
+ "Epikyur, Haunted House Coaster Start -> Epikyur, Coaster Finish":
+ lambda state: state.has("Coaster Battery", player),
+ "Epikyur, Center Fountain -> Epikyur, Castle of Time First Room":
+ lambda state: state.can_reach_location("Epikyur Zoniha Defeated", player),
+ "Epikyur, Center Fountain -> Epikyur, Misaligned Bridge":
+ lambda state: state.has("Earth Stone", player),
+ "Epikyur, Castle of Time Second Room -> Epikyur, Gravity Generator Room":
+ lambda state: state.has("Fire Stone", player) and state.has("Ice Stone", player) and state.has("Wind Stone", player) and state.has("Lightning Stone", player) and state.has("Earth Stone", player),
+
+ # Thantos
+ "Thantos, Hangout -> Thantos, Secret Room 1":
+ lambda state: state.has("Earth Stone", player),
+ "Thantos, Back Alley -> Thantos, Gravity Generator Room":
+ lambda state: state.has("Dark Stone", player),
+ "Thantos, Streets -> Thantos, Wrecked Lot":
+ lambda state: state.has("Earth Stone", player) or state.has("Guardian Glove", player),
+ "Thantos, Wrecked Lot -> Thantos, Battle for the Battery":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Thantos, Battle for the Battery -> Thantos, Compactor":
+ lambda state: state.has("Light Stone", player),
+ #"Thantos, Wrecked Lot -> Thantos, Battery Ambush":
+ "Thantos, Subway Entrance -> Thantos, Aboard the Subway":
+ lambda state: state.has("Train Batteries", player),
+ "Thantos, Supposed Dead End -> Thantos, The Crevice":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Thantos, The Crevice -> Thantos, Voltage Storage Unit":
+ lambda state: state.has("Wind Stone", player),
+ "Thantos, Voltage Storage Unit -> Thantos, Secret Room 3":
+ lambda state: state.has("Earth Stone", player),
+ "Thantos, Hidden Territory -> Thantos, Top of the Tower":
+ lambda state: state.has("Lightning Stone",player) and state.has("Guardian Boots", player) and state.can_reach_region("Thantos, Voltage Storage Unit", player),
+
+
+
+
+
+ }
+
+
+
+#"Rule":
+# lambda state: state.has("Item", player, 3),
+
+def get_location_rules(player):
+ return {
+
+ #"Guardian Armor Body":
+ # lambda state: state.has("Guardian Helmet",player) and state.has("Guardian Glove",player) and state.has("FireUp", player, 3) and state.has("Ice Stone", player),
+
+ # Alcatraz
+ "Alcatraz Part Red":
+ lambda state: state.can_reach_region("Alcatraz, Prison Bridge", player),
+ "Alcatraz Part Yellow":
+ lambda state: state.has("Guardian Glove", player),
+ "Alcatraz Generator":
+ lambda state: can_hit_high_object(state,player),
+
+ # Aquanet
+ "Aquanet Part Blue":
+ lambda state: can_build_ladder(state,player, 3),
+ "Aquanet Guardian Armor":
+ lambda state: state.has("Earth Stone", player),
+ "Aquanet Remote Fountain Room":
+ lambda state: state.has("Guardian Glove", player),
+ "Aquanet Remote To the Tower":
+ lambda state: state.has("Guardian Glove", player),
+ # Glove
+ #"Aquanet Behemos Defeated":
+ # lambda state: state.has("Guardian Glove", player) and state.has("Bomb"),
+ #"Aquanet Remote To the Tower":
+ # lambda state:
+ # Ice bomb + Fire Bomb
+ "Aquanet Part Yellow":
+ lambda state: state.has("Wind Stone", player) and state.has("Guardian Glove", player),
+ "Aquanet Generator":
+ lambda state: state.has("Ice Stone", player) and can_destroy_with_immunity(state, player, ["Ice Stone","Dark Stone"]),
+
+
+ # Horizon
+ "Horizon Right Blue Jewel":
+ lambda state: can_destroy_with_immunity(state,player,["Wind Stone"]) or can_move_bombs(state,player),
+ "Horizon Part Blue":
+ lambda state: can_destroy_with_immunity(state,player,["Dark Stone"]),
+ "Horizon Remote Second Trial":
+ lambda state: can_destroy_with_immunity(state,player,["Dark Stone"]),
+ "Horizon Part Red":
+ lambda state: can_build_ladder(state,player, 1),
+
+ # Two Blue Gems
+ "Horizon Remote Final Deposit":
+ lambda state: state.has("Guardian Glove", player),
+ # Wind Bomb
+ "Horizon Right Green Jewel":
+ lambda state: state.has("Wind Stone", player),
+
+ "Horizon Guardian Armor":
+ lambda state: state.has("Lightning Stone", player) and state.has("Wind Stone", player) and state.has("Guardian Glove", player),
+ "Horizon Part Yellow":
+ lambda state: state.has("Wind Stone", player),
+ # Two Green Gem
+ "Horizon Left Green Jewel":
+ lambda state: state.has("Wind Stone", player),
+ "Horizon Right Green Jewel":
+ lambda state: state.has("Guardian Helmet", player) and state.has("Wind Stone", player),
+ "Horizon Middle Green Jewel":
+ lambda state: state.has("Wind Stone", player) and state.has("Green Gems", player),
+
+ "Horizon Red Jewel":
+ lambda state: state.has("Wind Stone", player) and state.has("Green Gems", player),
+ # Red Gem
+ "Horizon Generator":
+ lambda state: state.has("Wind Stone", player) and can_move_bombs(state, player),
+
+ # Starlight
+ "Starlight Part Red":
+ lambda state: can_build_ladder(state, player, 3),
+ "Starlight Part Blue":
+ lambda state: can_move_bombs(state, player) and state.can_reach_region("Starlight, Slots Room", player),
+ "Starlight Remote Casino Lobby":
+ lambda state: can_build_ladder(state, player, 2),
+ "Starlight Remote Waiting Room":
+ lambda state: state.has("Wind Stone", player),
+ "Starlight Generator":
+ lambda state: state.has("Lightning Stone", player) and can_build_ice(state, player) and can_move_bombs(state, player),
+ "Starlight Zhael Defeated":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Starlight Part Yellow":
+ lambda state: state.can_reach_location("Starlight Zhael Defeated", player),
+ "Starlight King Of Clubs":
+ lambda state: starlight_card_hunt(state, player),
+ "Starlight Knight Of Diamonds":
+ lambda state: starlight_card_hunt(state, player),
+ "Starlight Ace Of Spaces":
+ lambda state: starlight_card_hunt(state, player),
+ "Starlight Queen Of Hearts":
+ lambda state: starlight_card_hunt(state, player),
+
+ # Neverland
+ "Neverland Part Red":
+ lambda state: state.has("Wind Stone", player) or can_build_ladder(state, player, 2),
+ "Neverland Remote Bonus Room":
+ lambda state: state.has("Wind Stone", player) or can_build_ladder(state, player, 3),
+ "Neverland Part Blue":
+ lambda state: can_build_ladder(state, player, 3),
+ "Neverland Molok Defeated":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ #"Neverland Guardian Armor":
+ # lambda state: state.has("Dark Stone", player) and ((state.has("Ice Stone", player) and can_move_bombs(state,player) )or state.has("Wind Stone", player)),
+ "Neverland Generator":
+ lambda state: state.has("Earth Stone", player) and can_build_ice(state, player) and state.has("Guardian Boots", player) and state.has("Wind Stone", player),
+
+ # Epikyur
+ "Epikyur Haunted House Pass":
+ lambda state: state.has("Guardian Glove", player),
+ "Epikyur Coaster Battery":
+ lambda state: state.has("Haunted House Pass", player),
+ "Epikyur Zoniha Defeated":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Epikyur Glove Center Fountain":
+ lambda state: can_build_ice(state, player),
+ "Epikyur Part Green":
+ lambda state: state.has("Wind Stone", player),
+ "Epikyur Part Yellow":
+ lambda state: can_build_ladder(state, player, 2),
+ "Epikyur Museum Pass":
+ lambda state: state.has("Lightning Stone", player),
+ "Epikyur Generator":
+ lambda state: state.has("Lightning Stone", player) and state.has("Wind Stone", player) and state.has("BombUp", player, 2) and state.has("Guardian Boots", player) and (state.has("Guardian Glove", player) or state.has("Light Stone", player)),
+
+ # Thantos
+ "Thantos Part Yellow":
+ lambda state: state.has("Earth Stone", player),
+ "Thantos Generator":
+ lambda state: can_build_ice(state, player) and state.has("Dark Stone", player) and state.has("Wind Stone", player) and state.has("Earth Stone", player),
+ "Thantos Lower Train Battery":
+ lambda state: can_open_car(state, player),
+ "Thantos Upper Train Battery":
+ lambda state: can_open_car(state, player) and (state.has("Lightning Stone", player) or state.has("Earth Stone", player)) ,
+ "Thantos Part Red":
+ lambda state: can_open_car(state, player) and state.has("Light Stone", player),
+ "Thantos Part Blue":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+ "Thantos Remote Crevice":
+ lambda state: state.has("Wind Stone", player),
+ "Thantos Part Green":
+ lambda state: state.has("Wind Stone", player) and state.has("Earth Stone", player),
+ "Thantos Bulzeeb Defeated":
+ lambda state: can_destroy_with_immunity(state, player, ["Dark Stone"]),
+
+ # Noah'
+ "Noah Card Key 1":
+ lambda state: state.has("Guardian Boots", player),
+ "Noah Card Key 2":
+ lambda state: state.has("Lightning Stone", player) and state.has("Earth Stone", player),
+ "Noah Card Key 3":
+ lambda state: state.has("Lightning Stone", player) and state.has("Earth Stone", player),
+
+ # Pommy Transforms
+ "Pommy Beast Transformation":
+ lambda state: state.has("Pommy Animal Gene", player),
+ "Pommy Dinosaur Transformation":
+ lambda state: state.has("Pommy Beast Gene", player),
+ "Pommy Shadow Transformation":
+ lambda state: state.has("Pommy Beast Gene", player) or state.has("Pommy Penguin Gene", player),
+ "Pommy Dragon Transformation":
+ lambda state: state.has("Pommy Penguin Gene", player),
+ "Pommy Bird Transformation":
+ lambda state: state.has("Pommy Penguin Gene", player),
+ "Pommy Chicken Transformation":
+ lambda state: state.has("Pommy Penguin Gene", player),
+
+ "Pommy Claw Transformation":
+ lambda state: state.has("Pommy Knuckle Gene", player),
+ "Pommy Hammer Transformation":
+ lambda state: state.has("Pommy Knuckle Gene", player),
+ "Pommy Pixie Transformation":
+ lambda state: state.has("Pommy Claw Gene", player) or state.has("Pommy Hammer Gene", player),
+ "Pommy Cat Transformation":
+ lambda state: state.has("Pommy Claw Gene", player),
+ "Pommy Devil Transformation":
+ lambda state: state.has("Pommy Claw Gene", player),
+ "Pommy Knight Transformation":
+ lambda state: state.has("Pommy Hammer Gene", player),
+ "Pommy Mage Transformation":
+ lambda state: state.has("Pommy Hammer Gene", player),
+ }
diff --git a/worlds/bomberman_tsa/__init__.py b/worlds/bomberman_tsa/__init__.py
new file mode 100644
index 000000000000..d5103b6ddb88
--- /dev/null
+++ b/worlds/bomberman_tsa/__init__.py
@@ -0,0 +1,310 @@
+from typing import List, Dict, Any, ClassVar
+
+from BaseClasses import Region, Tutorial, MultiWorld, ItemClassification
+from worlds.AutoWorld import WebWorld, World
+from .Items import BombTSAItem, item_data_table, item_table, pommy_shop_genes, item_filler, item_filler_weight, trap_filler, trap_filler_weight, planet_coord_list, elemental_stones
+from .Locations import *
+from .Options import BombTSAOptions, bomberman_tsa_option_groups
+from .Regions import region_data_table
+from .Rules import *
+from .Rom import MD5Hash, BombTSAProcedurePatch, write_tokens
+from .Rom import get_base_rom_path as get_base_rom_path
+from .Client import BombTSAClient
+
+
+
+import Utils
+import dataclasses
+import typing
+import random
+import os
+import pkgutil
+import Patch
+import settings
+import logging
+import copy
+
+logger = logging.getLogger("Bomberman TSA")
+
+#from .gamemaps import POMMY_SHOP_GENES, SHOP_PART_LOCS
+
+class BombTSASettings(settings.Group):
+ class RomFile(settings.UserFilePath):
+ """File name of the Bomberman The Second Attack US rom"""
+ copy_to = "BombermanTSA.z64"
+ description = "Bomberman The Second Attack (US) ROM File"
+ md5s = [MD5Hash]
+
+ rom_file: RomFile = RomFile(RomFile.copy_to)
+
+class BombTSAWebWorld(WebWorld):
+ theme = "partyTime"
+
+ setup_en = Tutorial(
+ tutorial_name="Start Guide",
+ description="A guide to playing Bomberman The Second Attack.",
+ language="English",
+ file_name="guide_en.md",
+ link="guide/en",
+ authors=["Happyhappyism"]
+ )
+
+ tutorials = [setup_en]
+ option_groups = bomberman_tsa_option_groups
+
+class BombTSAWorld(World):
+ """The greatest game of all time."""
+
+ game = "Bomberman The Second Attack"
+ data_version = 1
+ web = BombTSAWebWorld()
+ options: BombTSAOptions
+ options_dataclass = BombTSAOptions
+ settings: ClassVar[BombTSASettings]
+ topology_present = False
+ settings_key = "bombermantsa_settings"
+ location_name_to_id = location_table
+ location_name_to_id.update(shop_location_table)
+ location_name_to_id.update(pommy_location_table)
+ location_name_to_id.update(powerup_location_table)
+ item_name_to_id = item_table
+
+ startbomb = 0
+ pommy_shop_hint_map = {}
+
+ def __init__(self, world: MultiWorld, player: int):
+ #self.included_stages = create_stage_list(self.options.stage_total.value)
+ super().__init__(world, player)
+
+ def create_item(self, name: str) -> BombTSAItem:
+ return BombTSAItem(name, item_data_table[name].type, item_data_table[name].code, self.player)
+
+ def create_item_no_progression(self, name:str) -> BombTSAItem:
+ if item_data_table[name].type == ItemClassification.progression:
+ return BombTSAItem(name, ItemClassification.useful, item_data_table[name].code, self.player)
+ else:
+ return BombTSAItem(name, item_data_table[name].type, item_data_table[name].code, self.player)
+
+ def create_items(self) -> None:
+ item_pool: List[BombTSAItem] = []
+
+ for name, item in item_data_table.items():
+ if item.code and item.can_create(self):# and (name in self.included_stages):
+ for x in range(item.num_exist):
+ if name[0:5] == "Pommy" and self.options.pommysanity == False:
+ item_pool.append(self.create_item_no_progression(name))
+ else:
+ item_pool.append(self.create_item(name))
+
+ # Starting Element Stone
+ # valid_startingstones = ["Fire Stone","Ice Stone","Wind Stone","Earth Stone","Lightning Stone","Light Stone","Dark Stone"]
+ valid_startingstones = elemental_stones.copy()
+ self.startbomb = self.options.start_element.value
+ if self.startbomb == 7:
+ self.startbomb = self.random.randint(0,6)
+ #logger.warning(f"Starting Element Stone choice index: {self.startbomb}")
+ #logger.warning(f"Starting Stones: {valid_startingstones}")
+ self.multiworld.push_precollected(self.create_item(valid_startingstones.pop(self.startbomb)))
+ #logger.warning(f"Starting Stones: {valid_startingstones}")
+ for element in valid_startingstones:
+ item_pool.append(self.create_item(element))
+
+ # Starting Planets
+ #planet_items = ["Aquanet Coordinates","Horizon Coordinates","Starlight Coordinates",
+ #"Neverland Coordinates","Epikyur Coordinates","Thantos Coordinates"]
+ planet_items = planet_coord_list.copy()
+ planet_count = (self.options.start_planet.value - 1 )
+ if planet_count:
+ self.random.shuffle(planet_items)
+ for x in range(planet_count):
+ self.multiworld.push_precollected(self.create_item(planet_items.pop(0)))
+ for planet in planet_items:
+ item_pool.append(self.create_item(planet))
+
+ if self.options.pommyshop == 0:
+ for gene in pommy_shop_genes:
+ item_pool.append(self.create_item(gene))
+
+ if self.options.noah_open.value == 2:
+ item_pool.append(self.create_item("Noah Coordinates"))
+
+ if self.options.include_warkeys or self.options.noah_open == 1:
+ for x in range(3):
+ item_pool.append(self.create_item("Warship key"))
+
+ junk = len(self.multiworld.get_unfilled_locations(self.player)) - len(item_pool)
+ item_pool += [self.create_item(self.get_filler_item_name()) for _ in range(junk)]
+
+ self.multiworld.itempool += item_pool
+
+ def create_regions(self) -> None:
+ #self.included_stages = self.create_stage_list(self.options.stage_total.value)
+ #logger.warning(f"{shop_location_table}")
+ shop_gene_list_raw = list(shop_loc_list.keys())
+ self.random.shuffle(shop_gene_list_raw)
+ shop_gene_list = {}
+ for i in range(len(pommy_shop_genes)):
+
+ shop_loc_name = shop_gene_list_raw.pop(0)
+ shop_gene_list[shop_loc_name] = shop_loc_list[shop_loc_name]
+ #logger.warning(f"{shop_gene_list}")
+ # Create regions.
+ for region_name in region_data_table.keys():
+ region = Region(region_name, self.player, self.multiworld)
+ self.multiworld.regions.append(region)
+
+ # Create locations.
+ for region_name, region_data in region_data_table.items():
+
+ #if region_name in self.included_stages or region_name in fixed_regions:
+ region = self.get_region(region_name)
+ region.add_locations({
+ location_name: location_data.address for location_name, location_data in location_data_table.items()
+ if location_data.region == region_name and location_data.can_create(self) and location_data.base_loc
+ }, BombTSALocation)
+
+ if self.options.shopsanity or self.options.pommyshop:
+ if self.options.shopsanity:
+ region.add_locations({
+ location_name: location_data.address for location_name, location_data in location_data_table.items()
+ if location_data.region == region_name and location_data.loc_type == "Shop" # and (region_name in self.included_stages or region_name in fixed_regions)
+ }, BombTSALocation)
+ else:
+ #logger.warning(f"{shop_gene_list}")
+ region.add_locations({
+ location_name: shop_address for location_name, shop_address in shop_gene_list.items()
+ if location_data_table[location_name].region == region_name
+ }, BombTSALocation)
+
+
+ if self.options.pommysanity:
+ region.add_locations({
+ location_name: location_data.address for location_name, location_data in location_data_table.items()
+ if location_data.region == region_name and location_data.loc_type == "Gene"# and (region_name in self.included_stages or region_name in fixed_regions)
+ }, BombTSALocation)
+
+ if self.options.powersanity:
+ region.add_locations({
+ location_name: location_data.address for location_name, location_data in location_data_table.items()
+ if location_data.region == region_name and location_data.loc_type == "Powerup" # and (region_name in self.included_stages or region_name in fixed_regions)
+ }, BombTSALocation)
+ region.add_exits(region_data_table[region_name].connecting_regions)
+
+
+ # Place locked locations.
+ for location_name, location_data in locked_locations.items():
+ # Ignore locations we never created.
+ if not location_data.can_create(self):
+ continue
+
+ locked_item = self.create_item(location_data_table[location_name].locked_item)
+ self.get_location(location_name).place_locked_item(locked_item)
+
+ # Place Pommy Genes in Shop Parts
+ if self.options.pommyshop:
+ genes = pommy_shop_genes
+ self.random.shuffle(genes)
+ parts = list(shop_gene_list.keys())
+ self.random.shuffle(parts)
+ for gene in genes:
+ #self.pommy_shop_hint_map[gene] = parts[0]
+ logger.warning(f"{parts}")
+ shop_part = parts.pop(0)
+ logger.warning(f"{shop_part}")
+ self.get_location(shop_part).place_locked_item(self.create_item(gene))
+
+
+ def get_filler_item_name(self) -> str:
+ filler_items = item_filler
+ filler_weights = item_filler_weight
+ if self.options.include_traps:
+ filler_items.extend(trap_filler)
+ filler_weights.extend(trap_filler_weight)
+ junk_item = self.random.choices(item_filler,item_filler_weight)[0]
+ return junk_item
+ # filler_items = ["200 Coins","Heart","Gold Heart"]
+ # filler_weights = [0.4,0.8,0.2]
+ # if self.options.include_traps:
+ # filler_items.extend(["Stun Trap","Panic Bomb Trap","Fire Trap","Reverse Trap"])
+ # filler_weights.extend([0.3,0.4,0.3,0.4])
+ # junk_item = self.random.choices(filler_items,filler_weights)[0]
+ # return junk_item
+
+ def set_rules(self) -> None:
+ player = self.player
+ region_rules = get_region_rules(player)
+
+ for entrance_name, rule in region_rules.items():
+ entrance = self.multiworld.get_entrance(entrance_name, player)
+ entrance.access_rule = rule
+
+ # Noah Access
+ noah_entrance = self.multiworld.get_entrance("Menu -> Noah", player)
+
+ match self.options.noah_open.value:
+ case 0:
+ noah_entrance.access_rule = lambda state: ((sum(
+ (state.can_reach_location("Alcatraz Generator", player),
+ state.can_reach_location("Aquanet Generator", player),
+ state.can_reach_location("Horizon Generator", player),
+ state.can_reach_location("Starlight Generator", player),
+ state.can_reach_location("Neverland Generator", player),
+ state.can_reach_location("Epikyur Generator", player),
+ state.can_reach_location("Thantos Generator", player),)
+ ) >= self.options.planet_required.value))
+ case 1:
+ lambda state: state.has("Warship Key", player, 3)
+ case 2:
+ noah_entrance.access_rule = lambda state: state.has("Noah Coordinates", player)
+
+ location_rules = get_location_rules(player)
+
+ for location in self.multiworld.get_locations(player):
+ name = location.name
+ #if name in location_rules and location_data_table[name].can_create(self.multiworld, player):
+ if name in location_rules:
+ location.access_rule = location_rules[name]
+
+ # Completion condition.
+ match self.options.game_goal.value:
+ case 0: # Final Boss
+ self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
+ case 1: # Generators
+ self.multiworld.completion_condition[self.player] = lambda state: sum(
+ (state.can_reach_location("Alcatraz Generator", player),
+ state.can_reach_location("Aquanet Generator", player),
+ state.can_reach_location("Horizon Generator", player),
+ state.can_reach_location("Starlight Generator", player),
+ state.can_reach_location("Neverland Generator", player),
+ state.can_reach_location("Epikyur Generator", player),
+ state.can_reach_location("Thantos Generator", player),)
+ ) >= self.options.planet_required.value
+ case 2:
+ self.multiworld.completion_condition[self.player] = lambda state: (
+ state.has("Fire Stone",player) and state.has("Ice Stone",player) and state.has("Wind Stone",player) and state.has("Earth Stone",player)
+ and state.has("Lightning Stone",player) and state.has("Dark Stone",player) and state.has("Light Stone",player) )
+
+ def fill_slot_data(self) -> Dict[str, Any]:
+ return {
+ "DeathLink": self.options.death_link.value
+ }
+
+ def generate_output(self, output_directory: str):
+ outfilepname = f"_P{self.player}"
+ outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}"
+ self.rom_name_text = f'B64{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0'
+ self.romName = bytearray(self.rom_name_text, "utf8")[:0x20]
+ self.romName.extend([0] * (0x20 - len(self.romName)))
+ self.rom_name = self.romName
+ self.playerName = bytearray(self.multiworld.player_name[self.player], "utf8")[:0x20]
+ self.playerName.extend([0] * (0x20 - len(self.playerName)))
+
+ patch = BombTSAProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
+ #patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "tsabasepatch.bsdiff4"))
+ #procedure = [("apply_bsdiff4", ["base_patch.bsdiff4"]), ("apply_tokens", ["token_data.bin"])]
+ procedure = [("apply_tokens", ["token_data.bin"])]
+ patch.procedure = procedure
+ write_tokens(self, patch, self.startbomb)
+ out_file_name = self.multiworld.get_out_file_name_base(self.player)
+ patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}"))
+
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/archipelago.json b/worlds/bomberman_tsa/archipelago.json
new file mode 100644
index 000000000000..78749ff6c29e
--- /dev/null
+++ b/worlds/bomberman_tsa/archipelago.json
@@ -0,0 +1,8 @@
+{
+ "minimum_ap_version": "0.6.3",
+ "world_version": "0.1.2",
+ "authors": ["Happyhappyism"],
+ "version": 7,
+ "compatible_version": 7,
+ "game": "Bomberman 64 The Second Attack"
+}
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/com_ap_methods.py b/worlds/bomberman_tsa/com_ap_methods.py
new file mode 100644
index 000000000000..5046870b98a4
--- /dev/null
+++ b/worlds/bomberman_tsa/com_ap_methods.py
@@ -0,0 +1,127 @@
+import random
+
+def split_into_xbit_chunks(byte_array,size):
+ #Splits a byte array into chunks of 32 bits (4 bytes).
+
+ chunks = []
+ for i in range(0, len(byte_array), size):
+ chunk = byte_array[i:i + size]
+ # If the last chunk is less than 4 bytes, pad it with zeros
+ if len(chunk) < size:
+ chunk += b'\x00' * (size - len(chunk))
+ chunks.append(chunk)
+ return chunks
+
+def shuffle_dict(in_dict):
+ value_list = list(in_dict.values())
+ random.shuffle(value_list)
+ shuffled_dict = {}
+ keys_list = list(in_dict.keys())
+ for i, key in enumerate(keys_list):
+ shuffled_dict[key] = value_list[i]
+ return shuffled_dict
+
+def randomize_dict_items(my_dict):
+ values = list(my_dict.values())
+ random.shuffle(values)
+
+ keys = list(my_dict.keys())
+ for i, key in enumerate(keys):
+ my_dict[key] = values[i]
+ return my_dict
+
+def ramdomize_table(tbl,entrysize):
+ split_table = split_into_xbit_chunks(tbl,entrysize)
+ random.shuffle(split_table)
+ result = b''.join(split_table)
+ return result
+
+def ramdomize_table_with_exclude(tbl,entrysize,excepts):
+ split_table = split_into_xbit_chunks(tbl,entrysize)
+ idxshuffle = []
+ new_table = []
+ for x in range(len(split_table)):
+ idxshuffle.append(x)
+ new_table.append(b'/x00/x00/x00/x00')
+ random.shuffle(idxshuffle)
+ for idx in range(len(split_table)):
+ if idx in excepts:
+ new_table[idx] = split_table[idx]
+ continue
+ randidx = idxshuffle.pop(0)
+ while randidx in excepts:
+ randidx = idxshuffle.pop(0)
+ new_table[idx] = split_table[randidx]
+ result = b''.join(new_table)
+ return result
+
+def ramdomize_table_with_exclude_list(tbl,entrysize,excepts,baseoffset):
+ # Same as the other function but returns a list of offsets and data to write,
+ # Useful when randomizing multiple sets of data to the same table
+ split_table = split_into_xbit_chunks(tbl,entrysize)
+ idxshuffle = []
+ new_table = []
+ out_dict = {}
+ offset = baseoffset
+ for x in range(len(split_table)):
+ idxshuffle.append(x)
+ new_table.append(b'/x00/x00/x00/x00')
+ random.shuffle(idxshuffle)
+ for idx in range(len(split_table)):
+
+ if idx in excepts:
+ new_table[idx] = split_table[idx]
+ continue
+ randidx = idxshuffle.pop(0)
+ while randidx in excepts:
+ randidx = idxshuffle.pop(0)
+
+ new_table[idx] = split_table[randidx]
+ if randidx not in excepts:
+ out_dict.update({offset:new_table[idx]})
+ offset += entrysize
+ #result = b''.join(new_table)
+ return out_dict
+
+def randomize_multi_table(tbl,size1,tb2,size2,excepts):
+ split_table1 = split_into_xbit_chunks(tbl,size1)
+ split_table2 = split_into_xbit_chunks(tb2,size2)
+ idxshuffle = []
+ new_table1 = []
+ new_table2 = []
+ for x in range(len(split_table1)):
+ idxshuffle.append(x)
+ new_table1.append(b'/x00')
+ new_table2.append(b'/x00')
+ random.shuffle(idxshuffle)
+
+def import_binary_bytes(file_name: str, apworld: str):
+ from pkgutil import get_data
+ get_data(__name__, "data/Rooms.json")
+ with open(file_name, "rb") as f:
+ data = f.read()
+ return bytes(data)
+
+def randomize_palettes(world, pal_sources):
+ from .palette_colors import colorsets, single_color
+ tokens = {}
+ for pal_set in pal_sources:
+ rand_colorset = world.random.choice(list(colorsets[pal_sources[pal_set][0]].keys()))
+ i = 0
+ for clrinx in pal_sources[pal_set][1]:
+ if clrinx == 18:
+ color = world.random.choice(single_color)
+ #rom.write_bytes(pal_set+i,bytes(color))
+ tokens[pal_set + i] = bytes(color)
+ elif clrinx == 16:
+ i = i+2
+ continue
+ elif clrinx == 17:
+ #rom.write_bytes(pal_set+i,bytes([0x00,0x00]))
+ tokens[pal_set + i] = bytes([0x00,0x00])
+ else:
+ color = colorsets[pal_sources[pal_set][0]][rand_colorset][clrinx]
+ #rom.write_bytes(pal_set+i,bytes(color))
+ tokens[pal_set + i] = bytes(color)
+ i=i+2
+ return tokens
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/docs/guide_en.md b/worlds/bomberman_tsa/docs/guide_en.md
new file mode 100644
index 000000000000..c9015a9a7d78
--- /dev/null
+++ b/worlds/bomberman_tsa/docs/guide_en.md
@@ -0,0 +1,20 @@
+# Bomberman 64 The Second Attack Setup Guide
+
+## What you need
+- Bizhawk https://tasvideos.org/BizHawk/ReleaseHistory#Bizhawk210
+- The latest bomberman_tsa.apworld provided in the latest release post
+
+
+## Installation
+Same as every other unsupported N64 apworld,
+- simply move bomberman_tsa.apworld into the custom_worlds directory in your Archipelago install server
+(default path: C:\ProgramData\Archipelago\custom_worlds)
+- or simply double click the apworld and it should install itself (Archipelago version 0.5.0 and later)
+
+## Running
+1. In the Archipelago launcher push the 'Generate Template Options' button to generate a yaml for your options
+2. Like every other apworld, fill out your yaml options and have the host generate the multiworld, the produced zip file should contain a .apbombtsa patch file. Receive this .apbombtsa patch from the multiworld host.
+3. Push the 'Open Patch' button in the launcher and it should ask for a 'Bomberman 64 The Second Attack (US)' legal ROM dump (MD5 Hash: aec1fdb0f1caad86c9f457989a4ce482), then select the generated .apbombtsa patch file to produce a .z64 ROM which should auto launch in a bizhawk instance. You should be set otherwise from here just follow the same instructions as any other supported N64 apworld.
+
+## Troubleshooting
+- The windows 7 version of archipelago uses python 3.8 which will not work with this apworld.
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/gamemaps.py b/worlds/bomberman_tsa/gamemaps.py
new file mode 100644
index 000000000000..c91ef980d124
--- /dev/null
+++ b/worlds/bomberman_tsa/gamemaps.py
@@ -0,0 +1,328 @@
+FLAG_MAP = { # LocationID = [ADDRESS, MASK]
+ # Boss Defeats
+ 0x1C3005: [0xAC2DE,0x10,0x89E], # Molok Defeated
+ 0x1C3006: [0xAC2DE,0x20,0x89E], # Zoniha Defeated
+ 0x1C3007: [0xAC2DE,0x40,0x89E], # Bulzeeb Defeated
+ 0x1C3001: [0xAC2DE,0x01,0x89E], # Baelfael Defeated
+ 0x1C3002: [0xAC2DE,0x02,0x89E], # Behemos Defeated
+ 0x1C3003: [0xAC2DE,0x04,0x89E], # Ashtarth Defeated
+ 0x1C3004: [0xAC2DE,0x08,0x89E], # Zhael Defeated
+ # Guardian Armor
+ 0x1C300A: [0xAC2E8,0x4], # Guardian Armor Neverland
+ 0x1C300B: [0xAC2E8,0x1], # Guardian Armor Horizon
+ 0x1C300C: [0xAC2E8,0x2], # Guardian Armor Starlight
+ 0x1C300D: [0xAC2E9,0x80], # Guardian Armor Aquanet
+ # Generators
+ 0x1C3010: [0xAC2DF,0x1], # Alcatraz
+ 0x1C3011: [0xAC2DF,0x2], # Aquanet
+ 0x1C3012: [0xAC2DF,0x4], # Horizon
+ 0x1C3013: [0xAC2DF,0x8], # Starlight
+ 0x1C3014: [0xAC2DF,0x10], # Neverland
+ 0x1C3015: [0xAC2DF,0x20], # Epikyur
+ 0x1C3016: [0xAC2DF,0x40], # Thantos
+ #Head Parts
+ 0x1C3020: [0xAC2E0,0x1], # Epikyur Head
+ 0x1C3021: [0xAC2E0,0x2], # Neverland Head
+ 0x1C3022: [0xAC2E0,0x8], # Thantos Head
+ 0x1C3023: [0xAC2E0,0x40], # Starlight Head
+ 0x1C3024: [0xAC2E1,0x20], # Alcatraz Head
+ 0x1C3025: [0xAC2E1,0x40], # Aquanet Head
+ 0x1C3026: [0xAC2E1,0x80], # Horizon Head
+ #Arm Parts
+ 0x1C3028: [0xAC2E4,0x1], # Epikyur Arm
+ 0x1C3029: [0xAC2E4,0x2], # Neverland Arm
+ 0x1C302A: [0xAC2E4,0x8], # Thantos Arm
+ 0x1C302B: [0xAC2E4,0x40], # Starlight Arm
+ 0x1C302C: [0xAC2E5,0x20], # Alcatraz Arm
+ 0x1C302D: [0xAC2E5,0x40], # Aquanet Head
+ 0x1C302E: [0xAC2E5,0x80], # Horizon Arm
+ #Body Parts
+ 0x1C3030: [0xAC2E6,0x1], # Epikyur Body
+ 0x1C3031: [0xAC2E6,0x2], # Neverland Body
+ 0x1C3032: [0xAC2E6,0x8], # Thantos Body
+ 0x1C3033: [0xAC2E6,0x40], # Starlight Body
+ 0x1C3034: [0xAC2E7,0x20], # Alcatraz Body
+ 0x1C3035: [0xAC2E7,0x40], # Aquanet Body
+ 0x1C3036: [0xAC2E7,0x80], # Horizon Body
+ #Leg Parts
+ 0x1C3038: [0xAC2EA,0x1], # Epikyur Leg
+ 0x1C3039: [0xAC2EA,0x2], # Neverland Leg
+ 0x1C303A: [0xAC2EA,0x8], # Thantos Leg
+ 0x1C303B: [0xAC2EA,0x40], # Starlight Leg
+ 0x1C303C: [0xAC2EB,0x20], # Alcatraz Leg
+ 0x1C303D: [0xAC2EB,0x40], # Aquanet Leg
+ 0x1C303E: [0xAC2EB,0x80], # Horizon Leg
+}
+LEVEL_FLAG_MAP = {
+ # Planet, Offset, Mask
+ # 0: Alcatraz, 1: Aquanet, 2: Horizon, 3: Starlight
+ # 4: Neverland 5: Epikyur, 6: Thantos, 7: Noah
+ 0x1C3040: [0x02,0x02,0x01], # Horizon Left Blue Jewel
+ 0x1C3041: [0x02,0x03,0x80], # Horizon Right Blue Jewel
+ 0x1C3042: [0x02,0x01,0x40], # Horizon Right Green Jewel
+ 0x1C3043: [0x02,0x01,0x08], # Horizon Left Green Jewel
+ 0x1C3044: [0x02,0x01,0x80], # Horizon Middle Green Jewel
+ 0x1C3045: [0x02,0x02,0x40], # Horizon Red Jewel
+
+ 0x1C3046: [0x03,0x01,0x04], # Starlight King Of Clubs
+ 0x1C3047: [0x03,0x01,0x02], # Starlight Knight Of Diamonds
+ 0x1C3048: [0x03,0x01,0x01], # Starlight Ace Of Spaces
+ 0x1C3049: [0x03,0x02,0x80], # Starlight Queen Of Hearts
+
+ 0x1C304A: [0x05,0x03,0x40], # Epikur Haunted House Pass
+ 0x1C304B: [0x05,0x03,0x80], # Epikur Museum Pass
+ 0x1C304C: [0x05,0x02,0x02], # Epikur Coaster Battery
+
+ 0x1C304D: [0x06,0x03,0x04], # Thantos Lower Train Battery
+ 0x1C304E: [0x06,0x03,0x02], # Thantos Upper Train Battery
+
+ 0x1C302F: [0x07,0x03,0x02], # Noah Card Key 1
+ 0x1C303F: [0x07,0x03,0x04], # Noah Card Key 1
+ 0x1C304F: [0x07,0x03,0x01], # Noah Card Key 1
+
+}
+REMOTE_MAP = {
+ 0x84B: 0x1C3060, # Alcatraz Remote Security Room
+ 0x840: 0x1C3061, # Aquanet Secret Room
+ 0x843: 0x1C3061, # Aquanet Remote To the Tower
+ 0x83F: 0x1C306A, # Aquanet Remote Fountain Room
+ 0x859: 0x1C3062, # Horizon Remote Secret Room
+ 0x85D: 0x1C3063, # Horizon Remote Final Deposit
+ 0x860: 0x1C3064, # Horizon Remote Secret Room
+ 0x86B: 0x1C3065, # Starlight Remote Casino Lobby
+ 0x86E: 0x1C3066, # Starlight Remote Waiting Room
+ 0x823: 0x1C306B, # Neverland Remote Bonus Room
+ 0x82D: 0x1C3067, # Neverland Remote Underground Corridor
+ 0x877: 0x1C3068, # Epikyur Remote Haunted House Yard
+ 0x892: 0x1C3069, # Thantos Remote Crevice
+ 0x840: 0x1C3070, # Aquanet Remote Secret Room
+}
+KICK_MAP = {
+ 0x84A: 0x1C30D0, # Alcatraz Kick Twisted Sewers
+ 0x84B: 0x1C30D1, # Alcatraz Kick Security Room
+ 0x84E: 0x1C30D2, # Alcatraz Kick Through the Pipe
+ 0x850: 0x1C30D3, # Alcatraz Kick Pipe Room
+ 0x825: 0x1C30D4, # Neverland Kick Intersection
+ 0x82B: 0x1C30D5, # Neverland Kick Cage Room
+ 0x833: 0x1C30D6, # Neverland Kick Third Passageway
+ 0x874: 0x1C30D7, # Epikyur Kick Center Fountain
+ 0x839: 0x1C30D8, # Aquanet Kick Pool room
+ 0x83C: 0x1C30D9, # Aquanet Kick Top of Elevator Room
+ 0x869: 0x1C30DA, # Starlight Kick Alleyway
+ 0x86E: 0x1C30DB, # Starlight Kick Waiting Room
+ 0x86C: 0x1C30DC, # Starlight Kick Betting Room
+ 0x857: 0x1C30DD, # Horizon Kick Leading Road
+ 0x85E: 0x1C30DE, # Horizon Kick Last Route
+ 0x88C: 0x1C30DF, # Thantos Kick Lower Battery
+ 0x892: 0x1C30E0, # Thantos Kick Chasm
+
+}
+GLOVE_MAP = {
+ 0x84C: 0x1C30C0, # Alcatraz Kick Security Room B
+ 0x850: 0x1C30C1, # Alcatraz Glove Pipe Room
+ 0x874: 0x1C30C2, # Epikyur Glove Center Fountain
+ 0x835: 0x1C30C3, # Aquanet Glove Landing Site
+ 0x83E: 0x1C30C4, # Aquanet Glove Crap Room
+ 0x844: 0x1C30C5, # Aquanet Glove Fountain Room
+ 0x86D: 0x1C30C6, # Starlight Glove Slot Machines
+ 0x870: 0x1C30C7, # Starlight Glove Magician Room
+ 0x855: 0x1C30C8, # Horizon Glove Eastern Tower
+ 0x85B: 0x1C30C9, # "Horizon Glove Basement Hub
+ 0x889: 0x1C30CA, # Thantos Glove Ladder
+ 0x896: 0x1C30CB, # Thantos Glove Manhole Room
+ 0x892: 0x1C30CC, # Thantos Glove Chasm
+}
+
+STAGE_ITEMS = {
+ # Planet, Offset, Set Flag Mask, Item Mask, Place MapID, [Item MapID]
+ # 0: Alcatraz, 1: Aquanet, 2: Horizon, 3: Starlight
+ # 4: Neverland 5: Epikyur, 6: Thantos, 7: Noah
+ 0x1C30018: [0x02,0x00000003, 0x00000180, 0x854, [0x858,0x859]], # Blue Gems
+ 0x1C30019: [0x02,0x00003800, 0x00C80000, 0x85D, [0x862,0x85F,0x863]], # Green Gems
+ 0x1C3001A: [0x02,0x00000004, 0x00004000, 0x854, [0x85D]], # Red Gem
+ 0x1C3001B: [0x03,0x0000003C, 0x00078000, 0x867, [0x867,0x86C,0x865,0x869]], # Royal Straight
+ 0x1C3001C: [0x05,0x00000040, 0x00000040, 0x87F, [0x87C]], # Haunted House Pass
+ 0x1C3001D: [0x05,0x00000080, 0x00000080, 0x87C, [0x87E]], # Museum Pass
+ 0x1C3001E: [0x05,0x00000200, 0x00000200, 0x883, [0x87F]], # Coaster Battery
+ 0x1C3001F: [0x06,0x000000E0, 0x00000006, 0x88E, [0x88B,0x88C ]], # Train Batteries
+ #0x1C30050: [0x07,warshipmask,0x00000000, 0x89E, [0x000]] # Warshipkeys
+
+}
+
+SHOP_MAP = {
+ # Location ID: [address, mask]
+ 0x1C3081: [0xAC2DB,0x02], # Shop Heart 1
+ 0x1C3082: [0xAC2DB,0x04], # Shop Heart 2
+ 0x1C3083: [0xAC2DB,0x08], # Shop Heart 3
+ 0x1C3084: [0xAC2DB,0x10], # Shop Heart 4
+ 0x1C3085: [0xAC2DB,0x20], # Shop Heart 5
+ #0x1C3087: [0XAC2DB,0X40], # Youth Potion
+ #0x1C3087: [0XAC2DB,0X40], # Youth Potion
+ 0x1C308A: [0xAC2DB,0x80], # Shop Battle A
+
+ 0x1C308B: [0xAC2DA,0x01], # Shop Battle B
+ 0x1C308C: [0xAC2DA,0x02], # Shop Battle C
+ 0x1C308D: [0xAC2DA,0x04], # Shop Battle D
+
+ 0x1C3090: [0xAC2CF,0x01], # Shop Part Beard
+ 0x1C3091: [0xAC2CF,0x02], # Shop Part Tank
+ 0x1C3092: [0xAC2CF,0x04], # Shop Part Fan
+ 0x1C3093: [0xAC2CF,0x08], # Shop Part Bigfoot
+ 0x1C3094: [0xAC2CF,0x10], # Shop Part Topknot
+ 0x1C3095: [0xAC2CF,0x20], # Shop Part Kimono
+ 0x1C3096: [0xAC2CF,0x40], # Shop Part Sword
+ 0x1C3097: [0xAC2CF,0x80], # Shop Part Sandals
+
+ 0x1C3098: [0xAC2CE,0x01], # Shop Part Cat Ears
+ 0x1C3099: [0xAC2CE,0x02], # Shop Part Cat Suit
+ 0x1C309A: [0xAC2CE,0x04], # Shop Part Cat Gloves
+ 0x1C309B: [0xAC2CE,0x08], # Shop Part Cat Slippers
+ 0x1C309C: [0xAC2CE,0x10], # Shop Part Headgear
+ 0x1C309D: [0xAC2CE,0x20], # Shop Part Elephant Suit
+ 0x1C309E: [0xAC2CE,0x40], # Shop Part Gloves
+ 0x1C309F: [0xAC2CE,0x80], # Shop Part Kung Fu Shoes
+
+ 0x1C30A0: [0xAC2CD,0x01], # Shop Part Ribbon
+ 0x1C30A1: [0xAC2CD,0x02], # Shop Part Pink Dress
+ 0x1C30A2: [0xAC2CD,0x04], # Shop Part Slash Claws
+ 0x1C30A3: [0xAC2CD,0x08], # Shop Part High Heels
+ 0x1C30A4: [0xAC2CD,0x10], # Shop Part Rabbit Ears
+ 0x1C30A5: [0xAC2CD,0x20], # Shop Part Duck Suit
+ 0x1C30A6: [0xAC2CD,0x40], # Shop Part Drill
+ 0x1C30A7: [0xAC2CD,0x80], # Shop Part Sneakers
+
+ 0x1C30A8: [0xAC2CC,0x01], # Shop Part Bald Head
+ 0x1C30A9: [0xAC2CC,0x02], # Shop Part Apron
+ 0x1C30AA: [0xAC2CC,0x04], # Shop Part Hand Puppets
+ 0x1C30AB: [0xAC2CC,0x08], # Shop Part Pommy Slippers
+ 0x1C30AC: [0xAC2CC,0x10], # Shop Part Gold Helmet
+ 0x1C30AD: [0xAC2CC,0x20], # Shop Part Gold Suit
+ 0x1C30AE: [0xAC2CC,0x40], # Shop Part Gold Gloves
+ 0x1C30AF: [0xAC2CC,0x80], # Shop Part Gold Boots
+
+ 0x1C30B0: [0XAC2D7,0x01], # Shop Hint Beard
+ 0x1C30B1: [0XAC2D7,0x02], # Shop Hint Tank
+ 0x1C30B2: [0XAC2D7,0x04], # Shop Hint Fan
+ 0x1C30B3: [0XAC2D7,0x08], # Shop Hint Bigfoot
+ 0x1C30B4: [0XAC2D7,0x10], # Shop Hint Topknot
+ 0x1C30B5: [0XAC2D7,0x20], # Shop Hint Kimono
+ 0x1C30B6: [0XAC2D7,0x40], # Shop Hint Sword
+ 0x1C30B7: [0XAC2D7,0x80], # Shop Hint Sandals
+
+ 0x1C30B8: [0XAC2D6,0x01], # Shop Hint Beard
+ 0x1C30B9: [0XAC2D6,0x02], # Shop Hint Tank
+ 0x1C30BA: [0XAC2D6,0x04], # Shop Hint Fan
+ 0x1C30BB: [0XAC2D6,0x08], # Shop Hint Bigfoot
+ 0x1C30BC: [0XAC2D6,0x10], # Shop Hint Topknot
+ 0x1C30BD: [0XAC2D6,0x20], # Shop Hint Kimono
+ 0x1C30BE: [0XAC2D6,0x40], # Shop Hint Sword
+ 0x1C30BF: [0XAC2D6,0x80], # Shop Hint Sandals
+
+}
+
+POMMY_SHOP_GENES = [
+ "Pommy Knuckle Gene",
+ "Pommy Animal Gene",
+ "Pommy Hammer Gene",
+ "Pommy Claw Gene",
+ "Pommy Penguin Gene",
+ "Pommy Beast Gene",
+ "Pommy Mage Gene",
+ "Pommy Knight Gene",
+ "Pommy Devil Gene",
+ "Pommy Cat Gene",
+ "Pommy Bird Gene",
+ "Pommy Chicken Gene",
+ "Pommy Dragon Gene",
+ "Pommy Dinosaur Gene",
+ "Pommy Pixie Gene",
+ "Pommy Shadow Gene",
+]
+
+SHOP_PART_LOCS = [
+ # Base
+ "Shop Part Beard",
+ "Shop Part Tank",
+ "Shop Part Fan",
+ "Shop Part Bigfoot Shoes",
+ "Shop Part Topknot",
+ "Shop Part Kimono",
+ "Shop Part Sword",
+ "Shop Part Sandals",
+ # Aquanet
+ "Shop Part Cat Ears",
+ "Shop Part Cat Suit",
+ "Shop Part Cat Gloves",
+ "Shop Part Cat Slippers",
+ # Horizon
+ "Shop Part Headgear",
+ "Shop Part Elephant Suit",
+ "Shop Part Gloves",
+ "Shop Part Kung Fu Shoes",
+ # Starlight
+ "Shop Part Ribbon",
+ "Shop Part Pink Dress",
+ "Shop Part Slash Claws",
+ "Shop Part High Heels",
+ # Neverland
+ "Shop Part Rabbit Ears",
+ "Shop Part Duck Suit",
+ "Shop Part Drill",
+ "Shop Part Sneakers",
+ # Epikyur
+ "Shop Part Bald Head",
+ "Shop Part Apron",
+ "Shop Part Hand Puppets",
+ "Shop Part Pommy Slippers",
+ # Thanktos
+ "Shop Part Gold Helmet",
+ "Shop Part Gold Suit",
+ "Shop Part Gold Gloves",
+ "Shop Part Gold Boots",
+]
+
+SHOP_HINT_IDS = [
+ 0x1C3081,0x1C3082,0x1C3083,0x1C3084,0x1C3085,0x1C308A,0x1C308B,0x1C308C,0x1C308D,0x1C30C0,
+
+ 0x1C3090,0x1C3091,0x1C3092,0x1C3093,0x1C3094,0x1C3095,0x1C3096,0x1C3097,
+ 0x1C3098,0x1C3099,0x1C309A,0x1C309B,0x1C309C,0x1C309D,0x1C309E,0x1C309F,
+
+ 0x1C30A0,0x1C30A1,0x1C30A2,0x1C30A3,0x1C30A4,0x1C30A5,0x1C30A6,0x1C30A7,
+ 0x1C30A8,0x1C30A9,0x1C30AA,0x1C30AB,0x1C30AC,0x1C30AD,0x1C30AE,0x1C30AF,
+
+ 0x1C30B0,0x1C30B1,0x1C30B2,0x1C30B3,0x1C30B4,0x1C30B5,0x1C30B6,0x1C30B7,
+ 0x1C30B8,0x1C30B9,0x1C30BA,0x1C30BB,0x1C30BC,0x1C30BD,0x1C30BE,0x1C30BF,
+]
+
+DOOR_OFFSETS = [
+ 0x2A8780, # Alcatraz
+ 0x2A8920, # Aquanet
+ 0x2A8610, # Horizon
+ 0x2A9F90, # Starlight
+ 0x2A8280, # Neverland
+ 0x2A84E0, # Epikyur
+ 0x2A8290, # Thantos
+ 0x2AAC70, # Noah
+]
+
+# GUARDIAN_PATCH = bytes([0x14,0x40,0x00,0x03])
+#POMMY_CHECKS = [0x00,]
+
+DOOR_PATCH = bytes([
+ 0x3C,0x0B,0x80,0x0B, # LUI T3, 0x800B
+ 0x25,0x6C,0xBD,0x48, # ADDIU T4, T3, 0xBD48
+ 0x8D,0x8B,0x00,0x00, # LW T3, 0x0000 (T4)
+ 0x01,0x7C,0xB0,0x20, # ADD T4, T3, GP
+ 0x82,0xCB,0x00,0x00, # LB T3, 0x0000 (T4)
+ 0x39,0x77,0x00,0x01, # XORI S4, T3, 0x0001
+ 0x01,0x00,0x00,0x08, # JR T0
+])
+
+INVALID_SONGS = [0x00,0x06,0x17,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x39,0x3B,0x46,0x4A,0x4B]
+INVALID_EFX = [0x03,0x24, 0x2E,0x2F,0x30,0x31,0x34,0x3C,0x3E,0x50,0x52,0x53,
+ 0x59,0x5D,0x5F,0x62,0x64,0x66,0x67,0x68,0x69,0x6B,0x6C,0x6D,0x6E,
+ 0x6F,0x72,0x76,0x7A,0x7C,0x81,0x83,0x8E,0xAA,0xAC,0xAD,0xAE]
+
+RECV_INDEX = 0x1D0140
+TBL_ADR_POMATK = 0x270058
+TBL_ADR_ACTION = 0x26CED0
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/planet_select.py b/worlds/bomberman_tsa/planet_select.py
new file mode 100644
index 000000000000..bdc6ae1fd18b
--- /dev/null
+++ b/worlds/bomberman_tsa/planet_select.py
@@ -0,0 +1,142 @@
+def get_planet_selection(planets_have):
+
+ def has_positive(lst):
+ return any(x > 0 for x in lst)
+
+ def contains_negative(lst):
+ for num in lst:
+ if num < 0:
+ return True
+ return False
+
+ def get_lowest_negative(lst):
+ negatives = [num for num in lst if num < 0]
+ if negatives:
+ return max(negatives)
+ else:
+ return None
+ Planet_coords = {
+ 0x1C30010: [194,395,0], # 0 Alcatraz
+ 0x1C30011: [85,305,1], # 1 Aquanet
+ 0x1C30012: [385,370,2], # 2 Horizon
+ 0x1C30013: [125,165,3], # 3 Starlight
+ 0x1C30014: [525,235,4], # 4 Neverland
+ 0x1C30015: [260,120,5], # 5 Epikyur
+ 0x1C30016: [480,135,6], # 6 Thantos
+ 0x1C30017: [340,240,7], # 7 Noah
+ }
+
+ #planets_have = ["Alcatraz","Thantos","Starlight"]
+
+ Stage_Sel = [
+ 0x00,0x00,0x00,0x78, 0x00,0x00,0x00,0xC0, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 0
+ 0x00,0x00,0x00,0x20, 0x00,0x00,0x00,0xB0, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 1
+ 0x00,0x00,0x00,0xD0, 0x00,0x00,0x00,0xC8, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 2
+ 0x00,0x00,0x00,0x28, 0x00,0x00,0x00,0x40, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 3
+ 0x00,0x00,0x00,0xF8, 0x00,0x00,0x00,0x70, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 4
+ 0x00,0x00,0x00,0x70, 0x00,0x00,0x00,0x30, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 5
+ 0x00,0x00,0x00,0xF8, 0x00,0x00,0x00,0x40, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 6
+ 0x00,0x00,0x00,0xA8, 0x00,0x00,0x00,0x50, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0x00,0x00,0x00,0x00, # 7
+ ]
+
+
+ for planet in planets_have:
+ src_x = Planet_coords[planet][0]
+ src_y = Planet_coords[planet][1]
+ src_idx = Planet_coords[planet][2]
+ dist = []
+ connections = []
+ dist_x = []
+ dist_y = []
+ dist_id = []
+ for planets_coord_name, coord_array in Planet_coords.items():
+ if planets_coord_name in planets_have and (planets_coord_name != planet):
+ dest_x = coord_array[0]
+ dest_y = coord_array[1]
+ dest_idx = coord_array[2]
+ dif_x = src_x - dest_x
+ dif_y = src_y - dest_y
+ dist_x.append(dif_x)
+ dist_y.append(dif_y)
+ dist_id.append(dest_idx)
+
+ min_u = 9
+ min_d = 9
+ min_l = 9
+ min_r = 9
+
+
+ if has_positive(dist_y):
+ min_u= dist_y.index(min(y for y in dist_y if y > 0))
+ if contains_negative(dist_y):
+ min_d= dist_y.index(get_lowest_negative(dist_y))
+
+ if has_positive(dist_x):
+ min_l= dist_x.index(min(x for x in dist_x if x > 0))
+ if contains_negative(dist_x):
+ min_r = dist_x.index(get_lowest_negative(dist_x))
+ #print(min_x,dist_id[min_x], min_y, dist_id[min_y])
+
+ offset_base = (src_idx * 0x1C) + (0x08)
+ for i in range(3):
+ if min_u < 9:
+ Stage_Sel[offset_base+(i+0x00)] = 0x00 # Up
+ if min_d < 9:
+ Stage_Sel[offset_base+(i+0x04)] = 0x00 # Down
+ if min_l < 9:
+ Stage_Sel[offset_base+(i+0x08)] = 0x00 # Left
+ if min_r < 9:
+ Stage_Sel[offset_base+(i+0x0C)] = 0x00 # Right
+
+ if min_u < 9:
+ Stage_Sel[offset_base+0x00+3] = dist_id[min_u]
+ if min_d < 9:
+ Stage_Sel[offset_base+0x04+3] = dist_id[min_d]
+ if min_l < 9:
+ Stage_Sel[offset_base+0x08+3] = dist_id[min_l]
+ if min_r < 9:
+ Stage_Sel[offset_base+0x0C+3] = dist_id[min_r]
+
+ return bytes(Stage_Sel)
+#print(list(map(hex, Stage_Sel)))
+
+def highlight_planets(planets_have,stageflags,bossflags,select):
+ result = {}
+ Planet_items = [
+ 0x1C30010, # 0 Alcatraz
+ 0x1C30011, # 1 Aquanet
+ 0x1C30012, # 2 Horizon
+ 0x1C30013, # 3 Starlight
+ 0x1C30014, # 4 Neverland
+ 0x1C30015, # 5 Epikyur
+ 0x1C30016, # 6 Thantos
+ 0x1C30017, # 7 Noah
+ ]
+ planet_offsets = [
+ 0xB5694, # Alcatraz
+ 0xB56EC, # Aquanet
+ 0xB5744, # Horizon
+ 0xB579C, # Starlight
+ 0xB57F4, # Neverland
+ 0xB584C, # Epikyur
+ 0xB58A4, # Thantos
+ 0xB58FC, # Noah
+ ]
+ for idx in range(len(planet_offsets)):
+ if idx == select:
+ base_display = 0xFFFFFFFF
+ elif Planet_items[idx] in planets_have:
+ mask = 1 << idx
+ stageclear = stageflags & mask
+ bossclear = bossflags & mask
+ base_display = 0x808080FF
+ if bossclear:
+ base_display | 0xE0000000
+ if stageclear:
+ base_display | 0x0000E000
+ if bossclear and stageclear:
+ base_display = 0xE0E0E0FF
+ else:
+ base_display = 0x40404040
+ result[planet_offsets[idx]] = base_display
+ return result
\ No newline at end of file
diff --git a/worlds/bomberman_tsa/templogic.py b/worlds/bomberman_tsa/templogic.py
new file mode 100644
index 000000000000..fa792cf7aedb
--- /dev/null
+++ b/worlds/bomberman_tsa/templogic.py
@@ -0,0 +1,49 @@
+def has_all_elements(state,player): # Debug, checks to see if you have all the element stones
+ return (state.has("Fire Stone",player) and state.has("Ice Stone",player) and state.has("Wind Stone",player) and state.has("Earth Stone",player)
+ and state.has("Lightning Stone",player) and state.has("Dark Stone",player) and state.has("Light Stone",player) )
+
+def can_destroy_with_immunity(state, player, immunities):
+ stones = ["Fire Stone","Ice Stone","Wind Stone","Earth Stone","Lightning Stone","Dark Stone","Light Stone"]
+ for badstone in immunities:
+ stones.pop(stones.index(badstone))
+ for havestone in stones:
+ if state.has(havestone, player):
+ return True
+ return False
+
+def can_open_car(state, player):
+ return lambda state: state.has("Guardian Glove", player) or state.has("Earth Stone", player)
+
+def has_all_guardian(state, player): # Debug, checks to see if you have all the guardian armor
+ return state.has("Guardian Glove", player) and state.has("Guardian Helmet", player) and state.has("Guardian Boots", player)
+
+def can_build_ice(state, player):
+ return state.has("Ice Stone",player) and can_move_bombs(state,player)
+
+def has_multibomb(state, player): # has a bomb that can place more than 1 of itself
+ return state.has("Fire Stone",player) or state.has("Ice Stone",player) or state.has("Wind Stone",player) or state.has("Lightning Stone",player)
+
+def can_destroy(state,player):
+ return state.has("Fire Stone",player) or state.has("Electric Stone",player) or state.has("Ice Stone",player) or state.has("Earth Stone", player)
+
+def can_bomb_jump(state,player): # Depreciated?
+ return (state.has("Fire Stone",player) or state.has("Electric Stone",player) or state.has("Ice Stone",player) or state.has("Wind Stone", player)) and state.has("BombUp", player, 2)
+
+def can_hit_high_object(state,player):
+ return state.has("Guardian Glove", player) or (state.has("Ice Stone",player) and state.has("FireUp", player, 2)) or state.has("Dark Stone", player) or state.has("Light Stone", player)
+
+def can_hit_fountain(state, player):
+ return state.has("Ice Stone", player) and (state.has("Guardian Glove", player) or state.has("FireUp", player, 2))
+
+def can_build_ladder(state,player,bombcnt):
+ return state.has("Guardian Glove", player) and state.has("Guardian Helmet", player) and (((state.has("Fire Stone", player) or state.has("Lightning Stone", player)) and state.has("BombUp", player, bombcnt)) or (has_multibomb(state, player) and state.has("BombUp", player, (bombcnt+1))))
+
+def can_move_bombs(state,player): # Can kick or throw
+ return state.has("Guardian Glove", player) or state.has("Guardian Boots", player)
+
+def tempfunc(player):
+ return {
+
+
+
+ }
\ No newline at end of file
diff --git a/worlds/borderlands2/Client.py b/worlds/borderlands2/Client.py
new file mode 100644
index 000000000000..cf1dce56fe5f
--- /dev/null
+++ b/worlds/borderlands2/Client.py
@@ -0,0 +1,225 @@
+import asyncio
+import json
+import os
+import requests
+import time
+import re
+from NetUtils import ClientStatus
+import Utils
+from CommonClient import gui_enabled, logger, get_base_parser, CommonContext, server_loop
+from .Items import bl2_base_id
+
+# import ModuleUpdate
+# ModuleUpdate.update()
+
+# Testing:
+# import colorama
+# from asyncio import Task
+#
+
+from worlds.borderlands2.Locations import location_name_to_id
+
+
+class Borderlands2Context(CommonContext):
+ game = "Borderlands 2"
+ items_handling = 0b111 # Indicates you get items sent from other worlds. possibly should be 0b011
+ client_version = "0.4"
+ deathlink_pending = False
+
+ def __init__(self, server_address, password):
+ super(Borderlands2Context, self).__init__(server_address, password)
+ self.slot_data = dict()
+
+ async def server_auth(self, password_requested: bool = False):
+ if password_requested and not self.password:
+ await super(Borderlands2Context, self).server_auth(password_requested)
+ await self.get_username()
+ await self.send_connect()
+
+ async def connection_closed(self):
+ self.server_state_synchronized = False
+ await super(Borderlands2Context, self).connection_closed()
+
+ async def shutdown(self):
+ await super(Borderlands2Context, self).shutdown()
+
+ def run_gui(self):
+ """Import kivy UI system and start running it as self.ui_task."""
+ from kvui import GameManager
+
+ class BL2Manager(GameManager):
+ logging_pairs = [
+ ("Client", "Archipelago")
+ ]
+ base_title = "Archipelago BL2 Client"
+
+ self.ui = BL2Manager(self)
+ self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
+
+ def is_connected(self) -> bool:
+ if self.server and self.server.socket.open and self.seed_name and self.slot_data:
+ return True
+ return False
+
+ def on_package(self, cmd: str, args: dict):
+ if cmd == 'Connected':
+ self.slot_data = args.get("slot_data", {})
+ elif cmd == "RoomInfo":
+ self.seed_name = args['seed_name']
+
+ def on_deathlink(self, data: dict):
+ self.deathlink_pending = True
+ super().on_deathlink(data)
+ self.command_processor.output(self.command_processor, str("Death link received"))
+
+async def main(launch_args):
+ ctx = Borderlands2Context(launch_args.connect, launch_args.password)
+ ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
+ if gui_enabled:
+ ctx.run_gui()
+ ctx.run_cli()
+
+ def consolelog(msg):
+ ctx.command_processor.output(ctx.command_processor, str(msg))
+
+ async def handle_sock_client(reader, writer):
+ """
+ Handles communication with a single client asynchronously.
+ """
+ addr = writer.get_extra_info('peername')
+ print(f"sock connection from {addr}")
+ ctx.command_processor.output(ctx.command_processor, f"sock connection from {addr}")
+
+ while True:
+ if ctx.slot_data.get("death_link", False) and "DeathLink" not in ctx.tags:
+ await ctx.update_death_link(True)
+ try:
+ data = await reader.read(100) # Read data asynchronously
+ if not data:
+ break
+ message = data.decode()
+ print(f"Received from {addr}: {message}")
+ if message.startswith('blghello'):
+ print("initialization request received")
+ mod_vers = message.split(":")[-1]
+ if mod_vers != ctx.client_version:
+ ctx.command_processor.output(
+ ctx.command_processor,
+ f"Version Mismatch! Unexpected results ahead. mine:{ctx.client_version} mod:{mod_vers}"
+ )
+ response = "blgwelcome:" + ctx.client_version
+ writer.write(response.encode())
+ await writer.drain()
+ elif message == 'is_archi_connected':
+ print("is_archi_connected")
+ response = str(ctx.is_connected())
+ print("sending: " + response)
+ writer.write(response.encode())
+ await writer.drain()
+ elif message == 'options':
+ print("options")
+ opt = dict(ctx.slot_data)
+ opt["seed"] = ctx.seed_name
+ response = json.dumps(opt)
+ print(response)
+ writer.write(response.encode())
+ await writer.drain()
+ elif message.startswith('items_all'):
+ print("list items request received")
+ offset = message.split(":")[-1]
+ if offset == "items_all":
+ offset = 0
+ offset = int(offset)
+
+ # subtract bl2_base_id; mod is unaware of the base id, and the msg is shorter
+ chunk_end = offset + 500
+ # grab next 500 starting from offset
+ item_ids = [str(x.item - bl2_base_id) for x in ctx.items_received[offset:chunk_end]]
+
+ if chunk_end >= len(ctx.items_received): # mark end of list with 0
+ item_ids.append("0")
+
+ response = ",".join(item_ids)
+ writer.write(response.encode())
+ await writer.drain()
+ elif message.startswith('locations_all'):
+ print("list locations request received")
+ offset = message.split(":")[-1]
+ if offset == "locations_all":
+ offset = 0
+ offset = int(offset)
+
+ # subtract bl2_base_id; mod is unaware of the base id, and the msg is shorter
+ loc_ids = [str(x - bl2_base_id) for x in ctx.checked_locations]
+ # grab next 500 starting from offset
+ chunk_end = offset + 500
+ loc_ids = loc_ids[offset:chunk_end]
+ if chunk_end >= len(ctx.checked_locations): # mark end of list with 0
+ loc_ids.append("0")
+
+ response = ",".join(loc_ids)
+ writer.write(response.encode())
+ await writer.drain()
+ elif message == 'died':
+ if ctx.slot_data.get("death_link", False):
+ await ctx.send_death("BL2 Death")
+ response = "ok"
+ else:
+ response = "disabled"
+ writer.write(response.encode())
+ await writer.drain()
+ elif message == 'deathlink':
+ if ctx.deathlink_pending:
+ response = "yes"
+ ctx.deathlink_pending = False
+ else:
+ response = "no"
+ writer.write(response.encode())
+ await writer.drain()
+ else:
+ print("msg_check: " + str(message))
+ if message is None:
+ continue
+ item_id = int(message)
+ if (item_id + bl2_base_id) in ctx.locations_checked:
+ response = "skipped"
+ else:
+ response = "ack:" + str(item_id)
+ writer.write(response.encode())
+ await writer.drain()
+
+ if item_id == ctx.slot_data["goal"]: # victory condition
+ await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
+ ctx.finished_game = True
+ continue
+
+ await ctx.check_locations([item_id + bl2_base_id])
+
+ except asyncio.CancelledError:
+ print(f"Client {addr} disconnected (cancelled).")
+ ctx.command_processor.output(ctx.command_processor,f"sock client {addr} disconnected.")
+ except Exception as e:
+ print(f"Error with client {addr}: {e}")
+ ctx.command_processor.output(ctx.command_processor,f"Error with sock client {addr}: {e}")
+ break
+ # done with client
+ print(f"Client disconnected from: {addr}")
+ writer.close()
+ await writer.wait_closed()
+
+ server = await asyncio.start_server(
+ handle_sock_client, 'localhost', 9997
+ )
+ ctx.command_processor.output(ctx.command_processor,"sock server started on localhost:9997")
+
+ await ctx.exit_event.wait()
+ ctx.server_address = None
+ # await progression_watcher
+ await ctx.shutdown()
+def launch():
+ import colorama
+ parser = get_base_parser(description="Borderlands 2 Client, for text interfacing.")
+ args, rest = parser.parse_known_args()
+ colorama.init()
+ asyncio.run(main(args))
+ colorama.deinit()
diff --git a/worlds/borderlands2/Items.py b/worlds/borderlands2/Items.py
new file mode 100644
index 000000000000..11abe331a42a
--- /dev/null
+++ b/worlds/borderlands2/Items.py
@@ -0,0 +1,64 @@
+from typing import Dict, NamedTuple, Optional
+
+from BaseClasses import Item, ItemClassification
+from .archi_defs import item_name_to_id
+
+bl2_base_id: int = 2388000
+
+
+class Borderlands2Item(Item):
+ game = "Borderlands 2"
+
+
+class Borderlands2ItemData(NamedTuple):
+ code: Optional[int] = None
+ type: ItemClassification = ItemClassification.filler
+ description: Optional[str] = None
+ name: str = ""
+
+
+p_items = {"Progressive Jump", "Melee", "Crouch", "Sprint", "Gear Leveler", "Common Pistol"}
+# p_items = {}
+u_items = {"Vehicle Fire", "Gear Leveler", "Common Shield", "Common Pistol", "Common SMG"}
+# u_items = {}
+
+item_data_table: Dict[str, Borderlands2ItemData] = {
+ name: Borderlands2ItemData(
+ code=bl2_base_id + item_id,
+ type=ItemClassification.progression if name in p_items or name.startswith("Travel")
+ else ItemClassification.useful if name in u_items
+ else ItemClassification.trap if name.startswith("Trap")
+ else ItemClassification.filler,
+ description="",
+ name=name,
+ )
+ for name, item_id in item_name_to_id.items()
+}
+
+item_name_to_id = {name: data.code for name, data in item_data_table.items() if data.code is not None}
+item_descriptions = {name: data.description for name, data in item_data_table.items() if data.code is not None}
+
+# gear_rarities = [
+# "Common",
+# "Uncommon",
+# "Rare",
+# "VeryRare",
+# "Legendary",
+# "Seraph",
+# "Rainbow",
+# "Pearlescent",
+# "Unique",
+# ]
+#
+# gear_kinds = [
+# "Shield",
+# "GrenadeMod",
+# "ClassMod",
+# "Relic",
+# "Pistol",
+# "Shotgun",
+# "SMG",
+# "SniperRifle",
+# "AssaultRifle",
+# "RocketLauncher",
+# ]
diff --git a/worlds/borderlands2/Locations.py b/worlds/borderlands2/Locations.py
new file mode 100644
index 000000000000..b6c1883bff13
--- /dev/null
+++ b/worlds/borderlands2/Locations.py
@@ -0,0 +1,276 @@
+from typing import Dict, NamedTuple, Optional
+import re
+
+from BaseClasses import Location
+from .archi_defs import loc_name_to_id
+from .Items import bl2_base_id
+from .Regions import region_data_table
+
+
+class Borderlands2Location(Location):
+ game = "Borderlands 2"
+
+
+class Borderlands2LocationData(NamedTuple):
+ region: str
+ address: Optional[int] = None
+ description: Optional[str] = None
+
+
+# want to pull regions from the location names directly. So store some variants here
+region_name_variants = {
+ "Fink's": "FinksSlaughterhouse",
+ "Bloodshot": "BloodshotStronghold",
+ "Stronghold": "BloodshotStronghold",
+ "Ramparts": "BloodshotRamparts",
+ "Tundra": "TundraExpress",
+ "Outwash": "HighlandsOutwash",
+ "Caustic": "CausticCaverns",
+ "Caverns": "CausticCaverns",
+ "Thousand": "ThousandCuts",
+ "Blight": "EridiumBlight",
+ "Eridium": "EridiumBlight",
+ "Sawtooth": "SawtoothCauldron",
+ "Frostburn": "FrostburnCanyon",
+ "Southpaw": "SouthpawSteam&Power",
+ "Preserve": "WildlifeExploitationPreserve",
+ "WildlifePreserve": "WildlifeExploitationPreserve",
+ "Creature": "NaturalSelectionAnnex",
+ "CreatureSlaughter": "NaturalSelectionAnnex",
+ "Boneyard": "AridNexusBoneyard",
+ "Badlands": "AridNexusBadlands",
+ "BadassBar": "BadassCraterBar",
+ "Raceway": "SouthernRaceway",
+ "Grotto": "HuntersGrotto",
+ "Crater": "BadassCrater",
+ "Leviathan": "LeviathansLair",
+ "Magnys": "MagnysLighthouse",
+ "Washburne": "WashburneRefinery",
+ "Ardorton": "ArdortonStation",
+ "Crag": "CandlerakksCrag",
+ "Scylla's": "ScyllasGrove",
+ "Flamerock": "FlamerockRefuge",
+ "Immortal": "ImmortalWoods",
+ "Avarice": "MinesOfAvarice",
+ "TheBurrows": "Burrows",
+ "MtScarab": "Mt.ScarabResearchCenter",
+ "Mt.Scarab": "Mt.ScarabResearchCenter",
+ "GlutGulch": "GluttonyGulch",
+ "Distillery": "RotgutDistillery",
+ "MercenaryDay": "MarcusMercenaryShop",
+ "Rotgut": "RotgutDistillery",
+ "WamBam": "WamBamIsland",
+ "Digistruct": "DigistructPeak",
+ "DigistructInner": "DigistructPeakInner",
+ "Terramorphous": "TerramorphousPeak",
+ "Hayters": "HaytersFolly",
+ "Hayter's": "HaytersFolly",
+ "Warrior": "VaultOfTheWarrior",
+ "WarriorVault": "VaultOfTheWarrior",
+ "Handsome": "DragonKeep",
+ "Shadow": "HatredsShadow",
+ "Hatred's": "HatredsShadow",
+ "Agony": "LairOfInfiniteAgony",
+ "Lair": "LairOfInfiniteAgony",
+ "Pyro": "PyroPetesBar",
+ "Pete's": "PyroPetesBar",
+ "Murderlin's": "MurderlinsTemple",
+ "Arena": "TorgueArena",
+ "Hallowed": "HallowedHollow",
+ "Control": "ControlCoreAngel",
+ "Paradise": "FFSBossFight",
+ "DigiPeak": "DigistructPeak",
+ "DigiPeakInner": "DigistructPeakInner",
+ "LilithDLC": "FFSBossFight",
+ "TinaDLC": "DragonKeep",
+ "ScarlettDLC": "LeviathansLair",
+ "HammerlockDLC": "Terminus",
+ "TorgueDLC": "Forge",
+ "Combat": "SouthernShelf",
+ "Grenade": "SouthernShelf",
+ "Money": "SouthernShelf",
+}
+
+region_exceptions = {
+ "Common Shotgun": "SouthernShelf",
+ "Common Pistol": "SouthernShelf",
+ "Common Shield": "SouthernShelf",
+ "Legendary Pistol": "SouthernShelf",
+ "Level 2": "DigistructPeak",
+ "Level 3": "DigistructPeak",
+ "Level 4": "SouthernShelf",
+ "Level 15": "BloodshotRamparts",
+ "Level 16": "BloodshotRamparts",
+ "Level 17": "BloodshotRamparts",
+ "Level 18": "BloodshotRamparts",
+ "Level 19": "WildlifeExploitationPreserve",
+ "Level 20": "WildlifeExploitationPreserve",
+ "Level 21": "WildlifeExploitationPreserve",
+ "Level 22": "ThousandCuts",
+ "Level 23": "ThousandCuts",
+ "Level 24": "ThousandCuts",
+ "Level 25": "ControlCoreAngel",
+ "Level 26": "ControlCoreAngel",
+ "Level 27": "EridiumBlight",
+ "Level 28": "EridiumBlight",
+ "Level 29": "EridiumBlight",
+ "Level 30": "HerosPass",
+
+ "Chest WindshearWaste: Blindsided": "SouthernShelf", # don't let the chest past Knuckledragger be the intended way to kill him.
+ "Challenge Money: Whaddaya Buyin'?": "Sanctuary",
+ "Challenge Enemies: Hurly Burly": "SouthernShelf",
+
+ "Symbol ThreeHornsValley: Slums Wall": "BloodshotStronghold",
+ "Symbol Bloodshot: Pizza Intercom": "BloodshotRamparts",
+ "Symbol DahlAbandon: The Veiny Shaft": "HeliosFallen",
+
+ #enemies
+ "Enemy FrostburnCanyon: Spycho": "AridNexusBoneyard",
+ "Enemy ThreeHornsDivide: Boll": "FrostburnCanyon",
+ "Enemy ThreeHornsValley: Doc Mercy": "Sanctuary",
+ "Enemy Dust: Gettle": "Highlands",
+ "Enemy Dust: Mobley": "Highlands",
+ "Enemy ThreeHornsValley: Bad Maw": "BloodshotStronghold",
+ "Enemy Dust: McNally": "Opportunity",
+ "Enemy Dust: Mick/Tector": "Highlands",
+ "Enemy BloodshotStronghold: Dan": "BloodshotRamparts",
+ "Enemy BloodshotStronghold: Lee": "BloodshotRamparts",
+ "Enemy BloodshotStronghold: Mick": "BloodshotRamparts",
+ "Enemy BloodshotStronghold: Ralph": "BloodshotRamparts",
+ "Enemy BloodshotStronghold: Flinter": "BloodshotRamparts",
+ "Enemy TundraExpress: Prospector Zeke": "Highlands",
+ "Enemy Fridge: LaneyWhite": "Highlands",
+ "Enemy Fridge: Bloody": "Highlands",
+ "Enemy Fridge: Crabby": "Highlands",
+ "Enemy Fridge: Creepy": "Highlands",
+ "Enemy Fridge: Dirty": "Highlands",
+ "Enemy Fridge: Greedy": "Highlands",
+ "Enemy Fridge: Sleazy": "Highlands",
+ "Enemy Fridge: Tipsy": "Highlands",
+ "Enemy Fridge: Shorty": "Highlands",
+ "Enemy Fridge: Rakkman": "Highlands",
+ "Enemy Fridge: SmashHead": "Highlands",
+ "Enemy Fridge: Sinkhole": "Highlands",
+ "Enemy CausticCaverns: Blue": "Highlands",
+ "Enemy Lynchwood: DukinosMom": "EridiumBlight",
+ "Enemy Lynchwood: MadDog": "Opportunity",
+ "Enemy Lynchwood: SheriffNisha": "Opportunity",
+ "Enemy Lynchwood: DeputyWinger": "Opportunity",
+ "Enemy Opportunity: ForemanJasper": "Bunker",
+ "Enemy Opportunity: JackBodyDouble": "Bunker",
+ "Enemy UnassumingDocks: Unmotivated Golem": "MinesOfAvarice",
+ "Enemy Forest: Arguk the Butcher": "ImmortalWoods",
+ "Enemy DahlAbandon: The Dark Web": "HeliosFallen",
+ "Enemy Burrows: Lt. Angvar": "Mt.ScarabResearchCenter",
+ "Enemy DahlAbandon: Lt. Bolson": "Mt.ScarabResearchCenter",
+ "Enemy HeliosFallen: Lt. Tetra": "Mt.ScarabResearchCenter",
+
+ "Vending ThreeHornsValley Motel: Guns": "Sanctuary",
+ "Vending ThreeHornsValley Motel: Zed's Meds": "Sanctuary",
+ "Vending ThreeHornsValley Motel: Ammo Dump": "Sanctuary",
+
+ "Quest Lynchwood: Demon Hunter": "EridiumBlight",
+ "Quest Lynchwood: 3:10 to Kaboom": "Opportunity",
+ "Quest Lynchwood: Breaking the Bank": "Opportunity",
+ "Quest Lynchwood: Animal Rescue: Medicine": "Opportunity",
+ "Quest Lynchwood: Animal Rescue: Food": "Opportunity",
+ "Quest Lynchwood: Animal Rescue: Shelter": "Opportunity",
+ "Quest Lynchwood: Showdown": "Opportunity",
+ "Quest Dust: Positive Self Image": "FrostburnCanyon",
+ "Quest Dust: Too Close for Missiles": "FrostburnCanyon",
+ "Quest Dust: Clan War: Starting the War": "Highlands",
+ "Quest Dust: Clan War: First Place": "Highlands",
+ "Quest Dust: Clan War: Trailer Trashing": "Highlands",
+ "Quest Dust: Clan War: Zafords vs. Hodunks": "Highlands",
+ "Quest Dust: Rakkaholics Anonymous": "WildlifeExploitationPreserve",
+ "Quest Dust: The Good, the Bad, and the Mordecai": "Highlands",
+ "Quest Sanctuary: Bearer of Bad News": "ControlCoreAngel",
+ "Quest Sanctuary: BFFs": "EridiumBlight",
+ "Quest Sanctuary: Won't Get Fooled Again": "Highlands",
+ "Quest Sanctuary: Claptrap's Birthday Bash!": "Highlands",
+ "Quest ThreeHornsDivide: In Memoriam": "Sanctuary",
+ "Quest CausticCaverns: Minecart Mischief": "Highlands",
+ "Quest CausticCaverns: Perfectly Peaceful": "Highlands",
+ "Quest CausticCaverns: Safe and Sound": "Highlands",
+ "Quest Tundra Express: Mine, All Mine": "Highlands",
+ "Quest Tundra Express: The Pretty Good Train Robbery": "Highlands",
+ "Quest Fridge: The Cold Shoulder": "Highlands",
+ "Quest Fridge: Swallowed Whole": "Highlands",
+ "Quest Fridge: Note for Self-Person": "Highlands",
+ "Quest ThreeHornsValley: Medical Mystery": "FrostburnCanyon",
+ "Quest ThreeHornsValley: Medical Mystery: X-Com-municate": "FrostburnCanyon",
+ "Quest Washburne: Hyperius the Invincible": "LeviathansLair",
+ "Quest Hayters: Master Gee the Invincible": "LeviathansLair",
+ "Quest PyroPetesBar: Pyro Pete the Invincible": "Forge",
+ "Quest Beatdown: Number One Fan": "SouthernRaceway",
+ "Quest Beatdown: Mother-Lover": "SouthernRaceway",
+ "Quest CandlerakksCrag: Voracidous the Invincible": "Terminus",
+ "FlamerockRefuge: Feed Butt Stallion": "DragonKeep",
+ "FlamerockRefuge: Pet Butt Stallion": "DragonKeep",
+
+ "Generic: Skag": "ThreeHornsValley",
+ "Generic: Rakk": "SouthernShelf",
+ "Generic: Bullymong": "SouthernShelf",
+ "Generic: Psycho": "SouthernShelf",
+ "Generic: Rat": "SouthernShelf",
+ "Generic: Spiderant": "FrostburnCanyon",
+ "Generic: Varkid": "TundraExpress",
+ "Generic: Goliath": "ThousandCuts",
+ "Generic: Marauder": "SouthernShelf",
+ "Generic: Stalker": "HighlandsOutwash",
+ "Generic: Midget": "ThreeHornsValley",
+ "Generic: Nomad": "ThreeHornsValley",
+ "Generic: Thresher": "CausticCaverns",
+ "Generic: Badass": "Sanctuary",
+
+
+ "Chest BloodshotStronghold: Flinter's Room": "BloodshotRamparts",
+ "Chest Fridge: Smashhead's Cave": "Highlands",
+ "Chest Fridge: Rakkman's Lair": "Highlands",
+}
+
+coop_locations = {
+ # 1 = impossible, 2 = difficult
+
+ "Challenge Misc: Haters Gonna Hate": 1,
+ "Challenge Money: Psst, Hey Buddy...": 1,
+ "Challenge Dust: I've Got a Crush on You": 1,
+ "Challenge Lynchwood: Duel of Death": 1,
+ "Challenge Recovery: This Is No Time for Lazy!": 1,
+
+ "Challenge Opportunity: Top o' the World": 2,
+ "Challenge TerramorphousPeak: Cult of the Vault": 2,
+ "Symbol TerramorphousPeak: Dropdown": 2,
+}
+
+def get_region_from_loc_name(loc_name):
+ exception_loc = region_exceptions.get(loc_name)
+ if exception_loc is not None:
+ return exception_loc
+
+ pieces = re.split(r'[ :]', loc_name)
+
+ if len(pieces) <= 2:
+ return "Sanctuary"
+
+ second_word = pieces[1]
+ if second_word in region_data_table.keys():
+ return second_word
+
+ variant_translation = region_name_variants.get(second_word)
+ if variant_translation in region_data_table.keys():
+ return variant_translation
+
+ # print("didn't find region for loc: " + loc_name)
+ return "AridNexusBoneyard"
+
+
+location_data_table: Dict[str, Borderlands2LocationData] = {
+ name: Borderlands2LocationData(region=get_region_from_loc_name(name), address=bl2_base_id + loc_id, description="")
+ for name, loc_id in loc_name_to_id.items()
+}
+
+location_name_to_id = {name: data.address for name, data in location_data_table.items() if data.address is not None}
+
+location_descriptions = {name: data.description for name, data in location_data_table.items() if
+ data.address is not None}
diff --git a/worlds/borderlands2/Options.py b/worlds/borderlands2/Options.py
new file mode 100644
index 000000000000..6286f8dd0b5d
--- /dev/null
+++ b/worlds/borderlands2/Options.py
@@ -0,0 +1,375 @@
+import typing
+from dataclasses import dataclass
+from Options import Choice, Option, DeathLink, Range, Toggle, PerGameCommonOptions, StartInventoryPool
+
+class Goal(Choice):
+ """The victory condition for your run."""
+
+ display_name = "Goal"
+ option_save_roland_normal_mode = 0
+ option_saturn_normal_mode = 1
+ option_warrior_normal_mode = 2
+ # option_terramorphous_normal_mode = 3
+ # option_warrior_tvhm = 3
+ # option_warrior_uvhm = 4
+ # option_op_10 = 5
+ default = 0
+
+class DeleteStartingGear(Choice):
+ """Deletes your character's gear on first connection, avoids granting checks immediately for Skyrocket, Gearbox guns, etc.
+ (Please be careful to back up your saves and load the correct character)"""
+ display_name = "Delete Starting Gear"
+ option_keep = 0
+ option_delete = 1
+ default = 0
+
+class GearRarityItemPool(Choice):
+ """Gear kinds will be added to the item pool as receivable items.
+ disabled = Exclude from Item Pool, ability to equip things is always unlocked.
+ exclude_seraph_plus = Seraph, Pearlescent, and Effervescent are excluded
+ exclude_pearl_plus = Pearlescent and Effervescent are excluded
+ exclude_rainbow = Effervescent is excluded
+ """
+ display_name = "Gear Rarity Receivable Items"
+ option_disabled = 0
+ option_exclude_seraph_plus = 1
+ option_exclude_pearl_plus = 2
+ option_exclude_rainbow = 3
+ option_all = 4
+ default = 1
+
+class ReceiveGearItems(Choice):
+ """When receiving gear from the item pool, does it spawn for you or do you only get the ability to equip the ones you find?
+ This option does nothing if gear_rarity_item_pool is disabled
+ equip_only = Added to item pool, do not spawn gear
+ receive_non_unique = Added to item pool, only spawn gear that is not Unique/Legendary/etc. (red-text)
+ receive_all = Added to item pool, spawn all gear
+ """
+ display_name = "Gear Receive Type"
+ option_equip_only = 0
+ option_receive_non_unique = 1
+ option_receive_all = 2
+ # option_receive_unique_only = 4
+ default = 2
+
+# class FillerItems(Choice):
+# """What items should be added to fill out the item pool?
+# money = Money
+# eridium = Money and Eridium
+# gear = Extra Gear Checks
+# candy = Halloween Candy Spawns
+# xp = Experience
+# """
+# display_name = "Filler Items"
+# option_money = 0
+# option_eridium = 1
+# option_gear = 2
+# option_candy = 3
+# option_xp = 3
+# default = 3
+
+
+class VaultSymbols(Choice):
+ """Vault Symbols as location checks"""
+ display_name = "Vault Symbols"
+ option_none = 0
+ option_all = 1
+ # option_base_game_only = 2
+ default = 1
+
+class VendingMachines(Choice):
+ """Vending Machines as location checks"""
+ display_name = "Vending Machines"
+ option_none = 0
+ option_all = 1
+ # option_base_game_only = 2
+ default = 1
+
+class EntranceLocks(Choice):
+ """
+ Moving to another map area (regular or fast travel) is disabled until the associated item is found
+ Turning this option off has strange implications. You will basically be expected to goal in sphere one, since nothing would be "out of logic" for you.
+ all = You are required to unlock travel items in order to travel to other map areas
+ no_locks = Travel Items are not included in the multiworld.
+ """
+ display_name = "Entrance Locks"
+ option_no_locks = 0
+ option_all = 1
+ # option_base_game_only = 2
+ default = 1
+
+class JumpChecks(Choice):
+ """How many jump checks should be added to the pool. You will not start with the ability to jump unless you add "Progressive Jump" to your start_inventory_from_pool"""
+ display_name = "Jump Checks"
+ option_not_disabled = 0
+ option_1 = 1
+ option_2 = 2
+ option_3 = 3
+ option_4 = 4
+ option_5 = 5
+ default = 3
+
+class MaxJumpHeight(Choice):
+ """Each jump check will give you an equivalent fraction of your max jump height.
+ If Jump Checks is set to "not disabled" you will simply jump this high.
+ high = 1.5x
+ extra high = 2x"""
+ display_name = "Max Jump Height"
+ option_regular = 0
+ option_high = 1
+ option_extra_high = 2
+ default = 0
+
+class SprintChecks(Choice):
+ """How many sprint checks should be added to the pool. You will not start with the ability to sprint unless you add "Progressive Sprint" to your start_inventory_from_pool"""
+ display_name = "Sprint Checks"
+ option_not_disabled = 0
+ option_1 = 1
+ option_2 = 2
+ option_3 = 3
+ option_4 = 4
+ option_5 = 5
+ default = 3
+
+class MaxSprintSpeed(Choice):
+ """Each sprint check will give you an equivalent fraction of your max sprint speed.
+ If Sprint Checks is set to "not disabled" you will simply sprint this fast.
+ fast = 1.5x
+ extra fast = 2x"""
+ display_name = "Max Sprint Speed"
+ option_regular = 0
+ option_fast = 1
+ option_extra_fast = 2
+ option_supersonic = 4
+ default = 0
+
+class SpawnTraps(Choice):
+ """Add Spawn Traps to the item pool"""
+ display_name = "Entrance Locks"
+ option_none = 0
+ option_all = 1
+ # option_base_game_only = 2
+ default = 1
+
+# TODO: split this into separate toggleable quest checks and quest reward items
+class QuestRewardRando(Choice):
+ """Quest rewards are added to the item pool and Quests completions count as location checks"""
+ display_name = "Quest Reward Rando"
+ option_none = 0
+ option_all = 1
+ # option_base_game_only = 2
+ default = 1
+
+class GenericMobChecks(Choice):
+ """Adds a few checks into the location pool for farming generic mobs. Select a drop chance (default 5%)"""
+ display_name = "Generic Mob Checks"
+ option_disabled = 0
+ option_1_percent = 1
+ option_2_percent = 2
+ option_3_percent = 3
+ option_4_percent = 4
+ option_5_percent = 5
+ option_6_percent = 6
+ option_7_percent = 7
+ option_8_percent = 8
+ option_9_percent = 9
+ option_10_percent = 10
+ default = 5
+
+# class NamedEnemyChecks(Choice):
+# """Adds checks into the location pool for killing each named enemy
+# """
+# display_name = "Named Enemy Checks"
+# option_none = 0
+# option_all = 1
+# default = 1
+
+class GearRarityChecks(Choice):
+ """Adds checks into the location pool for the first time you pick up gear of each type + rarity combination
+ exclude_seraph_plus = Seraph, Pearlescent, and Effervescent are excluded
+ exclude_pearl_plus = Pearlescent and Effervescent are excluded
+ exclude_rainbow = Effervescent is excluded
+ """
+ display_name = "Rarity Checks"
+ option_disabled = 0
+ option_exclude_seraph_plus = 1
+ option_exclude_pearl_plus = 2
+ option_exclude_rainbow = 3
+ option_all = 4
+ default = 1
+
+class ChallengeChecks(Choice):
+ """Adds checks into the location pool for completing BAR challenges
+ """
+ display_name = "BAR Challenge Checks"
+ option_none = 0
+ option_level_1 = 1
+ # option_unique_only = 2
+ # option_exclude_unique = 3
+ # option_level_1_only_exclude_unique = 4
+ # option_all = 5
+ default = 1
+
+class ChestChecks(Choice):
+ """
+ Adds checks for opening most Red Chests
+ """
+ display_name = "Red Chest Checks"
+ option_none = 0
+ option_all = 1
+ # option_base_game_only = 2
+ default = 1
+
+# class ControlTraps(Choice):
+# """Add Control Traps to the item pool"""
+# display_name = "Entrance Locks"
+# option_none = 0
+# option_all = 1
+# default = 0
+
+
+# class FillExtraChecksWith(Choice):
+# """
+# Fill extra checks with this kind of item
+# """
+# display_name = "Fill Extra Checks With"
+# option_legendary_guns_and_items = 0
+# option_legendary_items = 1
+# option_legendary_guns = 2
+# option_purple_rarity_stuff = 3
+# default = 0
+
+class RemoveCoopChecks(Choice):
+ """
+ Removes checks that are impossible or difficult to do while playing solo
+ keep = don't remove any checks
+ remove_impossible = only remove impossible checks
+ remove_all = remove difficult and impossible checks
+ """
+ display_name = "Remove Co-op Checks"
+ option_keep = 0
+ option_remove_impossible = 1
+ option_remove_all = 2
+ default = 2
+
+class RemoveDLCChecks(Choice):
+ """
+ Removes checks associated with Scarlett, Torgue, Hammerlock, Tina, and Lilith DLCs
+ """
+ display_name = "Remove DLC Checks"
+ option_keep = 0
+ option_remove = 1
+ # maybe options for specific ones in the future.
+ default = 0
+
+class RemoveDigiPeakChecks(Choice):
+ """
+ Removes checks associated with Digistruct Peak
+ """
+ display_name = "Remove Digi Peak Checks"
+ option_keep = 0
+ option_remove = 1
+ # maybe options for specific ones in the future.
+ default = 0
+
+
+class RemoveHeadhunterChecks(Choice):
+ """
+ Removes checks associated with
+ Hallowed Hollow, Gluttony Gulch, Marcus's Mercenary Shop, Rotgut Distillery and Wam Bam Island
+ """
+ display_name = "Remove Headhunter Checks"
+ option_keep = 0
+ option_remove = 1
+ # maybe options for specific ones in the future.
+ default = 0
+
+class DeathLink(Toggle):
+ display_name = "Death Link"
+
+class DeathLinkPunishment(Choice):
+ """
+ If DeathLink is off, this option does nothing.
+ damage = take fatal damage when a DeathLink is received, but it can be blocked by healthgate.
+ ffyl = enter "fight for your life" mode when a DeathLink is received.
+ death = instantly die when a DeathLink is received.
+ """
+ display_name = "Death Link Mode"
+ option_damage = 0
+ option_ffyl = 1
+ option_death = 2
+ default = 1
+
+class DeathLinkSendMode(Choice):
+ """
+ If DeathLink is off, this option does nothing.
+ death = Send a DeathLink when you die
+ ffyl = Send a DeathLink whenever you fall into "fight for your life" mode
+ save_quit = Send a DeathLink whenever you save quit
+ save_quit_and_death = Send a DeathLink save quit and whenever you die
+ save_quit_and_ffyl = Send a DeathLink save quit and whenever you fall into ffyl
+ """
+ display_name = "Death Link Mode"
+ option_death = 0
+ option_ffyl = 1
+ option_save_quit = 2
+ option_save_quit_and_death = 3
+ option_save_quit_and_ffyl = 4
+ default = 0
+
+# class DropChanceMultiplier(Range):
+# """Runs the drop loot function extra times when any enemy dies. Multipliers will be added as items."""
+# display_name = "Drop Chance Multipliers"
+# range_start = 0
+# range_end = 3
+# default = 3
+
+# class LegendaryDropRandomizer(Toggle):
+# """Legendary drops will be removed from loot pools and replaced with checks."""
+# display_name = "Legendary Drop Randomizer"
+#
+#
+# class NamedEnemyRandomizer(Toggle):
+# """Named Enemies without legendary drops like Bone Head 2.0, Bad Maw, and W4R-D3N
+# will also have checks in their loot pools."""
+# display_name = "Named Enemy Randomizer"
+#
+#
+# class RandomLegendariesReceived(Toggle):
+# """Receive random legendaries."""
+# display_name = "Legendary Drop Randomizer"
+
+@dataclass
+class Borderlands2Options(PerGameCommonOptions):
+ goal: Goal
+ delete_starting_gear: DeleteStartingGear
+ gear_rarity_item_pool: GearRarityItemPool
+ receive_gear: ReceiveGearItems
+ vault_symbols: VaultSymbols
+ vending_machines: VendingMachines
+ entrance_locks: EntranceLocks
+ jump_checks: JumpChecks
+ max_jump_height: MaxJumpHeight
+ sprint_checks: SprintChecks
+ max_sprint_speed: MaxSprintSpeed
+ spawn_traps: SpawnTraps
+ quest_reward_rando: QuestRewardRando
+ generic_mob_checks: GenericMobChecks
+ gear_rarity_checks: GearRarityChecks
+ challenge_checks: ChallengeChecks
+ chest_checks: ChestChecks
+ # fill_extra_checks_with: FillExtraChecksWith
+ # legendary_rando: LegendaryDropRandomizer
+ # named_enemy_rando: NamedEnemyRandomizer
+ # drop_multiplier_amt: DropChanceMultiplier
+ remove_dlc_checks: RemoveDLCChecks
+ remove_digi_peak_checks: RemoveDigiPeakChecks
+ remove_headhunter_checks: RemoveHeadhunterChecks
+ remove_coop_checks: RemoveCoopChecks
+ death_link: DeathLink
+ death_link_punishment: DeathLinkPunishment
+ death_link_send_mode: DeathLinkSendMode
+ start_inventory_from_pool: StartInventoryPool
+
+
+
diff --git a/worlds/borderlands2/Regions.py b/worlds/borderlands2/Regions.py
new file mode 100644
index 000000000000..4aea20d38d1e
--- /dev/null
+++ b/worlds/borderlands2/Regions.py
@@ -0,0 +1,138 @@
+from typing import Dict, List, NamedTuple, Union
+
+
+class Borderlands2RegionData(NamedTuple):
+ name: str = ""
+ travel_item_name: Union[str, List[str]] = ""
+ connecting_regions: List[str] = []
+ @property
+ def primary_travel_item(self):
+ if type(self.travel_item_name) is str:
+ return self.travel_item_name
+ elif len(self.travel_item_name) > 0:
+ return self.travel_item_name[0]
+ return ""
+
+region_data_table: Dict[str, Borderlands2RegionData] = {
+ "Menu": Borderlands2RegionData("Menu", "", [
+ "WindshearWaste",
+ "DigistructPeak",
+ # "FFSIntroSanctuary",
+ # "UnassumingDocks",
+ # "BadassCrater",
+ # "Oasis",
+ # "HuntersGrotto",
+ # "MarcusMercenaryShop",
+ # "GluttonyGulch",
+ # "RotgutDistillery",
+ # "WamBamIsland",
+ # "HallowedHollow",
+ ]),
+
+ "WindshearWaste": Borderlands2RegionData("WindshearWaste", "", ["SouthernShelf"]),
+ "SouthernShelf": Borderlands2RegionData("SouthernShelf", "Travel: Southern Shelf", ["SouthernShelfBay", "ThreeHornsDivide"]),
+ "SouthernShelfBay": Borderlands2RegionData("SouthernShelfBay", "Travel: Southern Shelf Bay", []),
+ "ThreeHornsDivide": Borderlands2RegionData("ThreeHornsDivide", "Travel: Three Horns Divide", ["ThreeHornsValley", "Sanctuary"]),
+ "ThreeHornsValley": Borderlands2RegionData("ThreeHornsValley", "Travel: Three Horns Valley", ["SouthpawSteam&Power", "Dust"]),
+ "Sanctuary": Borderlands2RegionData("Sanctuary", "Travel: Sanctuary", ["FrostburnCanyon"]),
+ "FrostburnCanyon": Borderlands2RegionData("FrostburnCanyon", "Travel: Frostburn Canyon", ["BloodshotStronghold", "FriendshipGulag"]),
+ "SouthpawSteam&Power": Borderlands2RegionData("SouthpawSteam&Power", "Travel: Southpaw Steam & Power", []),
+ "Dust": Borderlands2RegionData("Dust", "Travel: The Dust", ["Lynchwood"]),
+ "BloodshotStronghold": Borderlands2RegionData("BloodshotStronghold", ["Travel: Bloodshot Stronghold", "Travel: The Dust", "Travel: Frostburn Canyon", "Travel: Three Horns Valley"], ["BloodshotRamparts"]),
+ "BloodshotRamparts": Borderlands2RegionData("BloodshotRamparts", "Travel: Bloodshot Ramparts", [
+ "TundraExpress",
+ "MarcusMercenaryShop",
+ "GluttonyGulch",
+ "RotgutDistillery",
+ "WamBamIsland",
+ "HallowedHollow",
+ "BadassCrater",
+ "Oasis",
+ ]),
+ "Fridge": Borderlands2RegionData("Fridge", ["Travel: The Fridge"], ["FinksSlaughterhouse", "HighlandsOutwash"]),
+ "HighlandsOutwash": Borderlands2RegionData("HighlandsOutwash", ["Travel: Highlands Outwash"], ["Highlands"]),
+ "Highlands": Borderlands2RegionData("Highlands", ["Travel: Highlands"], ["HolySpirits", "WildlifeExploitationPreserve", "ThousandCuts","Opportunity"]),
+ "FriendshipGulag": Borderlands2RegionData("FriendshipGulag", ["Travel: Friendship Gulag"], []),
+ "Lynchwood": Borderlands2RegionData("Lynchwood", ["Travel: Lynchwood"], []),
+ "FinksSlaughterhouse": Borderlands2RegionData("FinksSlaughterhouse", ["Travel: Fink's Slaughterhouse"], []),
+ "SanctuaryHole": Borderlands2RegionData("SanctuaryHole", ["Travel: Sanctuary Hole"], ["CausticCaverns"]),
+ "TerramorphousPeak": Borderlands2RegionData("TerramorphousPeak", ["Travel: Terramorphous Peak"], []),
+ "CausticCaverns": Borderlands2RegionData("CausticCaverns", ["Travel: Caustic Caverns"], []),
+ "Opportunity": Borderlands2RegionData("Opportunity", ["Travel: Opportunity"], ["Bunker"]),
+ "HolySpirits": Borderlands2RegionData("HolySpirits", ["Travel: The Holy Spirits"], []),
+ "WildlifeExploitationPreserve": Borderlands2RegionData("WildlifeExploitationPreserve", ["Travel: Wildlife Exploitation Preserve"], ["NaturalSelectionAnnex", "Bunker"]),
+ "NaturalSelectionAnnex": Borderlands2RegionData("NaturalSelectionAnnex", ["Travel: Natural Selection Annex"], []),
+ "TundraExpress": Borderlands2RegionData("TundraExpress", ["Travel: Tundra Express"], ["EndOfTheLine"]),
+ "EndOfTheLine": Borderlands2RegionData("EndOfTheLine", ["Travel: End of the Line"], ["SanctuaryHole", "Fridge"]),
+ "Bunker": Borderlands2RegionData("Bunker", ["Travel: The Bunker",
+ "Travel: Wildlife Exploitation Preserve",
+ "Travel: Thousand Cuts",
+ "Travel: Opportunity"], ["ControlCoreAngel"]),
+ "ThousandCuts": Borderlands2RegionData("ThousandCuts", ["Travel: Thousand Cuts"], ["Bunker"]),
+ "EridiumBlight": Borderlands2RegionData("EridiumBlight", ["Travel: Eridium Blight"], ["OreChasm", "SawtoothCauldron"]),
+ "SawtoothCauldron": Borderlands2RegionData("SawtoothCauldron", ["Travel: Sawtooth Cauldron"], ["AridNexusBoneyard"]),
+ "OreChasm": Borderlands2RegionData("OreChasm", ["Travel: Ore Chasm"], []),
+ "ControlCoreAngel": Borderlands2RegionData("ControlCoreAngel", ["Travel: Control Core Angel"], ["EridiumBlight"]),
+ "AridNexusBoneyard": Borderlands2RegionData("AridNexusBoneyard", ["Travel: Arid Nexus Boneyard"], ["AridNexusBadlands"]),
+ "AridNexusBadlands": Borderlands2RegionData("AridNexusBadlands", ["Travel: Arid Nexus Badlands"], ["HerosPass"]),
+ "HerosPass": Borderlands2RegionData("HerosPass", ["Travel: Hero's Pass"], ["VaultOfTheWarrior"]),
+ "VaultOfTheWarrior": Borderlands2RegionData("VaultOfTheWarrior", ["Travel: Vault of the Warrior"], ["TerramorphousPeak",
+ "FFSIntroSanctuary",
+ "UnassumingDocks",
+ "HuntersGrotto",
+ "DigistructPeakInner",
+ ]),
+
+ "FFSIntroSanctuary": Borderlands2RegionData("FFSIntroSanctuary", ["Travel: FFS Intro Sanctuary"], ["Backburner"]),
+ "Burrows": Borderlands2RegionData("Burrows", ["Travel: The Burrows"], ["HeliosFallen"]),
+ "Backburner": Borderlands2RegionData("Backburner", ["Travel: The Backburner", "Travel: FFS Intro Sanctuary"], ["DahlAbandon"]),
+ "DahlAbandon": Borderlands2RegionData("DahlAbandon", ["Travel: Dahl Abandon"], ["Burrows"]),
+ "HeliosFallen": Borderlands2RegionData("HeliosFallen", ["Travel: Helios Fallen"], ["Mt.ScarabResearchCenter"]),
+ "WrithingDeep": Borderlands2RegionData("WrithingDeep", ["Travel: Writhing Deep"], []),
+ "Mt.ScarabResearchCenter": Borderlands2RegionData("Mt.ScarabResearchCenter", ["Travel: Mt. Scarab Research Center"], ["FFSBossFight"]),
+ "FFSBossFight": Borderlands2RegionData("FFSBossFight", ["Travel: FFS Boss Fight"], ["WrithingDeep"]),
+
+ "UnassumingDocks": Borderlands2RegionData("UnassumingDocks", ["Travel: Unassuming Docks"], ["FlamerockRefuge"]),
+ "FlamerockRefuge": Borderlands2RegionData("FlamerockRefuge", ["Travel: Flamerock Refuge"], ["Forest"]),
+ "HatredsShadow": Borderlands2RegionData("HatredsShadow", ["Travel: Hatred's Shadow"], ["LairOfInfiniteAgony"]),
+ "LairOfInfiniteAgony": Borderlands2RegionData("LairOfInfiniteAgony",["Travel: Lair of Infinite Agony"], ["DragonKeep"]),
+ "ImmortalWoods": Borderlands2RegionData("ImmortalWoods", ["Travel: Immortal Woods"], ["MinesOfAvarice"]),
+ "Forest": Borderlands2RegionData("Forest", ["Travel: The Forest"], ["ImmortalWoods"]),
+ "MinesOfAvarice": Borderlands2RegionData("MinesOfAvarice", ["Travel: Mines of Avarice"], ["HatredsShadow"]),
+ "MurderlinsTemple": Borderlands2RegionData("MurderlinsTemple", ["Travel: Murderlin's Temple"], []),
+ "WingedStorm": Borderlands2RegionData("WingedStorm", ["Travel: The Winged Storm"], []),
+ "DragonKeep": Borderlands2RegionData("DragonKeep", ["Travel: Dragon Keep"], ["WingedStorm", "MurderlinsTemple"]),
+
+ "BadassCrater": Borderlands2RegionData("BadassCrater", ["Travel: Badass Crater"], ["TorgueArena"]),
+ "Beatdown": Borderlands2RegionData("Beatdown", ["Travel: The Beatdown"], ["PyroPetesBar"]),
+ "TorgueArena": Borderlands2RegionData("TorgueArena", ["Travel: Torgue Arena"], ["TorgueArenaRing","Beatdown"]),
+ "TorgueArenaRing": Borderlands2RegionData("TorgueArenaRing", ["Travel: Torgue Arena Ring"], ["SouthernRaceway"]),
+ "BadassCraterBar": Borderlands2RegionData("BadassCraterBar", ["Travel: Badass Crater Bar"], ["SouthernRaceway"]),
+ "Forge": Borderlands2RegionData("Forge", ["Travel: The Forge"], []),
+ "SouthernRaceway": Borderlands2RegionData("SouthernRaceway", ["Travel: Southern Raceway", "Travel: Torgue Arena Ring"], ["Forge"]),
+ "PyroPetesBar": Borderlands2RegionData("PyroPetesBar", ["Travel: Pyro Pete's Bar"], ["BadassCraterBar"]),
+
+ "Oasis": Borderlands2RegionData("Oasis", ["Travel: Oasis"], ["Wurmwater"]),
+ "HaytersFolly": Borderlands2RegionData("HaytersFolly", ["Travel: Hayter's Folly"], ["Rustyards"]),
+ "Wurmwater": Borderlands2RegionData("Wurmwater", ["Travel: Wurmwater"], ["HaytersFolly"]),
+ "WashburneRefinery": Borderlands2RegionData("WashburneRefinery", ["Travel: Washburne Refinery"], ["MagnysLighthouse"]),
+ "Rustyards": Borderlands2RegionData("Rustyards", ["Travel: The Rustyards"], ["WashburneRefinery"]),
+ "MagnysLighthouse": Borderlands2RegionData("MagnysLighthouse", ["Travel: Magnys Lighthouse"], ["LeviathansLair"]),
+ "LeviathansLair": Borderlands2RegionData("LeviathansLair", ["Travel: The Leviathan's Lair"], []),
+
+ "HuntersGrotto": Borderlands2RegionData("HuntersGrotto", ["Travel: Hunter's Grotto"], ["ScyllasGrove"]),
+ "CandlerakksCrag": Borderlands2RegionData("CandlerakksCrag", ["Travel: Candlerakk's Cragg"], ["Terminus"]),
+ "ArdortonStation": Borderlands2RegionData("ArdortonStation", ["Travel: Ardorton Station"], ["CandlerakksCrag"]),
+ "ScyllasGrove": Borderlands2RegionData("ScyllasGrove", ["Travel: Scylla's Grove"], ["ArdortonStation"]),
+ "Terminus": Borderlands2RegionData("Terminus", ["Travel: Terminus"], []),
+
+ "DigistructPeak": Borderlands2RegionData("DigistructPeak", ["Travel: Digistruct Peak"], []),
+ "DigistructPeakInner": Borderlands2RegionData("DigistructPeakInner", ["Travel: Digistruct Peak"], []),
+ # "DigistructPeakOP5": Borderlands2RegionData("DigistructPeakOP5", "", []),
+
+ "MarcusMercenaryShop": Borderlands2RegionData("MarcusMercenaryShop", ["Travel: Marcus's Mercenary Shop"], []),
+ "GluttonyGulch": Borderlands2RegionData("GluttonyGulch", ["Travel: Gluttony Gulch"], []),
+ "RotgutDistillery": Borderlands2RegionData("RotgutDistillery", ["Travel: Rotgut Distillery"], []),
+ "WamBamIsland": Borderlands2RegionData("WamBamIsland", ["Travel: Wam Bam Island"], []),
+ "HallowedHollow": Borderlands2RegionData("HallowedHollow", ["Travel: Hallowed Hollow"], []),
+}
diff --git a/worlds/borderlands2/Rules.py b/worlds/borderlands2/Rules.py
new file mode 100644
index 000000000000..de0f90356a4b
--- /dev/null
+++ b/worlds/borderlands2/Rules.py
@@ -0,0 +1,174 @@
+from worlds.generic.Rules import set_rule, add_rule
+
+from . import Borderlands2World
+from .Regions import region_data_table
+from .Locations import Borderlands2Location
+from .Items import Borderlands2Item
+from BaseClasses import ItemClassification
+
+# TODO record and calculate how much jump is required
+locs_with_jump_required = [
+ "Vending Tundra Farm: Guns",
+ "Quest ThreeHornsValley: No Vacancy",
+ "Vending ThreeHornsValley Motel: Guns",
+ "Vending ThreeHornsValley Motel: Zed's Meds",
+ "Vending ThreeHornsValley Motel: Ammo Dump",
+ "Quest ThreeHornsValley: Neither Rain nor Sleet nor Skags",
+ "Quest Dust: Too Close For Missiles",
+ "Quest Tundra Express: The Pretty Good Train Robbery",
+ "Quest Tundra Express: Mine, All Mine",
+ "Quest Highlands: Hidden Journals",
+ "Quest Fridge: Note for Self-Person",
+ "Enemy BloodshotStronghold: Flinter",
+ "Enemy TundraExpress: Prospector Zeke",
+ "Enemy CausticCaverns: Badass Creeper",
+ "Symbol SouthernShelfBay: Ice Flows Shipwreck",
+ "Symbol SouthernShelf: Flynt's Ship",
+ "Symbol SouthernShelf: Safehouse",
+ "Symbol ThreeHornsDivide: Billboard",
+ "Symbol Sanctuary: Rooftop",
+ "Symbol Sanctuary: Parkour Door",
+ "Symbol Southpaw: Parkour",
+ "Symbol Southpaw: Engine",
+ "Symbol ThreeHornsValley: Frostsprings Wall",
+ "Symbol Dust: Moonshiner Lid",
+ "Symbol Bloodshot: Switch Room",
+ "Symbol Fridge: Secret Stash",
+ "Symbol Fridge: Sheetmetal Roof",
+ "Symbol ThousandCuts: No Man's Land Shack",
+ "Symbol Lynchwood: Gunslinger Roof",
+ "Symbol Lynchwood: Main Street",
+ "Symbol Opportunity: Office Bridge",
+ "Symbol BadassCrater: Billboard Lower",
+ "Symbol HerosPass: Strut",
+ "Chest EridiumBlight: Volcano Free Chest",
+ "Chest EridiumBlight: Pipe Dreaming",
+ "Chest VaultOfTheWarrior: Lava River Cave",
+ "Chest BloodshotStronghold: Jail Cell",
+ "Chest BloodshotStronghold: Flinter's Room",
+ "Chest BloodshotRamparts: Car Junkyard Trunk",
+ "Chest Fridge: Smashhead's Cave",
+ "Chest AridNexusBoneyard: Eridium Pump Station 2",
+ "Chest AridNexusBoneyard: Eridium Pump Station 3",
+ "Chest FriendshipGulag: Gulag Awning",
+ "Chest FrostburnCanyon: Incinerator Camp",
+ "Chest WildlifePreserve: Observation Wing #1",
+ "Chest WildlifePreserve: Mordy's Secrest Stash",
+ "Chest TundraExpress: Mount Molehill Mine Top",
+ "Chest LeviathansLair: Outer #5",
+ "Chest LeviathansLair: Outer #1",
+ "Chest LeviathansLair: Outer #6",
+ "Chest LeviathansLair: Outer #4",
+ "Chest LeviathansLair: Outer #3",
+ "Chest LeviathansLair: Outer #7",
+ "Chest LeviathansLair: Outer #2",
+ "Chest LeviathansLair: Inner #1",
+ "Chest LeviathansLair: Inner #2",
+ "Chest LeviathansLair: Inner #3",
+ "Chest LeviathansLair: Inner #4",
+ "Chest LeviathansLair: Inner #5",
+ "Chest LeviathansLair: Inner #6",
+ "Chest Forge: Forge Parkour",
+ "Chest SawtoothCauldron: Smoking Guano Grotto Platform",
+ "Chest SawtoothCauldron: Cramfist's Foundry",
+ "Chest SawtoothCauldron: Mortar's Elevator",
+ "Chest Southpaw: After Oney",
+ "Chest AridNexusBadlands: Hey! Over Here!",
+ "Chest AridNexusBadlands: Fyrestone Motel Roof",
+ "Chest BadassCrater: Crater Rim Parkour Hut",
+ "Chest CandlerakksCrag: Lower Bluff",
+ "Chest CandlerakksCrag: Tracking Area",
+ "Chest CandlerakksCrag: Before Terminus",
+ "Chest HatredsShadow: Birds Nest", # and sprint
+ "Chest ImmortalWoods: Graveyard Pillar", # 630
+ "Chest ImmortalWoods: Ruins Parkour", # and sprint
+ "Chest WingedStorm: Chest #54",
+ "Chest WingedStorm: Chest #55",
+ "Chest MarcusMercenaryShop: Station Rooftop",
+]
+
+locs_with_crouch_required = [
+ "Enemy HatredsShadow: Handsome Dragon",
+ "Symbol Opportunity: Construction Site",
+ "Chest SouthernShelf: Boom Bewm Elevator",
+]
+
+# TODO list for crouch
+
+# TODO list for vehicle fire (mostly just challenges)
+
+def try_add_rule(place, rule):
+ if place is None:
+ return
+ try:
+ add_rule(place, rule)
+ except:
+ print(f"failed setting rule at {place}")
+
+
+def set_rules(world: Borderlands2World):
+
+ # items must be classified as progression to use in rules here
+ try_add_rule(world.try_get_entrance("WindshearWaste to SouthernShelf"),
+ lambda state: state.has("Melee", world.player) and state.has("Common Pistol", world.player))
+ #add_rule(world.multiworld.get
+ # add_rule(world.multiworld.get_entrance("SouthernShelf to ThreeHornsDivide", world.player),
+ # lambda state: state.has("Common Pistol", world.player))
+ # add_rule(world.multiworld.get_location("Enemy WindshearWaste: Knuckle Dragger", world.player),
+ # lambda state: state.has("Melee", world.player))
+
+ if world.options.jump_checks.value > 0:
+ # ensure you can at least jump a little before wildlife preserve
+ # try_add_rule(world.try_get_entrance("Highlands to WildlifeExploitationPreserve"),
+ # lambda state: state.has("Progressive Jump", world.player))
+ try_add_rule(world.try_get_entrance("HerosPass to VaultOfTheWarrior"),
+ lambda state: state.has("Progressive Jump", world.player))
+ try_add_rule(world.try_get_entrance("BadassCrater to TorgueArena"), # 490 jump_z required
+ lambda state: state.has("Progressive Jump", world.player))
+ try_add_rule(world.try_get_entrance("BloodshotRamparts to Oasis"),
+ lambda state: state.has("Progressive Jump", world.player))
+
+ for loc in locs_with_jump_required:
+ try_add_rule(world.try_get_location(loc),
+ lambda state: state.has("Progressive Jump", world.player)
+ )
+
+ #need melee to break vines to Hector
+ try_add_rule(world.try_get_entrance("Mt.ScarabResearchCenter to FFSBossFight"),
+ lambda state: state.has("Melee", world.player))
+
+ try_add_rule(world.try_get_entrance("CandlerakksCrag to Terminus"),
+ lambda state: state.has("Crouch", world.player))
+ # If you die to the dragon, you need to crouch under the gate
+ try_add_rule(world.try_get_entrance("HatredsShadow to LairOfInfiniteAgony"),
+ lambda state: state.has("Crouch", world.player))
+
+ for loc in locs_with_crouch_required:
+ try_add_rule(world.try_get_location(loc),
+ lambda state: state.has("Crouch", world.player)
+ )
+
+
+
+ if world.options.entrance_locks.value == 0:
+ # skip if no entrance locks
+ return
+
+ for name, region_data in region_data_table.items():
+ region = world.multiworld.get_region(name, world.player)
+ for c_region_name in region_data.connecting_regions:
+ c_region_data = region_data_table[c_region_name]
+ ent_name = f"{region.name} to {c_region_name}"
+ t_item = c_region_data.travel_item_name
+ if t_item and isinstance(t_item, str):
+ try_add_rule(
+ world.try_get_entrance(ent_name),
+ lambda state, travel_item=t_item: state.has(travel_item, world.player)
+ )
+ elif t_item and isinstance(t_item, list):
+ try_add_rule(
+ world.try_get_entrance(ent_name),
+ lambda state, travel_item=t_item: state.has_all(travel_item, world.player)
+ )
+
+
diff --git a/worlds/borderlands2/__init__.py b/worlds/borderlands2/__init__.py
new file mode 100644
index 000000000000..8a57d804cd6f
--- /dev/null
+++ b/worlds/borderlands2/__init__.py
@@ -0,0 +1,337 @@
+from typing import List
+
+from BaseClasses import ItemClassification, Region, Tutorial, LocationProgressType
+from worlds.AutoWorld import WebWorld, World
+from worlds.LauncherComponents import components, Component, launch_subprocess, Type
+from .Items import Borderlands2Item, item_data_table, bl2_base_id, item_name_to_id, item_descriptions
+from .Locations import Borderlands2Location, location_data_table, location_name_to_id, location_descriptions, get_region_from_loc_name, coop_locations
+from .Options import Borderlands2Options
+from .Regions import region_data_table
+from .archi_defs import loc_name_to_id, item_id_to_name, gear_kind_to_id
+import random
+
+class Borderlands2WebWorld(WebWorld):
+ theme = "ice"
+
+ tutorials = [Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up Borderlands 2 for Multiworld.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["EdricY"]
+ )]
+
+
+def launch_client():
+ from .Client import launch
+ launch_subprocess(launch, name='Borderlands 2 Client')
+
+
+components.append(Component("Borderlands 2 Client",
+ func=launch_client,
+ component_type=Type.CLIENT))
+
+
+class Borderlands2World(World):
+ """
+ Borderlands 2 is a looter shooter we all love.
+ """
+
+ game = "Borderlands 2"
+ web = Borderlands2WebWorld()
+ options_dataclass = Borderlands2Options
+ options: Borderlands2Options
+ location_name_to_id = location_name_to_id
+ location_descriptions = location_descriptions
+ item_name_to_id = item_name_to_id
+ item_descriptions = item_descriptions
+ goal = loc_name_to_id["Enemy BloodshotRamparts: W4R-D3N"] # without base id
+ skill_pts_total = 0
+ filler_counter = 0
+
+ restricted_regions = set()
+
+ def try_get_entrance(self, entrance_name):
+ try:
+ return self.multiworld.get_entrance(entrance_name, self.player)
+ except KeyError:
+ print("couldn't find entrance: " + entrance_name)
+ return None
+
+ def try_get_location(self, loc_name):
+ try:
+ return self.multiworld.get_location(loc_name, self.player)
+ except KeyError:
+ print("couldn't find location: " + loc_name)
+ return None
+
+ def generate_early(self):
+ if self.options.remove_dlc_checks.value == 1:
+ self.restricted_regions.update([
+ "FFSIntroSanctuary", "Burrows", "Backburner", "DahlAbandon", "HeliosFallen", "WrithingDeep", "Mt.ScarabResearchCenter", "FFSBossFight",
+ "UnassumingDocks", "FlamerockRefuge", "HatredsShadow", "LairOfInfiniteAgony", "ImmortalWoods", "Forest", "MinesOfAvarice", "MurderlinsTemple", "WingedStorm", "DragonKeep",
+ "BadassCrater","Beatdown","TorgueArena","TorgueArenaRing","BadassCraterBar","Forge","SouthernRaceway","PyroPetesBar", "Oasis", "HaytersFolly", "Wurmwater", "WashburneRefinery", "Rustyards", "MagnysLighthouse", "LeviathansLair",
+ "HuntersGrotto", "CandlerakksCrag", "ArdortonStation", "ScyllasGrove", "Terminus",
+ ])
+
+ if self.options.remove_digi_peak_checks.value == 1:
+ self.restricted_regions.update(["DigistructPeak", "DigistructPeakInner"])
+
+ if self.options.remove_headhunter_checks.value == 1:
+ self.restricted_regions.update([
+ "MarcusMercenaryShop", "GluttonyGulch", "RotgutDistillery", "WamBamIsland", "HallowedHollow"
+ ])
+
+
+ def create_item(self, name: str) -> Borderlands2Item:
+ return Borderlands2Item(name, item_data_table[name].type, item_data_table[name].code, self.player)
+
+ def create_filler(self) -> Borderlands2Item:
+ self.filler_counter += 1
+ branch = self.filler_counter % 7
+ if branch == 1:
+ if self.skill_pts_total < 126: # max at 126 skill points
+ self.skill_pts_total += 3
+ return self.create_item("3 Skill Points")
+
+ if branch == 2:
+ return self.create_item("10 Eridium")
+
+ if branch == 3:
+ return self.create_item("10% Exp")
+
+ if branch == 4:
+ # white and green gear
+ gear_name = random.choice([k for k in gear_kind_to_id.keys() if "common" in k.lower()])
+ gear_name = "Filler Gear: " + gear_name
+ return self.create_item(gear_name)
+
+ if branch == 5:
+ candy_name = random.choice(["YellowCandy", "RedCandy", "GreenCandy", "BlueCandy"])
+ return self.create_item(candy_name)
+
+ if branch == 6:
+ gemstone_name = random.choice(["Filler Gear: Gemstone Pistol", "Filler Gear: Gemstone Shotgun",
+ "Filler Gear: Gemstone SMG", "Filler Gear: Gemstone SniperRifle",
+ "Filler Gear: Gemstone AssaultRifle"])
+ return self.create_item(gemstone_name)
+
+ return self.create_item("$100")
+
+ def create_items(self) -> None:
+ item_pool: List[Borderlands2Item] = []
+ item_pool += [self.create_item(name) for name in item_data_table.keys()] # 1 of everything to start
+ item_pool += [self.create_item("Weapon Slot")] # 2 total weapon slots
+ item_pool += [self.create_item("Progressive Money Cap") for _ in range(7)] # money cap is 8 stages
+ item_pool += [self.create_item("3 Skill Points") for _ in range(8)] # hit 27 at least
+ self.skill_pts_total += 3 * 9
+
+ # remove filler gear for now
+ item_pool = [item for item in item_pool if not item.name.startswith("Filler Gear")]
+
+ # setup jump checks
+ if self.options.jump_checks.value == 0:
+ # remove jump check
+ item_pool = [item for item in item_pool if not item.name == "Progressive Jump"]
+ else:
+ # add num checks - 1
+ jumps_to_add = self.options.jump_checks.value - 1
+ item_pool += [self.create_item("Progressive Jump") for _ in range(jumps_to_add)]
+
+ # setup sprint checks
+ if self.options.sprint_checks.value == 0:
+ # remove sprint check
+ item_pool = [item for item in item_pool if not item.name == "Progressive Sprint"]
+ else:
+ # add num checks - 1
+ sprints_to_add = self.options.sprint_checks.value - 1
+ item_pool += [self.create_item("Progressive Sprint") for _ in range(sprints_to_add)]
+
+ restricted_travel_items = [region_data_table[r].primary_travel_item for r in self.restricted_regions]
+ new_pool = []
+ for item in item_pool:
+ # skip travel items (entrance locks)
+ if self.options.entrance_locks.value == 0 and item.name.startswith("Travel: "):
+ continue
+ # skip trap items
+ if self.options.spawn_traps.value == 0 and item.name.startswith("Trap Spawn"):
+ continue
+ # skip quest rewards
+ if self.options.quest_reward_rando.value == 0 and item.name.startswith("Quest"):
+ continue
+
+ # skip gear rewards
+ if self.options.gear_rarity_item_pool.value != 4:
+ if self.options.gear_rarity_checks.value <= 3 and item.name.startswith("Rainbow"):
+ continue
+ if self.options.gear_rarity_checks.value <= 2 and item.name.startswith("Pearlescent"):
+ continue
+ if self.options.gear_rarity_checks.value <= 1 and item.name.startswith("Seraph"):
+ continue
+ if self.options.gear_rarity_checks.value == 0 and item.code - bl2_base_id <= 199 and item.code - bl2_base_id >= 100:
+ continue
+
+ # skip restricted region Travel Items
+ if item.name in restricted_travel_items:
+ continue
+ # skip items from restricted regions (mostly quests)
+ if get_region_from_loc_name(item.name) in self.restricted_regions:
+ continue
+
+ # item should be included
+ new_pool.append(item)
+
+ item_pool = new_pool
+
+ # fill leftovers
+ location_count = len(self.multiworld.get_locations(self.player))
+ leftover = location_count - len(item_pool)
+ for _ in range(leftover - 1):
+ item_pool += [self.create_filler()]
+
+ self.multiworld.itempool += item_pool
+
+ def create_regions(self) -> None:
+ if self.options.goal.value == 0:
+ goal_name = "Enemy BloodshotRamparts: W4R-D3N"
+ elif self.options.goal.value == 1:
+ goal_name = "Enemy AridNexusBadlands: Saturn"
+ elif self.options.goal.value == 2:
+ goal_name = "Enemy VaultOfTheWarrior: Warrior"
+ self.goal = loc_name_to_id[goal_name]
+
+ loc_dict = {
+ location_name: location_data.address for location_name, location_data in location_data_table.items()
+ }
+
+ # remove goal from locations
+ del loc_dict[goal_name]
+
+ # remove symbols
+ if self.options.vault_symbols.value == 0:
+ for location_name, location_data in location_data_table.items():
+ if location_name.startswith("Symbol"):
+ del loc_dict[location_name]
+
+ # remove vending machines
+ if self.options.vending_machines.value == 0:
+ for location_name, location_data in location_data_table.items():
+ if location_name.startswith("Vending"):
+ del loc_dict[location_name]
+
+ # remove quests
+ if self.options.quest_reward_rando.value == 0:
+ for location_name, location_data in location_data_table.items():
+ if location_name.startswith("Quest"):
+ del loc_dict[location_name]
+
+ # remove generic mob checks
+ if self.options.generic_mob_checks.value == 0:
+ for location_name, location_data in location_data_table.items():
+ if location_name.startswith("Generic"):
+ del loc_dict[location_name]
+
+ # remove rarity checks
+ if self.options.gear_rarity_checks.value != 4:
+ for location_name, location_data in location_data_table.items():
+ if self.options.gear_rarity_checks.value <= 3 and location_name.startswith("Rainbow"):
+ del loc_dict[location_name]
+ elif self.options.gear_rarity_checks.value <= 2 and location_name.startswith("Pearlescent"):
+ del loc_dict[location_name]
+ elif self.options.gear_rarity_checks.value <= 1 and location_name.startswith("Seraph"):
+ del loc_dict[location_name]
+ elif self.options.gear_rarity_checks.value == 0 and location_data.address - bl2_base_id <= 199 and location_data.address - bl2_base_id >= 100:
+ del loc_dict[location_name]
+
+ # remove challenge checks
+ if self.options.challenge_checks.value == 0:
+ for location_name, location_data in location_data_table.items():
+ if location_name.startswith("Challenge"):
+ del loc_dict[location_name]
+
+ # remove chest checks
+ if self.options.chest_checks.value == 0:
+ for location_name, location_data in location_data_table.items():
+ if location_name.startswith("Chest "):
+ del loc_dict[location_name]
+
+ # remove co-op checks
+ if self.options.remove_coop_checks.value != 0:
+ for location_name, location_data in location_data_table.items():
+ v = coop_locations.get(location_name)
+ if v and v <= self.options.remove_coop_checks.value:
+ del loc_dict[location_name]
+
+ # create regions
+ for name, region_data in region_data_table.items():
+ region = Region(name, self.player, self.multiworld)
+ self.multiworld.regions.append(region)
+
+ # connect regions
+ for name, region_data in region_data_table.items():
+ region = self.multiworld.get_region(name, self.player)
+ for c_region_name in region_data.connecting_regions:
+ if c_region_name in self.restricted_regions:
+ continue
+ c_region = self.multiworld.get_region(c_region_name, self.player)
+ exit_name = f"{region.name} to {c_region.name}"
+ # TODO: do you have to (or is it better to) add all the exits in one go?
+ region.add_exits({c_region.name: exit_name})
+
+
+ # add locations to regions
+ for name, addr in loc_dict.items():
+ loc_data = location_data_table[name]
+ region_name = loc_data.region
+ if region_name in self.restricted_regions:
+ continue
+ region = self.multiworld.get_region(region_name, self.player)
+ region.add_locations({name: addr}, Borderlands2Location)
+
+ # setup victory condition (as "event" with None address/code)
+ v_region_name = get_region_from_loc_name(goal_name)
+ victory_region = self.multiworld.get_region(v_region_name, self.player)
+ victory_location = Borderlands2Location(self.player, "Victory Location", None, victory_region)
+ victory_item = Borderlands2Item("Victory: " + goal_name, ItemClassification.progression, None, self.player)
+ victory_location.place_locked_item(victory_item)
+ victory_region.locations.append(victory_location)
+
+ self.multiworld.completion_condition[self.player] = lambda state: (
+ state.has("Victory: " + goal_name, self.player)
+ )
+
+ from Utils import visualize_regions
+ visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
+ print("visualize_regions")
+
+ def get_filler_item_name(self) -> str:
+ return "$100"
+
+ def set_rules(self) -> None:
+ from .Rules import set_rules
+ set_rules(self)
+
+ def fill_slot_data(self):
+ return {
+ "goal": self.goal,
+ "delete_starting_gear": self.options.delete_starting_gear.value,
+ "gear_rarity_item_pool": self.options.gear_rarity_item_pool.value,
+ "receive_gear": self.options.receive_gear.value,
+ "vault_symbols": self.options.vault_symbols.value,
+ "vending_machines": self.options.vending_machines.value,
+ "entrance_locks": self.options.entrance_locks.value,
+ "jump_checks": self.options.jump_checks.value,
+ "max_jump_height": self.options.max_jump_height.value,
+ "sprint_checks": self.options.sprint_checks.value,
+ "max_sprint_speed": self.options.max_sprint_speed.value,
+ "spawn_traps": self.options.spawn_traps.value,
+ "quest_reward_rando": self.options.quest_reward_rando.value,
+ "generic_mob_checks": self.options.generic_mob_checks.value,
+ "gear_rarity_checks": self.options.gear_rarity_checks.value,
+ "challenge_checks": self.options.challenge_checks.value,
+ "chest_checks": self.options.chest_checks.value,
+ "death_link": self.options.death_link.value,
+ "death_link_punishment": self.options.death_link_punishment.value,
+ "death_link_send_mode": self.options.death_link_send_mode.value,
+ }
diff --git a/worlds/borderlands2/archi_defs.py b/worlds/borderlands2/archi_defs.py
new file mode 100644
index 000000000000..ff235adaddca
--- /dev/null
+++ b/worlds/borderlands2/archi_defs.py
@@ -0,0 +1,1909 @@
+# This file is intended to be shared between the Archipelago Client/World and the Game sdkmod.
+# I don't expect to be able to have one source of truth distributed to both, so just keep the two files in sync.
+
+# Note: make sure 0 is not associated with any item/location
+
+level_name_to_id = {
+ # 2 through 30
+ "Level " + str(i) : i for i in range(2, 31)
+}
+
+gear_kind_to_id = {
+ # Gear [100 - 199]
+ "Common Shield": 100,
+ "Uncommon Shield": 101,
+ "Rare Shield": 102,
+ "VeryRare Shield": 103,
+ # "E-Tech Shield": 104,
+ "Legendary Shield": 105,
+ "Seraph Shield": 106,
+ "Rainbow Shield": 107,
+ # "Pearlescent Shield": 108,
+ "Unique Shield": 109,
+
+ "Common GrenadeMod": 110,
+ "Uncommon GrenadeMod": 111,
+ "Rare GrenadeMod": 112,
+ "VeryRare GrenadeMod": 113,
+ # "E-Tech GrenadeMod": 114,
+ "Legendary GrenadeMod": 115,
+ "Seraph GrenadeMod": 116,
+ "Rainbow GrenadeMod": 117,
+ # "Pearlescent GrenadeMod": 118,
+ "Unique GrenadeMod": 119,
+
+ "Common ClassMod": 120,
+ "Uncommon ClassMod": 121,
+ "Rare ClassMod": 122,
+ "VeryRare ClassMod": 123,
+ # "E-Tech ClassMod": 124,
+ "Legendary ClassMod": 125,
+ # "Seraph ClassMod": 126,
+ # "Rainbow ClassMod": 127,
+ # "Pearlescent ClassMod": 128,
+ # "Unique ClassMod": 129,
+
+ "Common Relic": 130,
+ "Uncommon Relic": 131,
+ "Rare Relic": 132,
+ "VeryRare Relic": 133,
+ "E-Tech Relic": 134,
+ # "Legendary Relic": 135,
+ "Seraph Relic": 136,
+ "Rainbow Relic": 137,
+ # "Pearlescent Relic": 138,
+ "Unique Relic": 139,
+
+ "Common Pistol": 140,
+ "Uncommon Pistol": 141,
+ "Rare Pistol": 142,
+ "VeryRare Pistol": 143,
+ "E-Tech Pistol": 144,
+ "Legendary Pistol": 145,
+ "Seraph Pistol": 146,
+ # "Rainbow Pistol": 147,
+ "Pearlescent Pistol": 148,
+ "Unique Pistol": 149,
+
+ "Common Shotgun": 150,
+ "Uncommon Shotgun": 151,
+ "Rare Shotgun": 152,
+ "VeryRare Shotgun": 153,
+ "E-Tech Shotgun": 154,
+ "Legendary Shotgun": 155,
+ "Seraph Shotgun": 156,
+ "Rainbow Shotgun": 157,
+ "Pearlescent Shotgun": 158,
+ "Unique Shotgun": 159,
+
+ "Common SMG": 160,
+ "Uncommon SMG": 161,
+ "Rare SMG": 162,
+ "VeryRare SMG": 163,
+ "E-Tech SMG": 164,
+ "Legendary SMG": 165,
+ "Seraph SMG": 166,
+ "Rainbow SMG": 167,
+ "Pearlescent SMG": 168,
+ "Unique SMG": 169,
+
+ "Common SniperRifle": 170,
+ "Uncommon SniperRifle": 171,
+ "Rare SniperRifle": 172,
+ "VeryRare SniperRifle": 173,
+ "E-Tech SniperRifle": 174,
+ "Legendary SniperRifle": 175,
+ "Seraph SniperRifle": 176,
+ "Rainbow SniperRifle": 177,
+ "Pearlescent SniperRifle": 178,
+ "Unique SniperRifle": 179,
+
+ "Common AssaultRifle": 180,
+ "Uncommon AssaultRifle": 181,
+ "Rare AssaultRifle": 182,
+ "VeryRare AssaultRifle": 183,
+ "E-Tech AssaultRifle": 184,
+ "Legendary AssaultRifle": 185,
+ "Seraph AssaultRifle": 186,
+ "Rainbow AssaultRifle": 187,
+ "Pearlescent AssaultRifle": 188,
+ "Unique AssaultRifle": 189,
+
+ "Common RocketLauncher": 190,
+ "Uncommon RocketLauncher": 191,
+ "Rare RocketLauncher": 192,
+ "VeryRare RocketLauncher": 193,
+ "E-Tech RocketLauncher": 194,
+ "Legendary RocketLauncher": 195,
+ "Seraph RocketLauncher": 196,
+ "Rainbow RocketLauncher": 197,
+ "Pearlescent RocketLauncher": 198,
+ "Unique RocketLauncher": 199,
+}
+
+quest_name_to_id = {
+ # Quest [200 - 486]
+ "Sanctuary: The Road to Sanctuary": 200,
+ "Sanctuary: Plan B": 201,
+ "Frostburn: Hunting the Firehawk": 202,
+ "EndOfTheLine: A Train to Catch": 203,
+ "Fridge: Rising Action": 204,
+ "Highlands: Bright Lights, Flying City": 205,
+ "Badlands: Data Mining": 206,
+ "Lynchwood: Breaking the Bank": 207,
+ "Eridium Blight: Kill Yourself": 208,
+ "Boneyard: This Just In": 209,
+ "WindshearWaste: My First Gun": 210,
+ "HolySpirits: Clan War: Wakey Wakey": 211,
+ "SouthernShelf: Handsome Jack Here!": 212,
+ "SouthernShelf: This Town Ain't Big Enough": 213,
+ "SouthernShelf: Shielded Favors": 214,
+ "SouthernShelfBay: Symbiosis": 215,
+ "Thousand Cuts: Defend Slab Tower": 216,
+ "Highlands: Best Mother's Day Ever": 217,
+ "Fridge: Note for Self-Person": 218,
+ "Opportunity: The Bane": 219,
+ "Dust: The Good, the Bad, and the Mordecai": 220,
+ "Sawtooth Cauldron: The Lost Treasure": 221,
+ "Highlands: Arms Dealing": 222,
+ "Lynchwood: 3:10 to Kaboom": 223,
+ "Eridium Blight: Customer Service": 224,
+ "ThreeHornsValley: Neither Rain nor Sleet nor Skags": 225,
+ "SouthernShelf: Blindsided": 226,
+ "SouthernShelf: Cleaning up the Berg": 227,
+ "SouthernShelf: Best Minion Ever": 228,
+ "Ramparts: A Dam Fine Rescue": 229,
+ "WildlifePreserve: Wildlife Preservation": 230,
+ "Thousand Cuts: The Once and Future Slab": 231,
+ "Opportunity: The Man Who Would Be Jack": 232,
+ "Control Core Angel: Where Angels Fear to Tread": 233,
+ "Control Core Angel: Where Angels Fear to Tread (Part 2)": 234,
+ "Sawtooth Cauldron: Toil and Trouble": 235,
+ "WarriorVault: The Talon of God": 236,
+ "Southpaw: Assassinate the Assassins": 237,
+ "SouthernShelf: Bad Hair Day": 238,
+ "Sanctuary: Bearer of Bad News": 239,
+ "Sanctuary: BFFs": 240,
+ "Frostburn Canyon: Cult Following: Eternal Flame": 241,
+ "Frostburn Canyon: Cult Following: Lighting the Match": 242,
+ "Frostburn Canyon: Cult Following: The Enkindling": 243,
+ "Sanctuary: Claptrap's Secret Stash": 244,
+ "Tundra Express: You Are Cordially Invited: Party Prep": 245,
+ "EndOfTheLine: The Ice Man Cometh": 246,
+ "ThreeHornsDivide: In Memoriam": 247,
+ "Tundra Express: Mighty Morphin'": 248,
+ "Tundra Express: Mine, All Mine": 249,
+ "CausticCaverns: Minecart Mischief": 250,
+ "Sanctuary: The Name Game": 251,
+ "Tundra Express: No Hard Feelings": 252,
+ "ThreeHornsValley: No Vacancy": 253,
+ "CausticCaverns: Perfectly Peaceful": 254,
+ "Sanctuary: Rock, Paper, Genocide: Slag Weapons!": 255,
+ "Sanctuary: Rock, Paper, Genocide: Fire Weapons!": 256,
+ "Sanctuary: Do No Harm": 257,
+ "Tundra Express: The Pretty Good Train Robbery": 258,
+ "Sanctuary: Won't Get Fooled Again": 259,
+ "Eridium Blight: A Real Boy: Face Time": 260,
+ "Eridium Blight: A Real Boy: Clothes Make the Man": 261,
+ "Sanctuary: Claptrap's Birthday Bash!": 262,
+ "Dust: Clan War: Zafords vs. Hodunks": 263,
+ "WildlifePreserve: Animal Rights": 264,
+ "Opportunity: Hell Hath No Fury": 265,
+ "Opportunity: Home Movies": 266,
+ "Opportunity: Statuesque": 267,
+ "Lynchwood: Showdown": 268,
+ "HolySpirits: Clan War: End of the Rainbow": 269,
+ "Highlands: Clan War: Starting the War": 270,
+ "Highlands: The Overlooked: Medicine Man": 271,
+ "Highlands: The Overlooked: Shields Up": 272,
+ "Highlands: The Overlooked: This Is Only a Test": 273,
+ "Thousand Cuts: Poetic License": 274,
+ "Dust: Clan War: First Place": 275,
+ "CausticCaverns: Safe and Sound": 276,
+ "Lynchwood: Animal Rescue: Shelter": 277,
+ "Highlands: Slap-Happy": 278,
+ "Highlands: Stalker of Stalkers": 279,
+ "Terramorphous Peak: You. Will. Die. (Seriously.)": 280,
+ "Dust: Clan War: Trailer Trashing": 281,
+ "Opportunity: Written by the Victor": 282,
+ "Sawtooth Cauldron: Capture the Flags": 283,
+ "Sawtooth Cauldron: The Chosen One": 284,
+ "Fridge: The Cold Shoulder": 285,
+ "Eridium Blight: To Grandmother's House We Go": 286,
+ "Sawtooth Cauldron: The Great Escape": 287,
+ "Lynchwood: Hungry Like the Skag": 288,
+ "Highlands: Hyperion Contract #873": 289,
+ "ThreeHornsValley: Medical Mystery": 290,
+ "ThreeHornsValley: Medical Mystery: X-Com-municate": 291,
+ "Ramparts: Out of Body Experience": 292,
+ "OreChasm: Hyperion Slaughter: Round 1": 293,
+ "OreChasm: Hyperion Slaughter: Round 2": 294,
+ "OreChasm: Hyperion Slaughter: Round 3": 295,
+ "OreChasm: Hyperion Slaughter: Round 4": 296,
+ "OreChasm: Hyperion Slaughter: Round 5": 297,
+ "Fridge: Swallowed Whole": 298,
+ "Dust: Too Close For Missiles": 299,
+ "Badlands: Uncle Teddy": 300,
+ "Badlands: Get to Know Jack": 301,
+ "Tundra Express: You Are Cordially Invited: Tea Party": 302,
+ "Tundra Express: You Are Cordially Invited: RSVP": 303,
+ "Sanctuary: Rock, Paper, Genocide: Corrosive Weapons!": 304,
+ "Sanctuary: Rock, Paper, Genocide: Shock Weapons!": 305,
+ "Lynchwood: Animal Rescue: Medicine": 306,
+ "BloodshotStronghold: Splinter Group": 307,
+ "Thousand Cuts: Shoot This Guy in the Face": 308,
+ "Fink's Slaughterhouse: Bandit Slaughter: Round 1": 309,
+ "Fink's Slaughterhouse: Bandit Slaughter: Round 2": 310,
+ "Fink's Slaughterhouse: Bandit Slaughter: Round 3": 311,
+ "Fink's Slaughterhouse: Bandit Slaughter: Round 4": 312,
+ "Fink's Slaughterhouse: Bandit Slaughter: Round 5": 313,
+ "CreatureSlaughter: Creature Slaughter: Round 1": 314,
+ "CreatureSlaughter: Creature Slaughter: Round 2": 315,
+ "CreatureSlaughter: Creature Slaughter: Round 3": 316,
+ "CreatureSlaughter: Creature Slaughter: Round 4": 317,
+ "CreatureSlaughter: Creature Slaughter: Round 5": 318,
+ "Sawtooth Cauldron: Monster Mash (Part 1)": 319,
+ "Sawtooth Cauldron: Monster Mash (Part 2)": 320,
+
+ "Boneyard: Monster Mash (Part 3)": 321,
+ "Highlands: Torture Chairs": 322,
+ "WildlifePreserve: Doctor's Orders": 323,
+ "Dust: Rakkaholics Anonymous": 324,
+ "Highlands: Hidden Journals": 325,
+ "Dust: Positive Self Image": 326,
+ "Frostburn Canyon: Cult Following: False Idols": 327,
+ "HolySpirits: Clan War: Reach the Dead Drop": 328,
+ "Eridium Blight: A Real Boy: Human": 329,
+ "Lynchwood: Demon Hunter": 330,
+ "Thousand Cuts: Rocko's Modern Strife": 331,
+ "Lynchwood: Animal Rescue: Food": 332,
+ "MercenaryDay: Get Frosty": 333,
+ "GlutGulch: The Hunger Pangs": 334,
+ "MercenaryDay: Special Delivery": 335,
+ "GlutGulch: Grandma Flexington's Story": 336,
+ "GlutGulch: Grandma Flexington's Story: Raid Difficulty": 337,
+ "Backburner: The Dawn of New Pandora": 338,
+ "DahlAbandon: Winging It": 339,
+ "DahlAbandon: Spore Chores": 340,
+ "TheBurrows: A Hard Place": 341,
+ "HeliosFallen: Shooting The Moon": 342,
+ "Mt.Scarab: The Cost of Progress": 343,
+ "FFSBossFight: Paradise Found": 344,
+ "Mt.Scarab: Claptocurrency": 345,
+ "HeliosFallen: BFFFs": 346,
+ "DahlAbandon: Hypocritical Oath": 347,
+ "HeliosFallen: Cadeuceus": 348,
+ "FFSBossFight: My Brittle Pony": 349,
+ "DahlAbandon: The Oddest Couple": 350,
+ "WrithingDeep: A Most Cacophonous Lure": 351,
+ "HeliosFallen: Sirentology": 352,
+ "DahlAbandon: Space Cowboy": 353,
+ "DahlAbandon: The Vaughnguard": 354,
+ "HeliosFallen: The Hunt is Vaughn": 355,
+ "FFSBossFight: Chief Executive Overlord": 356,
+ "Mt.Scarab: Echoes of the Past": 357,
+ "FlamerockRefuge: A Role-Playing Game": 358,
+ "MinesOfAvarice: Dwarven Allies": 359,
+ "Agony: The Amulet": 360,
+ "MinesOfAvarice: The Claptrap's Apprentice": 361,
+ "MinesOfAvarice: The Beard Makes The Man": 362,
+ "MinesOfAvarice: My Kingdom for a Wand": 363,
+ "Immortal Woods: Critical Fail": 364,
+ "Lair of Infinite Agony: My Dead Brother": 365,
+ "Immortal Woods: Lost Souls": 366,
+ "Forest: Ell in Shining Armor": 367,
+ "FlamerockRefuge: Fake Geek Guy": 368,
+ "FlamerockRefuge: Feed Butt Stallion": 369,
+ "HatredsShadow: Loot Ninja": 370,
+ "Immortal Woods: MMORPGFPS": 371,
+ "FlamerockRefuge: Pet Butt Stallion": 372,
+ "Immortal Woods: Denial, Anger, Initiative": 373,
+ "HatredsShadow: A Game of Games": 374,
+ "Lair of Infinite Agony: Post-Crumpocalyptic": 375,
+ "WingedStorm: Raiders of the Last Boss": 376,
+ "FlamerockRefuge: Roll Insight": 377,
+ "Forest: Tree Hugger": 378,
+ "HatredsShadow: Winter is a Bloody Business": 379,
+ "Murderlin's Temple: Magic Slaughter: Round 1": 380,
+ "Murderlin's Temple: Magic Slaughter: Round 2": 381,
+ "Murderlin's Temple: Magic Slaughter: Round 3": 382,
+ "Murderlin's Temple: Magic Slaughter: Round 4": 383,
+ "Murderlin's Temple: Magic Slaughter: Round 5": 384,
+ "Murderlin's Temple: Magic Slaughter: Badass Round": 385,
+ "Murderlin's Temple: The Magic of Childhood": 386,
+ "Murderlin's Temple: Find Murderlin's Temple": 387,
+ "UnassumingDocks: The Sword in The Stoner": 388,
+ "Hallowed Hollow: The Bloody Harvest": 389,
+ "Hallowed Hollow: Trick or Treat": 390,
+ "BadassCrater: Highway To Hell": 391,
+ "TorgueArena: Tier 2 Battle: Appetite for Destruction": 392,
+ "TorgueArena: Tier 3 Battle: Appetite for Destruction": 393,
+ "TorgueArena: Tier 3 Rematch: Appetite for Destruction": 394,
+ "Pyro Pete's Bar: Tier 2 Battle: Bar Room Blitz": 395,
+ "Pyro Pete's Bar: Tier 3 Battle: Bar Room Blitz": 396,
+ "Pyro Pete's Bar: Battle: Bar Room Blitz": 397,
+ "Pyro Pete's Bar: Tier 3 Rematch: Bar Room Blitz": 398,
+ "BadassCrater: Tier 2 Battle: The Death Race": 399,
+ "BadassCrater: Tier 3 Battle: The Death Race": 400,
+ "BadassCrater: Battle: The Death Race": 401,
+ "BadassCrater: Tier 3 Rematch: The Death Race": 402,
+ "Forge: Tier 2 Battle: Twelve O' Clock High": 403,
+ "Forge: Tier 3 Battle: Twelve O' Clock High": 404,
+ "Forge: Battle: Twelve O' Clock High": 405,
+ "Forge: Tier 3 Rematch: Twelve O' Clock High": 406,
+ "Beatdown: Mother-Lover": 407,
+ "Beatdown: Number One Fan": 408,
+ "Forge: Commercial Appeal": 409,
+ "Forge: My Husband the Skag": 410,
+ "Forge: Say That To My Face": 411,
+ "TorgueArena: Welcome To The Jungle": 412,
+ "TorgueArena: Battle: Appetite for Destruction": 413,
+ "Pyro Pete's Bar: Burn, Baby, Burn": 414,
+ "Pyro Pete's Bar: Chop Suey": 415,
+ "BadassBar: A Montage": 416,
+ "SouthernRaceway: Get Your Motor Running": 417,
+ "TorgueArenaRing: Eat Cookies and Crap Thunder": 418,
+ "Forge: Knockin' on Heaven's Door": 419,
+ "Forge: Breaking and Entering": 420,
+ "Forge: Kickstart My Heart": 421,
+ "TorgueArena: Long Way To The Top": 422,
+ "SouthernRaceway: Gas Guzzlers": 423,
+ "SouthernRaceway: Matter Of Taste": 424,
+ "SouthernRaceway: Monster Hunter": 425,
+ "SouthernRaceway: Interview with a Vault Hunter": 426,
+ "BadassBar: Walking the Dog": 427,
+ "Pyro Pete's Bar: Pete the Invincible": 428,
+ "Beatdown: Totally Recall": 429,
+ "SouthernRaceway: Everybody Wants to be Wanted": 430,
+ "DigistructInner: A History of Simulated Violence": 431,
+ "DigiPeakInner: More History of Simulated Violence": 432,
+ "DigiPeak: Dr. T and the Vault Hunters": 433,
+ "WamBamIsland: Fun, Sun, and Guns": 434,
+ "RotgutDistillery: A Match Made on Pandora": 435,
+ "WamBamIsland: Victims of Vault Hunters": 436,
+ "RotgutDistillery: Learning to Love": 437,
+ "Oasis: A Warm Welcome": 438,
+ "Wurmwater: Message in a Bottle 2": 439,
+ "HaytersFolly: Message in a Bottle 3": 440,
+ "Rustyards: Message in a Bottle 4": 441,
+ "MagnysLighthouse: Message In A Bottle 5": 442,
+ "Oasis: My Life For A Sandskiff": 443,
+ "Wurmwater: A Study in Scarlett": 444,
+ "Hayters: Two Easy Pieces": 445,
+ "Rustyards: The Hermit": 446,
+ "Rustyards: Crazy About You": 447,
+ "Washburne: Whoops": 448,
+ "Magnys Lighthouse: Let There Be Light": 449,
+ "LeviathansLair: X Marks The Spot": 450,
+ "Oasis: Burying the Past": 451,
+ "Rustyards: Just Desserts for Desert Deserters": 452,
+ "LeviathansLair: Treasure of the Sands": 453,
+ "Oasis: Fire Water": 454,
+ "Washburne: Freedom of Speech": 455,
+ "Washburne: I Know It When I See It": 456,
+ "Washburne: Faster Than the Speed of Love": 457,
+
+ "Wurmwater: Smells Like Victory": 458,
+ "Oasis: Wingman": 459,
+ "Oasis: Giving Jocko A Leg Up": 460,
+ "Washburne: Don't Copy That Floppy": 461,
+ "Oasis: Message in a Bottle 1": 462,
+ "Washburne: Hyperius the Invincible": 463,
+ "Hayters: Master Gee the Invincible": 464,
+ "Wurmwater: Ye Scurvy Dogs": 465,
+ "Wurmwater: Declaration Against Independents": 466,
+ "Hayters: Grendel": 467,
+ "Oasis: Man's Best Friend": 468,
+ "Rustyards: Catch-A-Ride, and Also Tetanus": 469,
+ "HuntersGrotto: Savage Lands": 470,
+ "HuntersGrotto: Professor Nakayama, I Presume?": 471,
+ "HuntersGrotto: A-Hunting We Will Go": 472,
+ "Terminus: The Fall of Nakayama": 473,
+ "HuntersGrotto: An Acquired Taste": 474,
+ "CandlerakksCrag: Big Feet": 475,
+ "HuntersGrotto: Still Just a Borok in a Cage": 476,
+ "ArdortonStation: The Rakk Dahlia Murder": 477,
+ "HuntersGrotto: Egg on Your Face": 478,
+ "ArdortonStation: Follow The Glow": 479,
+ "ScyllasGrove: Nakayama-rama": 480,
+ "CandlerakksCrag: Now You See It": 481,
+ "ScyllasGrove: Ol' Pukey": 482,
+ "HuntersGrotto: Palling Around": 483,
+ "HuntersGrotto: I Like My Monsters Rare": 484,
+ "ScyllasGrove: Urine, You're Out": 485,
+ "CandlerakksCrag: Voracidous the Invincible": 486,
+}
+
+loc_name_to_id = {
+ # Base Game Enemies [500 - 568]
+ "Enemy WindshearWaste: Knuckle Dragger": 500,
+ "Enemy SouthernShelfBay: Midgemong": 501,
+ "Enemy SouthernShelf: Boom": 502,
+ "Enemy SouthernShelf: Bewm": 503,
+ "Enemy SouthernShelf: Captain Flynt": 504,
+ "Enemy ThreeHornsDivide: Savage Lee": 505,
+ "Enemy ThreeHornsDivide: Boll": 506,
+ "Enemy ThreeHornsValley: Doc Mercy": 507,
+ "Enemy SouthpawSteam&Power: Assassin Oney": 508,
+ "Enemy SouthpawSteam&Power: Assassin Wot": 509,
+ "Enemy SouthpawSteam&Power: Assassin Reeth": 510,
+ "Enemy SouthpawSteam&Power: Assassin Rouf": 511,
+ "Enemy FrostburnCanyon: Scorch": 512,
+ "Enemy FrostburnCanyon: Incinerator Clayton": 513,
+ "Enemy Dust: Gettle": 514,
+ "Enemy Dust: Mobley": 515,
+ "Enemy Dust: Black Queen": 516,
+ "Enemy ThreeHornsValley: Bad Maw": 517,
+ "Enemy BloodshotStronghold: Mad Mike": 518,
+ "Enemy BloodshotRamparts: W4R-D3N": 519,
+ "Enemy Frostburn Canyon: Spycho": 520,
+ "Enemy Dust: McNally": 521,
+ "Enemy Dust: Mick/Tector": 522,
+ # "Enemy Dust: Mick/Tector": 522,
+ "Enemy BloodshotStronghold: Dan": 523,
+ "Enemy BloodshotStronghold: Lee": 524,
+ "Enemy BloodshotStronghold: Mick": 525,
+ "Enemy BloodshotStronghold: Ralph": 526,
+ "Enemy BloodshotStronghold: Flinter": 527,
+ "Enemy TundraExpress: MadameVonBartlesby": 528,
+ "Enemy TundraExpress: Prospector Zeke": 529,
+ "Enemy EndOfTheLine: Wilhelm": 530,
+ "Enemy Fridge: LaneyWhite": 531,
+ "Enemy Fridge: Rakkman": 532,
+ "Enemy Fridge: SmashHead": 533,
+ "Enemy Fridge: Shorty": 534,
+ "Enemy Fridge: Sinkhole": 535,
+ "Enemy Fridge: Bloody": 536,
+ "Enemy Fridge: Crabby": 537,
+ "Enemy Fridge: Creepy": 538,
+ "Enemy Fridge: Dirty": 539,
+ "Enemy Fridge: Greedy": 540,
+ "Enemy Fridge: Sleazy": 541,
+ "Enemy Fridge: Tipsy": 542,
+ "Enemy Highlands: OldSlappy": 543,
+ "Enemy Highlands: GluttonousThresher": 544,
+ "Enemy Highlands: Henry": 545,
+ "Enemy HolySpirits: Bagman": 546,
+ "Enemy CausticCaverns: Blue": 547,
+ "Enemy CausticCaverns: Badass Creeper": 548,
+ "Enemy WildlifePreserve: Tumbaa": 549,
+ "Enemy WildlifePreserve: Pimon": 550,
+ "Enemy WildlifePreserve: SonOfMothrakk": 551,
+ # "Bloodwing": 552,
+ "Enemy ThousandCuts: GOD-liath": 553,
+ "Enemy Lynchwood: DukinosMom": 554,
+ "Enemy Lynchwood: MadDog": 555,
+ "Enemy Lynchwood: SheriffNisha": 556,
+ "Enemy Lynchwood: DeputyWinger": 557,
+ "Enemy Opportunity: ForemanJasper": 558,
+ "Enemy Opportunity: JackBodyDouble": 559,
+ "Enemy Bunker: BNK-3R": 560,
+ "Enemy EridiumBlight: KingMong": 561,
+ "Enemy EridiumBlight: DonkeyMong": 562,
+ "Enemy SawtoothCauldron: Mortar": 563,
+ "Enemy AridNexusBoneyard: HunterHellquist": 564,
+ "Enemy AridNexusBadlands: BoneHead": 565,
+ "Enemy AridNexusBadlands: Saturn": 566,
+ "Enemy VaultOfTheWarrior: Warrior": 567,
+ "Enemy Terramorphous the Invincible": 568,
+
+ # Vault Symbols [601-751]
+ "Symbol Sanctuary: Rooftop": 601,
+ "Symbol Sanctuary: Moxxi's Corner": 602,
+ "Symbol Sanctuary: Trash Corner": 603,
+ "Symbol Sanctuary: Tire Corner": 604,
+ "Symbol Sanctuary: Parkour Door": 605,
+ "Symbol EridiumBlight: Hellsfont Crate": 606,
+ "Symbol EridiumBlight: Stairs Door": 607,
+ "Symbol EridiumBlight: Refund Building": 608,
+ "Symbol Fink's: Column": 609,
+ "Symbol Bunker: Backside": 610,
+ "Symbol WarriorVault: Elevator Bottom": 611,
+ "Symbol SouthernShelfBay: Blackburn Cove Corner": 612,
+ "Symbol SouthernShelfBay: Ice Flows Shipwreck": 613,
+ "Symbol Sawtooth: Refinery Container": 614,
+ "Symbol Sawtooth: Elevator": 615,
+ "Symbol CreatureSlaughter: Crate Ceiling": 616,
+ "Symbol HerosPass: Strut": 617,
+ "Symbol HerosPass: Spawn": 618,
+ "Symbol Fridge: Secret Stash": 619,
+ "Symbol Fridge: Sheetmetal Roof": 620,
+ "Symbol ThreeHornsValley: Slums Wall": 621,
+ "Symbol ThreeHornsValley: Frostsprings Wall": 622,
+ "Symbol ThreeHornsValley: Frostsprings Shack": 623,
+ "Symbol AridNexusBoneyard: Pipe": 624,
+ "Symbol AridNexusBoneyard: Safety": 625,
+ "Symbol WindshearWaste: Claptrap's Closet": 626,
+ "Symbol ThousandCuts: Buzzard Factory": 627,
+ "Symbol ThousandCuts: No Man's Land Shack": 628,
+ "Symbol Lynchwood: Gunslinger Roof": 629,
+ "Symbol Lynchwood: Main Street": 630,
+ "Symbol Highlands: Highlands Tower": 631,
+ "Symbol FriendshipGulag: Gulag Container": 632,
+ "Symbol Opportunity: Construction Site": 633,
+ "Symbol Opportunity: Office Bridge": 634,
+ "Symbol ThreeHornsDivide: Drydocks Corner": 635,
+ "Symbol ThreeHornsDivide: Billboard": 636,
+ "Symbol ThreeHornsDivide: Windbreak Wall": 637,
+ "Symbol Dust: Speedway Wall": 638,
+ "Symbol Dust: Buzzard Pad": 639,
+ "Symbol Dust: Moonshiner Lid": 640,
+ "Symbol CausticCaverns: Gravestone Feature": 641,
+ "Symbol CausticCaverns: Bloody Ladder": 642,
+ "Symbol Bloodshot: Pizza Intercom": 643,
+ "Symbol Bloodshot: Switch Room": 644,
+ "Symbol BloodshotRamparts: Ramp": 645,
+ "Symbol Frostburn: Blisterpus Cave": 646,
+ "Symbol Frostburn: Ashmouth Pit": 647,
+ "Symbol TorgueArenaRing: Arena Wall": 648,
+ "Symbol TorgueArenaRing: Arena Ladder": 649,
+ "Symbol Beatdown: Underpass": 650,
+ "Symbol Beatdown: School Bus": 651,
+ "Symbol Forge: Smack Talker": 652,
+ "Symbol Forge: Lava": 653,
+ "Symbol Forge: Housing": 654,
+ "Symbol SouthernRaceway: Zer0 Poster": 655,
+ "Symbol SouthernRaceway: Maya Poster": 656,
+ "Symbol BadassCrater: Badass Highway": 657,
+ "Symbol BadassCrater: Beatdown Door": 658,
+ "Symbol BadassCrater: Billboard Lower": 659,
+ "Symbol BadassCrater: Billboard Upper": 660,
+ "Symbol HolySpirits: Bathroom": 661,
+ "Symbol HaytersFolly: Hot Springs Crate": 662,
+ "Symbol HaytersFolly: Grendel Exit": 663,
+ "Symbol Oasis: Kronus": 664,
+ "Symbol Oasis: Lair Exit": 665,
+ "Symbol Oasis: Lionel Docks": 666,
+ "Symbol Oasis: Coral Island": 667,
+ "Symbol WashburneRefinery: P3RV-E Lair": 668,
+ "Symbol WashburneRefinery: Forge Array": 669,
+ "Symbol Wurmwater: Rustyard Corridor": 670,
+ "Symbol Wurmwater: Scarlett's Ship": 671,
+ "Symbol Wurmwater: The Dish": 672,
+ "Symbol Rustyards: Maroonie's Clipper": 673,
+ "Symbol Rustyards: Rustyards Fence": 674,
+ "Symbol MagnysLighthouse: House": 675,
+ "Symbol MagnysLighthouse: Lighthouse": 676,
+ "Symbol LeviathansLair: Lair": 677,
+ "Symbol WildlifePreserve: Dockyard Roof": 678,
+ "Symbol WildlifePreserve: Broken Boulevard": 679,
+ "Symbol OreChasm: Elevator": 680,
+ "Symbol CandlerakksCrag: Elite Crossing House": 681,
+ "Symbol CandlerakksCrag: Near Voracidous": 682,
+ "Symbol CandlerakksCrag: Rouge's Lair": 683,
+ "Symbol Terminus: Terminus": 684,
+ "Symbol ArdortonStation: Woundspike's Lair": 685,
+ "Symbol ArdortonStation: Psycho Surgery": 686,
+ "Symbol ArdortonStation: Ardo Farm": 687,
+ "Symbol ArdortonStation: Dribble's Lair": 688,
+ "Symbol ScyllasGrove: Low Gut Rut Wall": 689,
+ "Symbol ScyllasGrove: Urine Wall": 690,
+ "Symbol ScyllasGrove: Upper Village": 691,
+ "Symbol ScyllasGrove: Lower Village": 692,
+ "Symbol SanctuaryHole: Staircase Wall": 693,
+ "Symbol SouthernShelf: Flynt's Ship": 694,
+ "Symbol SouthernShelf: Safehouse": 695,
+ "Symbol Southpaw: Parkour": 696,
+ "Symbol Southpaw: Engine": 697,
+ "Symbol Southpaw: Ladder": 698,
+ "Symbol TerramorphousPeak: Dropdown": 699,
+ "Symbol TerramorphousPeak: Elevator": 700,
+ "Symbol EndOfTheLine: Wilhelm Wall": 701,
+ "Symbol TundraExpress: Fork Shack": 702,
+ "Symbol TundraExpress: Facility Roof": 703,
+ "Symbol Backburner: Zed's": 704,
+ "Symbol Backburner: Entrance": 705,
+ "Symbol HatredsShadow: Sir Boil": 706,
+ "Symbol HatredsShadow: Nefarious Elevator": 707,
+ "Symbol HatredsShadow: Sir Mash": 708,
+ "Symbol DragonKeep: Sacrifice Staircase": 709,
+ "Symbol Forest: Blood Tree Camp": 710,
+ "Symbol Forest: Glenn's Cottage": 711,
+ "Symbol ImmortalWoods: Fields of the Fallen": 712,
+ "Symbol ImmortalWoods: Sacred Ruins Upper": 713,
+ "Symbol ImmortalWoods: Sacred Ruins Lower": 714,
+ "Symbol UnassumingDocks: Temple Shoreline": 715,
+ "Symbol UnassumingDocks: Forlorn Ruins": 716,
+ "Symbol UnassumingDocks: Hamlet Building": 717,
+ "Symbol WingedStorm: The Winged Storm": 718,
+ "Symbol LairOfInfiniteAgony: Platform Corridor": 719,
+ "Symbol LairOfInfiniteAgony: Hall of the Dead": 720,
+ "Symbol LairOfInfiniteAgony: Wailer Drop": 721,
+ "Symbol HeliosFallen: Moonshot Complaint Dept": 722,
+ "Symbol HeliosFallen: The Freightway": 723,
+ "Symbol HeliosFallen: Hellion Hole": 724,
+ "Symbol HeliosFallen: Munitions Loading": 725,
+ "Symbol HeliosFallen: Fire Control Alpha": 726,
+ "Symbol HeliosFallen: The Hold": 727,
+ "Symbol MinesOfAvarice: Ingot Processing": 728,
+ "Symbol MinesOfAvarice: Cube Room": 729,
+ "Symbol MinesOfAvarice: Greedtooth's Lair": 730,
+ "Symbol DahlAbandon: Simon's Perch Climb": 731,
+ "Symbol DahlAbandon: Ride's End": 732,
+ "Symbol DahlAbandon: Redclaw Rise Cliff": 733,
+ "Symbol DahlAbandon: The Veiny Shaft": 734,
+ "Symbol DahlAbandon: Simon's Perch Container": 735,
+ "Symbol MtScarab: Test Candidate Detention": 736,
+ "Symbol MtScarab: 02 Staircase": 737,
+ "Symbol MtScarab: Experimentation Room": 738,
+ "Symbol HuntersGrotto: Arizona's Lair": 739,
+ "Symbol HuntersGrotto: Grotto Lodge": 740,
+ "Symbol HuntersGrotto: Grotto Start": 741,
+ "Symbol HuntersGrotto: Dahl Door": 742,
+ "Symbol WrithingDeep: Elevator Room": 743,
+ "Symbol WrithingDeep: Southern Top": 744,
+ "Symbol WrithingDeep: Northern Top": 745,
+ "Symbol Burrows: Hyperion's Grave": 746,
+ "Symbol Burrows: Burrows Cliff": 747,
+ "Symbol MurderlinsTemple: The Nursery": 748,
+ "Symbol MurderlinsTemple: Below Murderlin": 749,
+ "Symbol FlamerockRefuge: Outskirts": 750,
+ "Symbol FlamerockRefuge: Parkour": 751,
+
+ # Vending Machines [800 - 1100]
+ "Vending Sanctuary Crimson Base: Guns": 800,
+ "Vending Sanctuary Crimson Base: Ammo Dump": 801,
+ "Vending Sanctuary Zed's: Ammo Dump": 802,
+ "Vending Sanctuary Zed's: Zed's Meds 1": 803,
+ "Vending Sanctuary Zed's: Zed's Meds 2": 804,
+ "Vending Sanctuary Marcus's: Zed's Meds": 805,
+ "Vending Sanctuary Marcus's: Ammo Dump": 806,
+ "Vending Sanctuary Marcus's: Marcus": 807,
+ "Vending FriendshipGulag: Zed's Meds": 808,
+ "Vending FriendshipGulag: Ammo Dump": 809,
+ "Vending SouthernShelf Hammerlock's: Guns": 812,
+ "Vending SouthernShelf Liar's: Ammo Dump": 813,
+ "Vending SouthernShelf Liar's: Zed's Meds": 814,
+ "Vending SouthernShelf Safehouse: Zed's Meds": 815,
+ "Vending SouthernShelf Dragon: Zed's Meds": 816,
+ "Vending SouthernShelf Dragon: Ammo Dump": 817,
+ "Vending SouthernShelfBay Spawn: Ammo Dump": 818,
+ "Vending SouthernShelfBay Spawn: Zed's Meds": 819,
+ "Vending SouthernShelfBay Cove: Guns": 820,
+ "Vending SouthernShelfBay Cove: Ammo Dump": 821,
+ "Vending SouthernShelfBay Cove: Zed's Meds": 822,
+ "Vending ThreeHornsDivide Jump: Ammo Dump": 823,
+ "Vending ThreeHornsDivide Jump: Zed's Meds": 824,
+ "Vending ThreeHornsDivide Crossroads: Guns": 825,
+ "Vending ThreeHornsDivide Crossroads: Ammo Dump": 826,
+ "Vending ThreeHornsDivide Crossroads: Zed's Meds": 827,
+ "Vending ThreeHornsValley Motel: Guns": 828,
+ "Vending ThreeHornsValley Motel: Ammo Dump": 829,
+ "Vending ThreeHornsValley Motel: Zed's Meds": 830,
+ "Vending Southpaw: Ammo Dump": 831,
+ "Vending Southpaw: Zed's Meds": 832,
+ "Vending Frostburn Spawn: Ammo Dump": 833,
+ "Vending Frostburn Spawn: Zed's Meds": 834,
+ "Vending Frostburn Firehawk: Ammo Dump": 835,
+ "Vending Frostburn Firehawk: Zed's Meds": 836,
+ "Vending Stronghold: Ammo Dump": 837,
+ "Vending Stronghold: Zed's Meds": 838,
+ "Vending Ramparts: Ammo Dump": 839,
+ "Vending Ramparts: Zed's Meds": 840,
+ "Vending Tundra Spawn: Ammo Dump": 841,
+ "Vending Tundra Spawn: Zed's Meds": 842,
+ "Vending Tundra Farm: Guns": 843,
+ "Vending Tundra Tina: Ammo Dump": 844,
+ "Vending EndOfTheLine: Ammo Dump": 845,
+ "Vending EndOfTheLine: Zed's Meds": 846,
+ "Vending Fridge Spawn: Ammo Dump": 847,
+ "Vending Fridge Spawn: Zed's Meds": 848,
+ "Vending Fridge Elevator: Ammo Dump": 849,
+ "Vending Fridge Elevator: Zed's Meds": 850,
+ "Vending Fink's: Guns": 851,
+ "Vending Fink's: Ammo Dump": 852,
+ "Vending Fink's: Zed's Meds": 853,
+ "Vending Outwash: Ammo Dump": 854,
+ "Vending Outwash: Zed's Meds": 855,
+ "Vending Highlands Bridge: Ammo Dump": 856,
+ "Vending Highlands Bridge: Zed's Meds": 857,
+ "Vending Highlands Overlook: Guns": 858,
+ "Vending Highlands Overlook: Ammo Dump": 859,
+ # "Vending Highlands Overlook: Zed's Meds": 860,
+ # "Vending Extraction Plant: Zed's Meds": 861,
+ # "Vending Hyperion Bridge: Zed's Meds": 862,
+ "Vending HolySpirits: Guns": 863,
+ "Vending HolySpirits: Ammo Dump": 864,
+ "Vending HolySpirits: Zed's Meds": 865,
+ "Vending Dust Ellie's: Zed's Meds": 810,
+ "Vending Dust Ellie's: Ammo Dump": 811,
+ "Vending Dust Speedway: Ammo Dump": 866,
+ "Vending Dust Speedway: Zed's Meds": 867,
+ "Vending Dust Steve: Ammo Dump": 868,
+ "Vending Dust Steve: Zed's Meds": 869,
+ "Vending Dust Dahlwell: Zed's Meds": 870,
+ "Vending Dust Dahlwell: Ammo Dump": 871,
+ "Vending SanctuaryHole: Ammo Dump": 872,
+ "Vending SanctuaryHole: Zed's Meds": 873,
+ "Vending Caverns Spawn: Ammo Dump": 874,
+ "Vending Caverns Spawn: Zed's Meds": 875,
+ "Vending Caverns Shortcut: Guns": 876,
+ "Vending Preserve Spawn: Ammo Dump": 877,
+ "Vending Preserve Spawn: Zed's Meds": 878,
+ "Vending Preserve Halfway: Ammo Dump": 879,
+ "Vending Preserve Halfway: Zed's Meds": 880,
+ "Vending Creature: Ammo Dump": 881,
+ "Vending Creature: Zed's Meds": 882,
+ "Vending Thousand: Ammo Dump": 883,
+ "Vending Thousand: Zed's Meds": 884,
+ "Vending Lynchwood Spawn: Ammo Dump": 885,
+ "Vending Lynchwood Spawn: Zed's Meds": 886,
+ "Vending Lynchwood Gunslinger: Guns": 887,
+ "Vending Opportunity Spawn: Guns": 888,
+ "Vending Opportunity Spawn: Ammo Dump": 889,
+ "Vending Opportunity Spawn: Zed's Meds": 890,
+ "Vending Opportunity Office: Ammo Dump": 891,
+ "Vending Opportunity Office: Zed's Meds": 892,
+ "Vending Opportunity Square: Ammo Dump": 893,
+ "Vending Opportunity Square: Zed's Meds": 894,
+ "Vending Opportunity Residential: Guns": 895,
+ "Vending Opportunity Residential: Ammo Dump": 896,
+ "Vending Bunker: Ammo Dump": 897,
+ "Vending Bunker: Zed's Meds": 898,
+ "Vending Blight Spawn: Ammo Dump": 899,
+ "Vending Blight Spawn: Zed's Meds": 900,
+ "Vending Blight Extraction: Ammo Dump": 901,
+ "Vending Blight Extraction: Zed's Meds": 902,
+ "Vending Blight Refinery: Guns": 903,
+ "Vending Blight Refinery: Ammo Dump": 904,
+ "Vending Blight Refinery: Zed's Meds": 905,
+ "Vending OreChasm: Guns": 906,
+ "Vending OreChasm: Ammo Dump": 907,
+ "Vending OreChasm: Zed's Meds": 908,
+ "Vending Sawtooth Spawn: Ammo Dump": 909,
+ "Vending Sawtooth Spawn: Zed's Meds": 910,
+ "Vending Sawtooth Elevator: Ammo Dump": 911,
+ "Vending Boneyard Spawn: Ammo Dump": 912,
+ "Vending Boneyard Spawn: Zed's Meds": 913,
+ "Vending Badlands Fyrestone: Ammo Dump": 914,
+ "Vending Badlands Fyrestone: Zed's Meds": 915,
+ "Vending Badlands Baha: Guns": 916,
+ "Vending HerosPass Spawn: Ammo Dump": 917,
+ "Vending HerosPass Spawn: Zed's Meds": 918,
+ "Vending Warrior: Ammo Dump": 919,
+ "Vending Warrior: Zed's Meds": 920,
+ "Vending Terramorphous: Guns": 921,
+ "Vending Terramorphous: Ammo Dump": 922,
+ "Vending Terramorphous: Zed's Meds": 923,
+ # "Vending Oasis Town: Seraph Vendor": 924,
+ "Vending Oasis Town: Guns": 925,
+ "Vending Oasis Town: Ammo Dump": 926,
+ "Vending Oasis Town: Zed's Meds": 927,
+ "Vending Wurmwater Oasis Door: Ammo Dump": 928,
+ "Vending Wurmwater Oasis Door: Zed's Meds": 929,
+ "Vending Wurmwater Canyon: Guns": 930,
+ "Vending Wurmwater Canyon: Ammo Dump": 931,
+ "Vending Wurmwater Fen: Ammo Dump": 932,
+ "Vending Wurmwater Ship: Guns": 933,
+ "Vending Wurmwater Ship: Ammo Dump": 934,
+ "Vending Wurmwater Ship: Zed's Meds": 935,
+ "Vending Wurmwater Rustyard: Guns": 936,
+ "Vending Hayters Spawn: Guns": 937,
+ "Vending Hayters Spawn: Ammo Dump": 938,
+ "Vending Hayters Spawn: Zed's Meds": 939,
+ "Vending Hayters Gee: Ammo Dump": 940,
+ "Vending Hayters Gee: Zed's Meds": 941,
+ "Vending Rustyards Spawn: Ammo Dump": 942,
+ "Vending Rustyards Spawn: Zed's Meds": 943,
+ "Vending Washburne Spawn: Ammo Dump": 944,
+ "Vending Washburne Spawn: Zed's Meds": 945,
+ "Vending Washburne Hyperius: Ammo Dump": 946,
+ "Vending Washburne Hyperius: Zed's Meds": 947,
+ "Vending Magnys Spawn: Ammo Dump": 948,
+ "Vending Magnys Spawn: Zed's Meds": 949,
+ "Vending Leviathan Body: Ammo Dump": 950,
+ "Vending Leviathan Body: Zed's Meds": 951,
+ "Vending Leviathan Lair: Ammo Dump": 952,
+ "Vending Leviathan Lair: Zed's Meds": 953,
+ "Vending Crater Spawn: Ammo Dump": 954,
+ "Vending Crater Spawn: Zed's Meds": 955,
+ # "Vending Crater Town: Seraph Vendor": 956,
+ "Vending Crater Town: Ammo Dump": 957,
+ "Vending Crater Town: Zed's Meds": 958,
+ "Vending Crater Forge: Torgue Vendor": 959,
+ "Vending Crater Forge: Ammo Dump": 960,
+ "Vending Crater Forge: Zed's Meds": 961,
+ "Vending Arena Inner: Torgue Vendor": 962,
+ "Vending Arena Inner: Ammo Dump": 963,
+ "Vending Arena Inner: Zed's Meds": 964,
+ "Vending Beatdown Spawn: Torgue Vendor": 965,
+ "Vending Beatdown Spawn: Ammo Dump": 966,
+ "Vending Beatdown Spawn: Zed's Meds": 967,
+ "Vending Beatdown Underpass: Torgue Vendor": 968,
+ "Vending Beatdown Underpass: Ammo Dump": 969,
+ "Vending Beatdown Underpass: Zed's Meds": 970,
+ "Vending Pete's: Torgue Vendor": 971,
+ "Vending Pete's: Ammo Dump": 972,
+ "Vending Pete's: Zed's Meds": 973,
+ "Vending BadassBar: Torgue Vendor": 974,
+ "Vending BadassBar: Ammo Dump": 975,
+ "Vending BadassBar: Zed's Meds": 976,
+ "Vending Raceway: Torgue Vendor": 977,
+ "Vending Raceway: Ammo Dump": 978,
+ "Vending Raceway: Zed's Meds": 979,
+ "Vending Forge Spawn: Torgue Vendor": 980,
+ "Vending Forge Spawn: Ammo Dump": 981,
+ "Vending Forge Spawn: Zed's Meds": 982,
+ "Vending Forge Flyboy: Ammo Dump": 983,
+ "Vending Forge Flyboy: Zed's Meds": 984,
+ # "Vending Grotto Lodge: Seraph Vendor": 985,
+ "Vending Grotto Lodge: Guns": 986,
+ "Vending Grotto Lodge: Ammo Dump": 987,
+ "Vending Grotto Lodge: Zed's Meds": 988,
+ "Vending Scylla's Entrance: Guns": 989,
+ "Vending Scylla's Entrance: Ammo Dump": 990,
+ "Vending Scylla's Entrance: Zed's Meds": 991,
+ "Vending Scylla's Pukey: Zed's Meds": 992,
+ "Vending Ardorton Entrance: Ammo Dump": 993,
+ "Vending Ardorton Entrance: Zed's Meds": 994,
+ "Vending Ardorton Exit: Ammo Dump": 995,
+ "Vending Ardorton Exit: Zed's Meds": 996,
+ "Vending Crag Spawn: Ammo Dump": 997,
+ "Vending Crag Spawn: Zed's Meds": 998,
+ "Vending Crag Voracidous: Ammo Dump": 999,
+ "Vending Crag Voracidous: Zed's Meds": 1000,
+ "Vending Terminus Entrance: Ammo Dump": 1001,
+ "Vending Terminus Entrance: Zed's Meds": 1002,
+ # "Vending FlamerockRefuge: Seraph Vendor": 1003,
+ "Vending FlamerockRefuge: Guns/Cannons": 1004,
+ "Vending FlamerockRefuge: Ammo Dump/Missiles": 1005,
+ "Vending FlamerockRefuge: Zed's Apothecary": 1006,
+ "Vending Forest Spawn: Ammo Dump/Missiles": 1007,
+ "Vending Forest Spawn: Zed's Apothecary": 1008,
+ "Vending Immortal Spawn: Ammo Dump/Missiles": 1009,
+ "Vending Immortal Spawn: Zed's Apothecary": 1010,
+ "Vending Immortal Vitality Grove: Ammo Dump/Missiles": 1011,
+ "Vending Immortal Vitality Grove: Zed's Apothecary": 1012,
+ "Vending Avarice Spawn: Ammo Dump/Missiles": 1013,
+ "Vending Avarice Spawn: Zed's Apothecary": 1014,
+ "Vending Avarice Greedtooth: Ammo Dump/Missiles": 1015,
+ "Vending Avarice Greedtooth: Zed's Apothecary": 1016,
+ "Vending Shadow Spawn: Ammo Dump/Missiles": 1017,
+ "Vending Shadow Spawn: Zed's Apothecary": 1018,
+ "Vending Handsome Bridge: Guns/Cannons": 1019,
+ "Vending Handsome Bridge: Ammo Dump/Missiles": 1020,
+ "Vending Handsome Bridge: Zed's Apothecary": 1021,
+ "Vending Agony Spawn: Ammo Dump/Missiles": 1022,
+ "Vending Agony Spawn: Zed's Apothecary": 1023,
+ "Vending Agony Daughter: Ammo Dump/Missiles": 1024,
+ "Vending Agony Daughter: Zed's Apothecary": 1025,
+ # "Vending Agony Hall of the Dead: Mr. Miz": 1026,
+ "Vending DragonKeep: Ammo Dump/Missiles": 1027,
+ "Vending DragonKeep: Zed's Apothecary": 1028,
+ "Vending WingedStorm: Guns/Cannons": 1029,
+ "Vending WingedStorm: Ammo Dump/Missiles": 1030,
+ "Vending WingedStorm: Zed's Apothecary": 1031,
+ "Vending Murderlin's Temple: Guns/Cannons": 1032,
+ "Vending Murderlin's Temple: Ammo Dump/Missiles": 1033,
+ "Vending Murderlin's Temple: Zed's Apothecary": 1034,
+ "Vending HallowedHollow Entrance: Ammo Dump": 1035,
+ "Vending HallowedHollow Entrance: Zed's Meds": 1036,
+ "Vending HallowedHollow Kingpin Approach: Zed's Meds": 1037,
+ "Vending HallowedHollow Kingpin Approach: Ammo Dump": 1038,
+ "Vending GlutGulch Entrance: Guns": 1039,
+ "Vending GlutGulch Entrance: Ammo Dump": 1040,
+ "Vending GlutGulch Entrance: Zed's Meds": 1041,
+ "Vending GlutGulch Kitchen: Ammo Dump": 1042,
+ "Vending GlutGulch Stabber Jabber: Guns": 1043,
+ "Vending GlutGulch Tribute Station: Guns": 1044,
+ "Vending GlutGulch Tribute Station: Zed's Meds": 1045,
+ "Vending GlutGulch Tribute Station: Ammo Dump": 1046,
+ "Vending MercenaryDay Entrance: Guns": 1047,
+ "Vending MercenaryDay Entrance: Ammo Dump": 1048,
+ "Vending MercenaryDay Entrance: Zed's Meds": 1049,
+ "Vending MercenaryDay Tinder Arena: Zed's Meds": 1050,
+ "Vending MercenaryDay Tinder Arena: Ammo Dump": 1051,
+ "Vending Distillery Entrance: Zed's Meds": 1052,
+ "Vending Distillery Entrance: Ammo Dump": 1053,
+ "Vending Distillery Pot of Gold: Ammo Dump": 1054,
+ "Vending Distillery Wedding Venue: Zed's Meds": 1055,
+ "Vending Distillery Wedding Venue: Ammo Dump": 1056,
+ "Vending WamBam Entrance: Ammo Dump": 1057,
+ "Vending WamBam Entrance: Zed's Meds": 1058,
+ "Vending WamBam Water Wheel: Zed's Meds": 1059,
+ "Vending WamBam Water Wheel: Ammo Dump": 1060,
+ "Vending WamBam Son's Arena: Ammo Dump": 1061,
+ "Vending WamBam Son's Arena: Zed's Meds": 1062,
+ "Vending Backburner Entrance: Ammo Dump": 1063,
+ "Vending Backburner Entrance: Zed's Meds": 1064,
+ "Vending Backburner Vaughn's HQ: Guns": 1065,
+ "Vending Backburner Vaughn's HQ: Ammo Dump": 1066,
+ "Vending Backburner Vaughn's HQ: Zed's Meds": 1067,
+ "Vending Backburner Marcus: Guns": 1068,
+ "Vending Backburner Marcus: Ammo Dump": 1069,
+ "Vending DahlAbandon Entrance: Ammo dump": 1070,
+ "Vending DahlAbandon Entrance: Zed's Meds": 1071,
+ "Vending DahlAbandon Mine Door: Ammo Dump": 1072,
+ "Vending DahlAbandon Mine Door: Zed's Meds": 1073,
+ "Vending Burrows Entrance: Ammo Dump": 1074,
+ "Vending Burrows Entrance: Zed's Meds": 1075,
+ "Vending HeliosFallen Entrance: Ammo Dump": 1076,
+ "Vending HeliosFallen Entrance: Zed's Meds": 1077,
+ "Vending HeliosFallen Munitions Loading: Ammo Dump": 1078,
+ "Vending HeliosFallen Munitions Loading: Zed's Meds": 1079,
+ "Vending HeliosFallen The Arsenal: Ammo Dump": 1080,
+ "Vending HeliosFallen The Arsenal: Zed's Meds": 1081,
+ "Vending Mt.Scarab Entrance: Ammo Dump": 1082,
+ "Vending Mt.Scarab Entrance: Zed's Meds": 1083,
+ "Vending Mt.Scarab Observation Deck: Ammo Dump": 1084,
+ "Vending Mt.Scarab Observation Deck: Zed's Meds": 1085,
+ # "Vending Paradise Sanctum Entrance: Ammo Dump": 1087,
+ # "Vending Paradise Sanctum Entrance: Zed's Meds": 1088,
+ # "Vending Paradise Sanctum Marcus Munitions: Guns": 1089,
+ # "Vending Paradise Sanctum Marcus Munitions: Zed's Meds": 1089,
+ # "Vending Paradise Sanctum Hector: Ammo Dump": 1090,
+ # "Vending Paradise Sanctum Hector: Zed's Meds": 1091,
+ "Vending WrithingDeep: Ammo Dump": 1092,
+ "Vending WrithingDeep: Zed's Meds": 1093,
+ "Vending Digistruct Spawn: Guns": 1094,
+ "Vending Digistruct Spawn: Ammo Dump": 1095,
+ "Vending Digistruct Spawn: Zed's Meds": 1096,
+ "Vending DigistructInner Butcher Base: Ammo Dump": 1097,
+ # "Vending DigistructInner Area of Anguish: Ammo Dump": 1098,
+ # "Vending DigistructInner Area of Anguish: Zed's Meds": 1099,
+ # "Vending DigistructInner Tower of Torment: Ammo Dump": 1100,
+
+ # Generic Mobs [1150 - 1163]
+ "Generic: Skag": 1150,
+ "Generic: Rakk": 1151,
+ "Generic: Bullymong": 1152,
+ "Generic: Psycho": 1153,
+ "Generic: Rat": 1154,
+ "Generic: Spiderant": 1155,
+ "Generic: Varkid": 1156,
+ "Generic: Goliath": 1157,
+ "Generic: Marauder": 1158,
+ "Generic: Stalker": 1159,
+ "Generic: Midget": 1160,
+ "Generic: Nomad": 1161,
+ "Generic: Thresher": 1162,
+ "Generic: Badass": 1163,
+
+ # DLC Enemies [1200 - 1324]
+ "Enemy HallowedHollow: Sully the Blacksmith": 1200,
+ "Enemy HallowedHollow: Pumpkin Kingpin": 1201,
+ "Enemy HallowedHollow: Cryptkeeper Clark": 1202,
+ "Enemy GluttonyGulch: Chef Gouda Ramsay": 1203,
+ "Enemy GluttonyGulch: The Rat in the Hat": 1204,
+ "Enemy GluttonyGulch: Chef Brulee": 1205,
+ "Enemy GluttonyGulch: Chef Bork Bork": 1206,
+ "Enemy GluttonyGulch: Wattle Gobbler": 1207,
+ "Enemy GluttonyGulch: Glasspool, Tribute of Wurmwater": 1208,
+ "Enemy GluttonyGulch: William, Tribute of Wurmwater": 1209,
+ "Enemy GluttonyGulch: Axel, Tribute of Opportunity": 1210,
+ "Enemy GluttonyGulch: Rose, Tribute of Opportunity": 1211,
+ "Enemy GluttonyGulch: Strip, Tribute of Southern Shelf": 1212,
+ "Enemy GluttonyGulch: Flay, Tribute of Southern Shelf": 1213,
+ "Enemy GluttonyGulch: Bailly, Tribute of Sawtooth Cauldron": 1214,
+ "Enemy GluttonyGulch: Moretus, Tribute of Sawtooth Cauldron": 1215,
+ "Enemy GluttonyGulch: Cynder, Tribute of Frostburn": 1216,
+ "Enemy GluttonyGulch: Fuse, Tribute of Frostburn": 1217,
+ "Enemy GluttonyGulch: Annie, Tribute of Lunchwood": 1218,
+ "Enemy GluttonyGulch: Garret, Tribute of Lynchwood": 1219,
+ "Enemy GluttonyGulch: Fiona, Tribute of Sanctuary": 1220,
+ "Enemy GluttonyGulch: Max, Tribute of Sanctuary": 1221,
+ "Enemy MercenaryDay: Tinder Snowflake": 1222,
+ "Enemy RotgutDistillery: Colin Zaford": 1223,
+ "Enemy RotgutDistillery: Bridget Hodunk": 1224,
+ "Enemy RotgutDistillery: BLNG Loader": 1225,
+ "Enemy RotgutDistillery: Stella": 1226,
+ "Enemy RotgutDistillery: Ed": 1227,
+ "Enemy RotgutDistillery: Innuendobot 5000": 1228,
+ "Enemy RotgutDistillery: Sigmand": 1229,
+ "Enemy RotgutDistillery: Ikaroa": 1230,
+ "Enemy RotgutDistillery: Moby": 1231,
+ "Enemy RotgutDistillery: Fire Crak'n": 1232,
+ "Enemy RotgutDistillery: Rue, the Love Thresher": 1233,
+ "Enemy WamBamIsland: Son of Crawmerax": 1234,
+ "Enemy WamBamIsland: Sparky Flynt": 1235,
+ "Enemy Oasis: No Beard": 1236,
+ "Enemy Oasis: Tinkles": 1237,
+ "Enemy Hayter's Folly: Grendel": 1238,
+ "Enemy Hayter's Folly: Sandman": 1239,
+ "Enemy Hayter's Folly: Big Sleep": 1240,
+ "Enemy Oasis: Benny the Booster": 1241,
+ "Enemy Hayter's Folly: Deckhand": 1242,
+ "Enemy Rustyards: Toothless Terry": 1243,
+ "Enemy Washburne Refinery: P3RV-E": 1244,
+ "Enemy Washburne Refinery: H3RL-E": 1245,
+ "Enemy Magnys Lighthouse: DJ Tanner": 1246,
+ "Enemy Magnys Lighthouse: Mr. Bubbles": 1247,
+ "Enemy Magnys Lighthouse: Lt. Hoffman": 1248,
+ "Enemy Magnys Lighthouse: Lt. White": 1249,
+ "Enemy LeviathansLair: Roscoe": 1250,
+ "Enemy LeviathansLair: The Leviathan": 1251,
+ "Enemy Washburne Refinery: Hyperius": 1252,
+ "Enemy Hayter's Folly: Master Gee": 1253,
+ "Enemy PyroPetesBar: Pyro Pete": 1254,
+ "Enemy Beatdown: Sully the Stabber": 1255,
+ "Enemy Beatdown: Hamhock": 1256,
+ "Enemy SouthernRaceway: Buff Gamer": 1257,
+ "Enemy SouthernRaceway: Game Critic": 1258,
+ "Enemy SouthernRaceway: Motor Mama": 1259,
+ "Enemy Forge: Anonymous Troll": 1260,
+ "Enemy Forge: Uriah": 1261,
+ "Enemy Forge: Piston's Blimp": 1262,
+ "Enemy TorgueArena: Badassasaurus": 1263,
+ "Enemy TorgueArena: Piston": 1264,
+ "Enemy Pyro Pete's Bar: Pyro Pete the Invincible": 1265,
+ "Enemy HuntersGrotto: Arizona": 1266,
+ "Enemy ScyllasGrove: Thermitage": 1267,
+ "Enemy ScyllasGrove: Rakkanoth": 1268,
+ "Enemy Ardorton Station: Dribbles:": 1269,
+ "Enemy Ardorton Station: Woundspike": 1270,
+ "Enemy HuntersGrotto: The Bulwark": 1271,
+ "Enemy CandlerakksCrag: Rouge": 1272,
+ "Enemy CandlerakksCrag: Bloodtail": 1273,
+ "Enemy Terminus: Jackenstein": 1274,
+ "Enemy Terminus: Dr. Nakayama": 1275,
+ "Enemy HuntersGrotto: Bulstoss": 1276,
+ "Enemy UnassumingDocks: Mr. Boney Pants Guy": 1277,
+ "Enemy UnassumingDocks: Unmotivated Golem": 1278,
+ "Enemy FlamerockRefuge: Sir Reginald von Bartlesby": 1279,
+ "Enemy Forest: Spiderpants": 1280,
+ "Enemy Forest: Arguk the Butcher": 1281,
+ "Enemy Forest: Warlord Glug": 1282,
+ "Enemy Immortal Woods: -=n00bkiller=-": 1283,
+ "Enemy Immortal Woods: 420_E-Sports_Masta": 1284,
+ "Enemy Immortal Woods: [720NoScope]Headshotz": 1285,
+ "Enemy Immortal Woods: xxDatVaultHuntrxx": 1286,
+ "Enemy Immortal Woods: King Aliah": 1287,
+ "Enemy Immortal Woods: King Crono": 1288,
+ "Enemy Immortal Woods: King Seth": 1289,
+ "Enemy Immortal Woods: King Nazar": 1290,
+ "Enemy MinesOfAvarice: Maxibillion": 1291,
+ "Enemy MinesOfAvarice: Magical Orc": 1292,
+ "Enemy MinesOfAvarice: Magical Spider": 1293,
+ "Enemy MinesOfAvarice: Greedtooth": 1294,
+ "Enemy HatredsShadow: Canine": 1295,
+ "Enemy HatredsShadow: Molehill": 1296,
+ "Enemy HatredsShadow: Sir Boil": 1297,
+ "Enemy HatredsShadow: Sir Mash": 1298,
+ "Enemy HatredsShadow: Sir Stew": 1299,
+ "Enemy HatredsShadow: Handsome Dragon": 1300,
+ "Enemy LairOfInfiniteAgony: Sorcerer's Daughter": 1301,
+ "Enemy LairOfInfiniteAgony: Edgar/Simon": 1302,
+ # "Enemy LairOfInfiniteAgony: Edgar/Simon": 1302,
+ "Enemy DragonKeep: Handsome Sorcerer": 1303,
+ "Enemy WingedStorm: Incinerator": 1304,
+ "Enemy WingedStorm: Healianth": 1305,
+ "Enemy WingedStorm: Brood": 1306,
+ "Enemy WingedStorm: Boost": 1307,
+ "Enemy MinesOfAvarice: Warlord Turge": 1308,
+ "Enemy MurderlinsTemple: Warlord Slog": 1309,
+ "Enemy Burrows: Ghost": 1310,
+ "Enemy DahlAbandon: The Dark Web": 1311,
+ "Enemy HeliosFallen: Uranus Bot": 1312,
+ "Enemy Burrows: Lt. Angvar": 1313,
+ "Enemy DahlAbandon: Lt. Bolson": 1314,
+ "Enemy HeliosFallen: Lt. Tetra": 1315,
+ "Enemy MtScarab: Lt. Hoffman": 1316,
+ "Enemy MtScarab: Cassius": 1317,
+ "Enemy Paradise Sanctum: Hector": 1318,
+ "Enemy HuntersGrotto: Dexiduous the Invincible": 1319,
+ "Enemy HuntersGrotto: Omnd-Omnd-Ohk": 1320,
+ # "Enemy HuntersGrotto: Omnd-Omnd-Ohk": 1320,
+ "Enemy CandlerakksCrag: Chief Ngwatu": 1321,
+ "Enemy CandlerakksCrag: Voracidous the Invincible": 1322,
+ "Enemy WrithingDeep: Haderax the Invincible": 1323,
+ "Enemy TundraExpress: Vermivorous the Invincible": 1324,
+
+ # BAR Challenges [1400 - 1764]
+ "Challenge LilithDLC: Do It For The Vine": 1400,
+ "Challenge LilithDLC: Fertile Ground": 1401,
+ "Challenge LilithDLC: Flower Power": 1402,
+ "Challenge LilithDLC: Haderax The Invincible": 1403,
+ "Challenge LilithDLC: Let It Grow": 1404,
+ "Challenge LilithDLC: Tanks But No Tanks": 1405,
+ "Challenge LilithDLC: You Started It": 1406,
+ "Challenge Backburner: Make Yourself At Home": 1407,
+ "Challenge Backburner: Fandir Fiction": 1408,
+ "Challenge Backburner: Harmville": 1409,
+ "Challenge Backburner: The Burdens of Creation": 1410,
+ "Challenge DahlAbandon: Abandoned Records": 1411,
+ "Challenge DahlAbandon: The Path to Paradise": 1412,
+ "Challenge HeliosFallen: Butt Stomped": 1413,
+ "Challenge HeliosFallen: Corporate Droning": 1414,
+ "Challenge HeliosFallen: Serve, Spike, Slaughter": 1415,
+ "Challenge HeliosFallen: Cult of the Vault": 1416,
+ "Challenge MtScarab: Soliloquy Scientific": 1417,
+ "Challenge DahlAbandon: Mine, Dahl Mine!": 1418,
+ "Challenge DahlAbandon: Hold The Door!": 1419,
+ "Challenge DahlAbandon: Out of Scope": 1420,
+ "Challenge DahlAbandon: Cult of the Vault": 1421,
+ "Challenge MtScarab: For Science!": 1422,
+ "Challenge MtScarab: Gaseous Cassius": 1423,
+ "Challenge MtScarab: No Peeking": 1424,
+ "Challenge MtScarab: Cult of the Vault": 1425,
+ "Challenge Burrows: Dangers Deified": 1426,
+ "Challenge Burrows: Grrang'laarg'll (Queen's Treasure)": 1427,
+ "Challenge Burrows: Never Enough Tools": 1428,
+ "Challenge Burrows: Timber!": 1429,
+ "Challenge Burrows: Cult of the Vault": 1430,
+ "Challenge WrithingDeep: Dune Raider": 1431,
+ "Challenge WrithingDeep: Cult of the Vault": 1432,
+ "Challenge Backburner: Cult of the Vault": 1433,
+ "Challenge TinaDLC: Fus Roh Die": 1434,
+ "Challenge TinaDLC: Scot-Free": 1435,
+ "Challenge TinaDLC: Rock Out With Your Rock Out": 1436,
+ "Challenge TinaDLC: Knighty Knight": 1437,
+ "Challenge TinaDLC: Can't Fool Me": 1438,
+ "Challenge TinaDLC: Orcs Should Perish": 1439,
+ "Challenge TinaDLC: Bone Breaker": 1440,
+ "Challenge TinaDLC: Ew Ew Ew Ew": 1441,
+ "Challenge TinaDLC: Cheerful Green Giants": 1442,
+ "Challenge TinaDLC: Magical Massacre": 1443,
+ "Challenge HatredsShadow: Cult of the Vault": 1444,
+ "Challenge DragonKeep: Blood for Blood": 1445,
+ "Challenge DragonKeep: CastleKeep Cult of the Vault": 1446,
+ "Challenge Forest: SO. CUTE.": 1447,
+ "Challenge Forest: Extreme Orc Makeover": 1448,
+ "Challenge Forest: Cult of the Vault": 1449,
+ "Challenge ImmortalWoods: Cult of the Vault": 1450,
+ "Challenge UnassumingDocks: The Taste of War": 1451,
+ "Challenge UnassumingDocks: Docks Cult of the Vault": 1452,
+ "Challenge WingedStorm: Cult of the Vault": 1453,
+ "Challenge LairOfInfiniteAgony: A Single Deadly Bite": 1454,
+ "Challenge LairOfInfiniteAgony: Cult of the Vault": 1455,
+ "Challenge MinesOfAvarice: The Floor is Lava": 1456,
+ "Challenge MinesOfAvarice: Cult of the Vault": 1457,
+ "Challenge Murderlin's: Work! Work! Work!": 1458,
+ "Challenge Murderlin's: Cult of the Vault": 1459,
+ "Challenge Flamerock: Smashin' Pumpkins": 1460,
+ "Challenge Flamerock: Cult of the Vault": 1461,
+ "Challenge Money: Whaddaya Buyin'?": 1463,
+ "Challenge Money: For the Hoard!": 1464,
+ "Challenge Money: Limited-Time Offer": 1465,
+ "Challenge Money: Wholesale": 1466,
+ "Challenge Money: Dolla Dolla Bills, Y'all": 1467,
+ "Challenge Money: Psst, Hey Buddy...": 1468,
+ "Challenge Combat: Not Full of Monkeys": 1469,
+ "Challenge Combat: Knee-Deep in Brass": 1470,
+ "Challenge Combat: Afternoon Delight": 1471,
+ "Challenge Combat: ...I Got to Boogie": 1472,
+ "Challenge Combat: Critical Acclaim": 1473,
+ "Challenge Combat: ...To Pay the Bills": 1474,
+ "Challenge Combat: Gun Slinger": 1475,
+ "Challenge Combat: Boomerbang": 1476,
+ "Challenge Grenade: Pull the Pin": 1477,
+ "Challenge Grenade: Chemical Sprayer": 1478,
+ "Challenge Grenade: Woah, Black Betty": 1479,
+ "Challenge Grenade: EXPLOOOOOSIONS!": 1480,
+ "Challenge Grenade: Singled Out": 1481,
+ "Challenge Grenade: Health Vampire": 1482,
+ "Challenge EridiumBlight: Can You Hear Me Now": 1483,
+ "Challenge EridiumBlight: Bring Out the Big Guns": 1484,
+ "Challenge EridiumBlight: Tie Clip": 1485,
+ "Challenge EridiumBlight: 'Twas Slag Killed the Beast": 1486,
+ "Challenge EridiumBlight: Pipe Dreaming": 1487,
+ "Challenge EridiumBlight: Save the Turrets": 1488,
+ "Challenge EridiumBlight: Cult of the Vault": 1489,
+ "Challenge EridiumBlight: Vault Hunter vs. The Volcano": 1490,
+ "Challenge Fink's: BanditSlaughter Cult of the Vault": 1491,
+ "Challenge Bunker: BNK-3R Buster": 1492,
+ "Challenge Bunker: Cult of the Vault": 1493,
+ "Challenge VaultOfTheWarrior: The Warrior's Way": 1494,
+ "Challenge VaultOfTheWarrior: Cult of the Vault": 1495,
+ "Challenge VaultOfTheWarrior: Dying of the Light": 1496,
+ "Challenge CausticCaverns: Harchek's Revenge": 1497,
+ "Challenge CausticCaverns: Ever Blow Bubbles...?": 1498,
+ "Challenge CausticCaverns: I Bet I Can Make It": 1499,
+ "Challenge CausticCaverns: Can't Feel A Thing": 1500,
+ "Challenge CausticCaverns: Cult of the Vault": 1501,
+ "Challenge SouthernShelfBay: I Can See Your Corpse from Here!": 1502,
+ "Challenge SouthernShelfBay: Vault Hunter on Wire": 1503,
+ "Challenge SouthernShelfBay: Cult of the Vault": 1504,
+ "Challenge SouthernShelfBay: Midgemong Has No Friends": 1505,
+ "Challenge Sawtooth: Happy Camper": 1506,
+ "Challenge Sawtooth: Race to the Top": 1507,
+ "Challenge Sawtooth: Cult of the Vault": 1508,
+ "Challenge NaturalSelectionAnnex: Cult of the Vault": 1509,
+ "Challenge BloodshotRamparts: The Last Place You Look": 1510,
+ "Challenge BloodshotRamparts: The Not-So-Phantom Tollbooth": 1511,
+ "Challenge BloodshotRamparts: Cult of the Vault": 1512,
+ "Challenge BloodshotStronghold: Eff Yo' Couch": 1513,
+ "Challenge BloodshotStronghold: Cut 'Em No Slack": 1514,
+ "Challenge BloodshotStronghold: Cult of the Vault": 1515,
+ "Challenge BloodshotStronghold: Give It a Whirl": 1516,
+ "Challenge HerosPass: When Nature Calls": 1517,
+ "Challenge HerosPass: Environmental Protection": 1518,
+ "Challenge HerosPass: Cult of the Vault": 1519,
+ "Challenge Fridge: Stiff Competition": 1520,
+ "Challenge Fridge: Trapped Rat": 1521,
+ "Challenge Fridge: Fairest of Them All": 1522,
+ "Challenge Fridge: It's Off to Hell We Go": 1523,
+ "Challenge Fridge: Cult of the Vault": 1524,
+ "Challenge ThreeHornsValley: This Ain't My First Rodeo": 1525,
+ "Challenge ThreeHornsValley: Cult of the Vault": 1526,
+ "Challenge AridNexusBoneyard: Boned": 1527,
+ "Challenge AridNexusBoneyard: Dead Air": 1528,
+ "Challenge AridNexusBoneyard: Let's Start at the Very Beginning": 1529,
+ "Challenge AridNexusBoneyard: Hey! Over Here!": 1530,
+ "Challenge AridNexusBoneyard: Under Pressure": 1531,
+ "Challenge AridNexusBoneyard: Cult of the Vault": 1532,
+ "Challenge WindshearWaste: Cult of the Vault": 1533,
+ "Challenge ThousandCuts: Slab UHF": 1534,
+ "Challenge ThousandCuts: A Grave Matter": 1535,
+ "Challenge ThousandCuts: Cult of the Vault": 1536,
+ "Challenge Highlands: Winds of the Highlands": 1537,
+ "Challenge Highlands: Failure to Communicate": 1538,
+ "Challenge Highlands: Cult of the Vault": 1539,
+ "Challenge Opportunity: Shame the Foreman": 1540,
+ "Challenge Opportunity: Top o' the World": 1541,
+ "Challenge Opportunity: Down With Big, Handsome Brother": 1542,
+ "Challenge Opportunity: Cult of the Vault": 1543,
+ "Challenge FriendshipGulag: In the Middle of the Night": 1544,
+ "Challenge FriendshipGulag: Cult of the Vault": 1545,
+ "Challenge OreChasm: Cult of the Vault": 1546,
+ "Challenge Frostburn: Totems of Fire": 1547,
+ "Challenge Frostburn: Praise Be to Scorch": 1548,
+ "Challenge Frostburn: IceCanyon Cult of the Vault": 1549,
+ "Challenge ThreeHornsDivide: Man's Best Friends": 1550,
+ "Challenge ThreeHornsDivide: Ice Cult of the Vault": 1551,
+ "Challenge Dust: I've Got a Crush on You": 1552,
+ "Challenge Dust: The Van Is Damned": 1553,
+ "Challenge Dust: If You're Not First, You're Last": 1554,
+ "Challenge Dust: Cult of the Vault": 1555,
+ "Challenge HolySpirits: Luckys Cult of the Vault": 1556,
+ "Challenge Lynchwood: Duel of Death": 1557,
+ "Challenge Lynchwood: That's Impossible, Even for a Computer": 1558,
+ "Challenge Lynchwood: Look Ma, No Hands": 1559,
+ "Challenge Lynchwood: Last Train to Nowhere": 1560,
+ "Challenge Lynchwood: Cult of the Vault": 1561,
+ "Challenge WildlifePreserve: Mordy's Secret Stashes": 1562,
+ "Challenge WildlifePreserve: Bear Minimum": 1563,
+ "Challenge WildlifePreserve: Ride Together, Die Together": 1564,
+ "Challenge WildlifePreserve: Cult of the Vault": 1565,
+ "Challenge SanctuaryHole: Down the Rabbit Hole": 1566,
+ "Challenge SanctuaryHole: Sugar Shack": 1567,
+ "Challenge SanctuaryHole: Cult of the Vault": 1568,
+ "Challenge Sanctuary: Jackpot!": 1569,
+ "Challenge Sanctuary: Cult of the Vault": 1570,
+ "Challenge Southpaw: Boss Run": 1571,
+ "Challenge Southpaw: Cult of the Vault": 1572,
+ "Challenge SouthernShelf: Make Boom Go Boom": 1573,
+ "Challenge SouthernShelf: Fireproof": 1574,
+ "Challenge SouthernShelf: Cult of the Vault": 1575,
+ "Challenge TerramorphousPeak: Terramorphous the Not-So-Invincible": 1576,
+ "Challenge TerramorphousPeak: Cult of the Vault": 1577,
+ "Challenge EndOfTheLine: Cult of the Vault": 1578,
+ "Challenge EndOfTheLine: Master of All He Surveys": 1579,
+ "Challenge TundraExpress: Bug Off": 1580,
+ "Challenge TundraExpress: What's Yours Is Mine": 1581,
+ "Challenge TundraExpress: Positive Reinforcements": 1582,
+ "Challenge TundraExpress: King of the Buzzard World": 1583,
+ "Challenge TundraExpress: Cult of the Vault": 1584,
+ "Challenge AridNexusBadlands: The Beginning of the End": 1585,
+ "Challenge Lynchwood: Do or Die": 1586,
+ "Challenge BloodshotStronghold: Bloodshot Memories": 1587,
+ "Challenge Dust: Dust to Dust": 1588,
+ "Challenge Fridge: The Rakk Knight": 1589,
+ "Challenge Frostburn: Burning Sensation": 1590,
+ "Challenge WildlifePreserve: Siren's Song": 1591,
+ "Challenge Opportunity: Opportunity Knocks": 1592,
+ "Challenge ThousandCuts: Portrait of the Gunzerker as a Young Man": 1593,
+ "Challenge Sanctuary: Rise of the Crimson Raiders": 1594,
+ "Challenge Sawtooth: Haiku Hitman": 1595,
+ "Challenge Loot: The Call of Booty": 1596,
+ "Challenge Loot: Open Pandora's Boxes": 1597,
+ "Challenge Loot: Gun Runner": 1598,
+ "Challenge Melee: Fisticuffs!": 1599,
+ "Challenge Melee: A Squall of Violence": 1600,
+ "Challenge Misc: Yo Dawg, I Heard You Like Challenges": 1601,
+ "Challenge Misc: JEEEEENKINSSSSSS!!!": 1602,
+ "Challenge Misc: Compl33tionist": 1603,
+ "Challenge Misc: Sidejacked": 1604,
+ "Challenge Misc: Haters Gonna Hate": 1462,
+ "Challenge Loot: I Like My Treasure Rare": 1605,
+ "Challenge Loot: It's Not Easy Looting Green": 1606,
+ "Challenge Loot: Nothing Rhymes with Orange": 1607,
+ "Challenge Loot: Purple Reign": 1608,
+ "Challenge Loot: Another Man's Treasure": 1609,
+ "Challenge Recovery: This Is No Time for Lazy!": 1610,
+ "Challenge Recovery: Heal Plz": 1611,
+ "Challenge Recovery: I'll Just Help Myself": 1612,
+ "Challenge Recovery: Badass Bingo": 1613,
+ "Challenge Recovery: Green Meanie": 1614,
+ "Challenge Recovery: Death, Wind, and Fire": 1615,
+ "Challenge Recovery: I'm Back! Shocked?": 1616,
+ "Challenge Shields: Ammo Eater": 1617,
+ "Challenge Shields: Amp It Up": 1618,
+ "Challenge Shields: Super Novas": 1619,
+ "Challenge Shields: Roid Rage": 1620,
+ "Challenge Shields: Game of Thorns": 1621,
+ "Challenge Vehicles: Blue Sparks": 1622,
+ "Challenge Vehicles: Hit-and-Fun": 1623,
+ "Challenge Vehicles: Passive Aggressive": 1624,
+ "Challenge Vehicles: Turret Syndrome": 1625,
+ "Challenge Vehicles: One Van Leaves": 1626,
+ "Challenge AssaultRifle: ...This Is My Gun": 1627,
+ "Challenge AssaultRifle: This Is My Rifle...": 1628,
+ "Challenge AssaultRifle: Aggravated Assault": 1629,
+ "Challenge AssaultRifle: Crouching Tiger, Hidden Assault Rifle": 1630,
+ "Challenge AssaultRifle: From My Cold, Dead Hands": 1631,
+ "Challenge Launcher: Rocket and Roll": 1632,
+ "Challenge Launcher: Catch-a-Rocket!": 1633,
+ "Challenge Launcher: Shield Basher": 1634,
+ "Challenge Launcher: Sky Rockets in Flight...": 1635,
+ "Challenge Launcher: Splish Splash": 1636,
+ "Challenge Launcher: Gone with the Second Wind": 1637,
+ "Challenge Pistol: Pistolero": 1638,
+ "Challenge Pistol: Deadeye": 1639,
+ "Challenge Pistol: The Killer": 1640,
+ "Challenge Pistol: Quickdraw": 1641,
+ "Challenge Pistol: Hard Boiled": 1642,
+ "Challenge SMG: High Rate of Ire": 1643,
+ "Challenge SMG: Constructive Criticism": 1644,
+ "Challenge SMG: Hail of Bullets": 1645,
+ "Challenge SMG: More Like Submachine FUN": 1646,
+ "Challenge Shotgun: Shotgun Surgeon": 1647,
+ "Challenge Shotgun: Faceful of Buckshot": 1648,
+ "Challenge Shotgun: Shotgun!": 1649,
+ "Challenge Shotgun: Shotgun Sniper": 1650,
+ "Challenge Shotgun: Open Wide!": 1651,
+ "Challenge Shotgun: Lock, Stock, and...": 1652,
+ "Challenge SniperRifle: Longshot": 1653,
+ "Challenge SniperRifle: No Scope, No Problem": 1654,
+ "Challenge SniperRifle: Eviscerated": 1655,
+ "Challenge SniperRifle: Surprise!": 1656,
+ "Challenge Sniper: Snipe Hunting": 1657,
+ "Challenge Sniper: Longshot Headshot": 1658,
+ "Challenge Sniper: Leaf on the Second Wind": 1659,
+ "Challenge Elemental: Slag-Licked": 1660,
+ "Challenge Elemental: Corroderate": 1661,
+ "Challenge Elemental: I Just Want to Set the World on Fire": 1662,
+ "Challenge Elemental: Say ''Watt'' Again": 1663,
+ "Challenge Elemental: Acid Trip": 1664,
+ "Challenge Elemental: Boom.": 1665,
+ "Challenge Elemental: Cowering Inferno": 1666,
+ "Challenge Enemies: Cruising for a Bruising": 1667,
+ "Challenge Enemies: Bully the Bullies": 1668,
+ "Challenge Enemies: Constructor Destructor": 1669,
+ "Challenge Enemies: Crystals Are a Girl's Best Friend": 1670,
+ "Challenge Enemies: Paingineering": 1671,
+ "Challenge Enemies: WHY SO MUCH HURT?!": 1672,
+ "Challenge Enemies: Die in the Friendly Skies": 1673,
+ "Challenge Enemies: Load and Lock": 1674,
+ "Challenge Enemies: Marauder? I Hardly Know 'Er!": 1675,
+ "Challenge Enemies: Little Person, Big Pain": 1676,
+ "Challenge Enemies: You (No)Mad, Bro?": 1677,
+ "Challenge Enemies: Mama's Boys": 1678,
+ "Challenge Enemies: You Dirty Rat": 1679,
+ "Challenge Enemies: Skags to Riches": 1680,
+ "Challenge Enemies: Pest Control": 1681,
+ "Challenge Enemies: You're One Ugly Mother...": 1682,
+ "Challenge Enemies: Just a Moment of Your Time...": 1683,
+ "Challenge Enemies: Tentacle Obsession": 1684,
+ "Challenge Enemies: Another Bug Hunt": 1685,
+ "Challenge Enemies: Pod Pew Pew": 1686,
+ "Challenge Enemies: Short-Chained": 1687,
+ "Challenge Enemies: Hurly Burly": 1688,
+ "Challenge TorgueDLC: Torgue Items Purchased": 1689,
+ "Challenge TorgueDLC: Battles Completed": 1690,
+ "Challenge TorgueDLC: Bikers Killed": 1691,
+ "Challenge TorgueDLC: Bikes Destroyed": 1692,
+ "Challenge TorgueDLC: Pete the Invincible Defeated": 1693,
+ "Challenge TorgueDLC: Torgue Tokens Acquired": 1694,
+ "Challenge TorgueArena: C4 Is For Cookie": 1695,
+ "Challenge TorgueArena: Cult of the Vault": 1696,
+ "Challenge TorgueArena: It's Cool, I'm Part of the Crew": 1697,
+ "Challenge TorgueArena: Are You Not Entertained?": 1698,
+ "Challenge TorgueArena: Burnin' Ring of Fire": 1699,
+ "Challenge Beatdown: Cult of the Vault": 1700,
+ "Challenge Beatdown: I Thought You'd Be... Bigger": 1701,
+ "Challenge BadassCrater: As Advertised": 1702,
+ "Challenge BadassCrater: Cult of the Vault": 1703,
+ "Challenge BadassCrater: Fan Club Membership": 1704,
+ "Challenge BadassCrater: I Can Fly!": 1705,
+ "Challenge BadassCrater: Only Shells I Need Are in My Guns": 1706,
+ "Challenge Forge: Air Mail": 1707,
+ "Challenge Forge: Cult of the Vault": 1708,
+ "Challenge Forge: I DID Start the Fire": 1709,
+ "Challenge Forge: Parachutes Are for Pansies": 1710,
+ "Challenge SouthernRaceway: Cult of the Vault": 1711,
+ "Challenge ScarlettDLC: In The Pink": 1712,
+ "Challenge ScarlettDLC: Land Lubber": 1713,
+ "Challenge ScarlettDLC: Hovernator": 1714,
+ "Challenge ScarlettDLC: Worm Killer": 1715,
+ "Challenge ScarlettDLC: Pirate Booty": 1716,
+ "Challenge ScarlettDLC: Shady Dealings": 1717,
+ "Challenge ScarlettDLC: Hyperius the Not-So-Invincible": 1718,
+ "Challenge ScarlettDLC: Master Worm Food": 1719,
+ "Challenge HaytersFolly: Playing Chicken in my Sleep": 1720,
+ "Challenge HaytersFolly: Wet Reward": 1721,
+ "Challenge HaytersFolly: Master of My Domain": 1722,
+ "Challenge HaytersFolly: Cult of the Vault": 1723,
+ "Challenge Oasis: I Ain't Afraid of Heights": 1724,
+ "Challenge Oasis: Cult of the Vault": 1725,
+ "Challenge Oasis: Horrid's Hideaway": 1726,
+ "Challenge Washburne: I Get It for the Articles": 1727,
+ "Challenge Washburne: Arm Thyself": 1728,
+ "Challenge Washburne: Cult of the Vault": 1729,
+ "Challenge Magnys Lighthouse: Maroonie's Inheritance": 1730,
+ "Challenge Magnys Lighthouse: N00b Cannon": 1731,
+ "Challenge Magnys Lighthouse: Cult of the Vault": 1732,
+ "Challenge Wurmwater: Skiffless": 1733,
+ "Challenge Wurmwater: Comb the Desert!": 1734,
+ "Challenge Wurmwater: Refined Tastes": 1735,
+ "Challenge Wurmwater: Cult of the Vault": 1736,
+ "Challenge Rustyards: My Main Squeeze": 1737,
+ "Challenge Rustyards: Crow's Nest": 1738,
+ "Challenge Rustyards: Cult of the Vault": 1739,
+ "Challenge LeviathansLair: Lost Lost Treasure": 1740,
+ "Challenge LeviathansLair: Bleeding Scarlett": 1741,
+ "Challenge LeviathansLair: Cult of the Vault": 1742,
+ "Challenge HammerlockDLC: Boroking Around": 1743,
+ "Challenge HammerlockDLC: Harder They Fall": 1744,
+ "Challenge HammerlockDLC: Fan Boy": 1745,
+ "Challenge HammerlockDLC: Savage Bloody Savage": 1746,
+ "Challenge HammerlockDLC: Stinging Sensation": 1747,
+ "Challenge HammerlockDLC: Voracidous the Invincible": 1748,
+ "Challenge CandlerakksCrag: Cage Free": 1749,
+ "Challenge CandlerakksCrag: Nest Egg": 1750,
+ "Challenge CandlerakksCrag: Cult of the Vault": 1751,
+ "Challenge Terminus: Cult of the Vault": 1752,
+ "Challenge Ardorton Station: Horrible Assistants": 1753,
+ "Challenge Ardorton Station: I Promised to Kill You Last": 1754,
+ "Challenge Ardorton Station: Power High": 1755,
+ "Challenge Ardorton Station: Cult of the Vault": 1756,
+ "Challenge ScyllasGrove: Beware the Clap": 1757,
+ "Challenge ScyllasGrove: Human Barbecue": 1758,
+ "Challenge ScyllasGrove: Rakk Forest": 1759,
+ "Challenge ScyllasGrove: Cult of the Vault": 1760,
+ "Challenge HuntersGrotto: Grenade Fishing": 1761,
+ "Challenge HuntersGrotto: Stool Sample": 1762,
+ "Challenge HuntersGrotto: It's a Trap! Kind of.": 1763,
+ "Challenge HuntersGrotto: Cult of the Vault": 1764,
+
+ # Chests [1800 - 2114]
+ "Chest Sanctuary: Roland's Armory": 1800,
+ # "Chest Sanctuary: Golden Chest": 1801,
+ # "Chest Sanctuary: Roland's Armory": 1802,
+ # "Chest Sanctuary: Golden Chest": 1803,
+ "Chest EridiumBlight: Volcano Free Chest": 1804,
+ "Chest EridiumBlight: Pipe Dreaming": 1805,
+ "Chest EridiumBlight: Eastern Chest": 1806,
+ "Chest EridiumBlight: Mount Hellsfont Trunk": 1807,
+ "Chest EridiumBlight: Infested Grotto Trunk": 1808,
+ # "Chest EridiumBlight: Eagle 1": 1809,
+ # "Chest EridiumBlight: Eagle 2": 1810,
+ # "Chest EridiumBlight: Eagle 3": 1811,
+ "Chest Bunker: Back Room": 1812,
+ "Chest VaultOfTheWarrior: Lava River Cave": 1813,
+ "Chest CausticCaverns: Treasure Chest": 1814,
+ "Chest CausticCaverns: Nether Hive": 1815,
+ "Chest CausticCaverns: Infested Warehouse": 1816,
+ "Chest SouthernShelf: Boom Bewm Elevator": 1817,
+ "Chest SouthernShelf: Gateway Harbor Shipwreck": 1818,
+ "Chest SouthernShelf: Captain Flynt's Chest": 1819,
+ "Chest SouthernShelf: Roaring Dragon Camp": 1820,
+ "Chest SouthernShelfBay: Midgemong's Trunk": 1821,
+ "Chest SouthernShelfBay: Cove Ship": 1822,
+ "Chest BloodshotStronghold: Electric Gate": 1823,
+ "Chest BloodshotStronghold: Jail Cell": 1824,
+ "Chest BloodshotStronghold: Flinter's Room": 1825,
+ "Chest BloodshotRamparts: Car Junkyard Trunk": 1826,
+ "Chest BloodshotRamparts: W4R-D3N Trunk": 1827,
+ "Chest Fridge: Rakkman's Lair": 1828,
+ "Chest Fridge: Sinkhole's Pit": 1829,
+ "Chest Fridge: Three Switch Electric Gate": 1830,
+ "Chest Fridge: Smashhead's Cave": 1831,
+ "Chest AridNexusBoneyard: Eridium Pump Station 2": 1832,
+ "Chest AridNexusBoneyard: Eridium Pump Station 3": 1833,
+ "Chest WindshearWaste: Blindsided": 1834,
+ "Chest ThousandCuts: Slab UHF": 1835,
+ "Chest ThousandCuts: Buzzard Factory": 1836,
+ "Chest ThousandCuts: Control Croe Loading Dock": 1837,
+ "Chest ThousandCuts: Super Badass Constructor": 1838,
+ "Chest Lynchwood: MadDog Trunk": 1839,
+ "Chest Lynchwood: Gunslinger Top Trunk": 1840,
+ "Chest Lynchwood: Outskirts Trunk": 1841,
+ "Chest Lynchwood: Gunslinger Lower Trunk": 1842,
+ "Chest Highlands: Blake Bridge Fall": 1843,
+ "Chest FriendshipGulag: Carson Cell": 1844,
+ "Chest FriendshipGulag: Gulag Awning": 1845,
+ "Chest ThreeHornsDivide: Windbreak Car Trunk": 1846,
+ "Chest ThreeHornsDivide: Drydocks Upstairs": 1847,
+ "Chest FrostburnCanyon: Incinerator Camp": 1848,
+ "Chest FrostburnCanyon: Above Spawn": 1849,
+ "Chest FrostburnCanyon: Spycho Area": 1850,
+ "Chest FrostburnCanyon: After Firehawk": 1851,
+ "Chest Dust: Goose's Roost Volleyball Net": 1852,
+ "Chest HolySpirits: The Stash": 1853,
+ "Chest HighlandsOutwash: Old Slappy Tower": 1854,
+ "Chest WildlifePreserve: Observation Wing #1": 1855,
+ "Chest WildlifePreserve: Dockyard": 1856,
+ "Chest WildlifePreserve: Loot Midget Room": 1857,
+ "Chest WildlifePreserve: Observation Wing #2": 1858,
+ "Chest WildlifePreserve: Mordy's Secrest Stash": 1859,
+ "Chest TundraExpress: Mount Molehill Mine Top": 1860,
+ "Chest TundraExpress: Varkid Ranch Observatory": 1861,
+ "Chest TundraExpress: South Western Chest": 1862,
+ "Chest HaytersFolly: Wet Reward Chest #1": 1863,
+ "Chest HaytersFolly: Wet Reward Chest #2": 1864,
+ "Chest HaytersFolly: Sandman's Overlook": 1865,
+ "Chest HaytersFolly: Sandman Arena": 1866,
+ "Chest Wurmwater: Sand Worm Queen Pit": 1867,
+ "Chest Wurmwater: The Dish Metal Hut": 1868,
+ "Chest Wurmwater: The Washburne Fen Rocky Outcrop": 1869,
+ "Chest Wurmwater: Refined Tastes": 1870,
+ "Chest Wurmwater: Top o' The Dish": 1871,
+ "Chest Wurmwater: Old Murphy's Canyon": 1872,
+ "Chest Wurmwater: The Dish Symbol": 1873,
+ "Chest LeviathansLair: Outer #5": 1874,
+ "Chest LeviathansLair: Outer #1": 1875,
+ "Chest LeviathansLair: Outer #6": 1876,
+ "Chest LeviathansLair: Outer #4": 1877,
+ "Chest LeviathansLair: Outer #3": 1878,
+ "Chest LeviathansLair: Outer #7": 1879,
+ "Chest LeviathansLair: Outer #2": 1880,
+ "Chest LeviathansLair: Inner #1": 1881,
+ "Chest LeviathansLair: Inner #2": 1882,
+ "Chest LeviathansLair: Inner #3": 1883,
+ "Chest LeviathansLair: Inner #4": 1884,
+ "Chest LeviathansLair: Inner #5": 1885,
+ "Chest LeviathansLair: Inner #6": 1886,
+ "Chest LeviathansLair: Lost Treasure": 1887,
+ "Chest TorgueArenaRing: First Arena Ladder": 1888,
+ "Chest TorgueArenaRing: Maintenance Pipes": 1889,
+ "Chest TorgueArenaRing: Storage Area": 1890,
+ "Chest TorgueArenaRing: Arena Foyer": 1891,
+ "Chest TorgueArena: Arena Foyer": 1892,
+ "Chest PyroPetesBar: Moxxi Cell #1": 1893,
+ "Chest PyroPetesBar: Moxxi Cell #2": 1894,
+ "Chest Beatdown: Beatdown Rooftops": 1895,
+ "Chest Beatdown: Gun Emporeum Alley": 1896,
+ "Chest Forge: Landing Pad Maintenance": 1897,
+ "Chest Forge: Forge Parkour": 1898,
+ "Chest Forge: Junkyard": 1899,
+ "Chest Terminus: Chest #5": 1900,
+ "Chest Terminus: Chest #10": 1901,
+ "Chest Terminus: Chest #2": 1902,
+ "Chest Terminus: Chest #4": 1903,
+ "Chest Terminus: Chest #12": 1904,
+ "Chest Terminus: Chest #8": 1905,
+ "Chest Terminus: Chest #1": 1906,
+ "Chest Terminus: Chest #3": 1907,
+ "Chest Terminus: Chest #6": 1908,
+ "Chest Terminus: Chest #7": 1909,
+ "Chest Terminus: Chest #9": 1910,
+ "Chest Terminus: Chest #11": 1911,
+ "Chest Terminus: Chest #13": 1912,
+ "Chest SawtoothCauldron: Smoking Guano Grotto Platform": 1913,
+ "Chest SawtoothCauldron: Avie's Camp": 1914,
+ "Chest SawtoothCauldron: Cramfist's Foundry": 1915,
+ "Chest SawtoothCauldron: Mortar's Elevator": 1916,
+ "Chest HerosPass: Volcano Entrance": 1917,
+ "Chest ThreeHornsValley: Split Skull Bay Trunk": 1918,
+ "Chest Opportunity: Home Movies Crate": 1919,
+ "Chest Opportunity: Home Movies Upload Room": 1920,
+ "Chest Opportunity: Voice Modulator Room": 1921,
+ "Chest Opportunity: Orbital Delivery Zone": 1922,
+ "Chest Opportunity: Southern Construction Site": 1923,
+ "Chest Opportunity: Random Rooftop": 1924,
+ # "Chest Opportunity: Random Rooftop": 1925,
+ # "Chest Opportunity: Random Rooftop": 1926,
+ "Chest SanctuaryHole: Generator Room": 1927,
+ "Chest Southpaw: After Rouf": 1928,
+ "Chest Southpaw: After Oney": 1929,
+ "Chest AridNexusBadlands: Zed's Old Home": 1930,
+ "Chest AridNexusBadlands: Hyperion Info Stockade": 1931,
+ "Chest AridNexusBadlands: Hey! Over Here!": 1932,
+ "Chest AridNexusBadlands: Fyrestone Motel Roof": 1933,
+ "Chest TerramorphousPeak: Terramorphous Chest #1": 1934,
+ "Chest TerramorphousPeak: Terramorphous Chest #2": 1935,
+ "Chest ControlCoreAngel: Marcus Chest #1": 1936,
+ "Chest ControlCoreAngel: Marcus Chest #2": 1937,
+ "Chest ControlCoreAngel: Marcus Chest #3": 1938,
+ "Chest Oasis: Coral Island": 1939,
+ "Chest Oasis: Horrid's Hidewaway": 1940,
+ "Chest Oasis: Canyon Deserter Camp #1": 1941,
+ "Chest Oasis: Canyon Deserter Camp #2": 1942,
+ "Chest Washburne: Forge Array": 1943,
+ "Chest Washburne: Arm Thyself": 1944,
+ "Chest Washburne: H3Rl-E": 1945,
+ "Chest Rustyards: 4th Audio Recording Ship": 1946,
+ "Chest Rustyards: Rusty Cog Settlement": 1947,
+ "Chest Rustyards: Maroonie's Clipper": 1948,
+ "Chest Rustyards: Crow's Nest": 1949,
+ "Chest MagnysLighthouse: Under A House": 1950,
+ "Chest MagnysLighthouse: AM-FM Isle": 1951,
+ "Chest MagnysLighthouse: Magnys Lighthouse Top #1": 1952,
+ "Chest MagnysLighthouse: Magnys Lighthouse Top #2": 1953,
+ "Chest BadassCrater: Beatdown Billboard": 1954,
+ "Chest BadassCrater: Torgue Arena Billboard": 1955,
+ "Chest BadassCrater: Crater Rim Parkour Hut": 1956,
+ "Chest CandlerakksCrag: Lower Bluff": 1957,
+ "Chest CandlerakksCrag: Tracking Area": 1958,
+ "Chest CandlerakksCrag: Rouge Overlook": 1959,
+ "Chest CandlerakksCrag: Before Terminus": 1960,
+ "Chest CandlerakksCrag: Hyperion Nest Box West": 1961,
+ "Chest CandlerakksCrag: Hyperion Nest Box East": 1962,
+ "Chest CandlerakksCrag: Hyperion Nest Box Centre": 1963,
+ "Chest ArdortonStation: Ardo Farm": 1964,
+ "Chest ArdortonStation: Power High": 1965,
+ "Chest ArdortonStation: Woundspike's Lair": 1966,
+ "Chest ArdortonStation: Skin-Chop Chop-Shop": 1967,
+ "Chest HuntersGrotto: Bulstoss Village": 1968,
+ "Chest HuntersGrotto: Under Carriage Top": 1969,
+ "Chest HuntersGrotto: Arizona's Chest": 1970,
+ "Chest HuntersGrotto: Destroyed Road": 1971,
+ "Chest HatredsShadow: The Pit Cliff": 1972,
+ "Chest HatredsShadow: Birds Nest": 1973,
+ "Chest HatredsShadow: Knight Bridge Challenge #1": 1974,
+ "Chest HatredsShadow: Knight Bridge Challenge #2": 1975,
+ "Chest HatredsShadow: Darkness Magic Missile Door": 1976,
+ "Chest DragonKeep: Ledge": 1977,
+ "Chest Forest: Orc Camp": 1978,
+ "Chest Forest: Blacksmith": 1979,
+ "Chest ImmortalWoods: Graveyard Pillar": 1980,
+ "Chest ImmortalWoods: Ruins Altar": 1981,
+ "Chest ImmortalWoods: Ruins Parkour": 1982,
+ "Chest UnassumingDocks: Forlorn Ruins": 1983,
+ "Chest UnassumingDocks: Southern Temple": 1984,
+ "Chest LairOfInfiniteAgony: Sepulcher Challenge": 1985,
+ "Chest LairOfInfiniteAgony: Boss Lair": 1986,
+ "Chest LairOfInfiniteAgony: Edgar's Sanctum": 1987,
+ "Chest LairOfInfiniteAgony: Bone Barracks": 1988,
+ # "Chest WingedStorm: Mimic Easter Egg #1": 1989,
+ # "Chest WingedStorm: Mimic Easter Egg #2": 1990,
+ # "Chest WingedStorm: Mimic Easter Egg #3": 1991,
+ "Chest WingedStorm: Chest #36": 1992,
+ "Chest WingedStorm: Chest #2": 1993,
+ "Chest WingedStorm: Chest #9": 1994,
+ "Chest WingedStorm: Chest #7": 1995,
+ "Chest WingedStorm: Chest #35": 1996,
+ "Chest WingedStorm: Chest #28": 1997,
+ "Chest WingedStorm: Chest #34": 1998,
+ "Chest WingedStorm: Chest #33": 1999,
+ "Chest WingedStorm: Chest #44": 2000,
+ "Chest WingedStorm: Chest #48": 2001,
+ "Chest WingedStorm: Chest #4": 2002,
+ "Chest WingedStorm: Chest #56": 2003,
+ "Chest WingedStorm: Chest #1": 2004,
+ "Chest WingedStorm: Chest #50": 2005,
+ "Chest WingedStorm: Chest #10": 2006,
+ "Chest WingedStorm: Chest #38": 2007,
+ "Chest WingedStorm: Chest #26": 2008,
+ "Chest WingedStorm: Chest #45": 2009,
+ "Chest WingedStorm: Chest #46": 2010,
+ "Chest WingedStorm: Chest #43": 2011,
+ "Chest WingedStorm: Chest #42": 2012,
+ "Chest WingedStorm: Chest #41": 2013,
+ "Chest WingedStorm: Chest #40": 2014,
+ "Chest WingedStorm: Chest #39": 2015,
+ "Chest WingedStorm: Chest #47": 2016,
+ "Chest WingedStorm: Chest #8": 2017,
+ "Chest WingedStorm: Chest #6": 2018,
+ "Chest WingedStorm: Chest #51": 2019,
+ "Chest WingedStorm: Chest #49": 2020,
+ "Chest WingedStorm: Chest #54": 2021,
+ "Chest WingedStorm: Chest #53": 2022,
+ "Chest WingedStorm: Chest #52": 2023,
+ "Chest WingedStorm: Chest #55": 2024,
+ "Chest WingedStorm: Chest #21": 2025,
+ "Chest WingedStorm: Chest #11": 2026,
+ "Chest WingedStorm: Chest #13": 2027,
+ "Chest WingedStorm: Chest #25": 2028,
+ "Chest WingedStorm: Chest #24": 2029,
+ "Chest WingedStorm: Chest #22": 2030,
+ "Chest WingedStorm: Chest #23": 2031,
+ "Chest WingedStorm: Chest #12": 2032,
+ "Chest WingedStorm: Chest #14": 2033,
+ "Chest WingedStorm: Chest #17": 2034,
+ "Chest WingedStorm: Chest #19": 2035,
+ "Chest WingedStorm: Chest #20": 2036,
+ "Chest WingedStorm: Chest #18": 2037,
+ "Chest WingedStorm: Chest #16": 2038,
+ "Chest WingedStorm: Chest #15": 2039,
+ "Chest WingedStorm: Chest #27": 2040,
+ "Chest WingedStorm: Chest #5": 2041,
+ "Chest WingedStorm: Chest #32": 2042,
+ "Chest WingedStorm: Chest #31": 2043,
+ "Chest WingedStorm: Chest #3": 2044,
+ "Chest WingedStorm: Chest #30": 2045,
+ "Chest WingedStorm: Chest #29": 2046,
+ "Chest WingedStorm: Chest #37": 2047,
+ "Chest MinesOfAvarice: Ingot Processing": 2048,
+ "Chest MinesOfAvarice: Gilded Forge": 2049,
+ "Chest MinesOfAvarice: Greedtooth": 2050,
+ "Chest MinesOfAvarice: Hanging Platform": 2051,
+ # "Chest FFSBossFight: 102": 2052,
+ # "Chest FFSBossFight: 110": 2053,
+ # "Chest FFSBossFight: 9": 2054,
+ # "Chest FFSBossFight: 11": 2055,
+ "Chest HeliosFallen: 78": 2056,
+ "Chest HeliosFallen: 29": 2057,
+ "Chest DahlAbandon: 22": 2058,
+ "Chest DahlAbandon: 24": 2059,
+ "Chest DahlAbandon: 106": 2060,
+ "Chest DahlAbandon: 36": 2061,
+ "Chest Mt.ScarabResearchCenter: 47": 2062,
+ "Chest Mt.ScarabResearchCenter: 9": 2063,
+ "Chest Burrows: 12": 2064,
+ "Chest Burrows: 28": 2065,
+ "Chest Burrows: 99": 2066,
+ "Chest WrithingDeep: 0": 2067,
+ "Chest WrithingDeep: 1": 2068,
+ "Chest WrithingDeep: 2": 2069,
+ "Chest WrithingDeep: 3": 2070,
+ "Chest WrithingDeep: 4": 2071,
+ "Chest RotgutDistillery: Rectory 1": 2072,
+ "Chest RotgutDistillery: Rectory 2": 2073,
+ "Chest RotgutDistillery: Thresher Tails": 2074,
+ "Chest RotgutDistillery: Hodunk Holler Dock": 2075,
+ "Chest RotgutDistillery: Motel Car": 2076,
+ "Chest WamBamIsland: Treasure Varkid 3": 2077,
+ "Chest WamBamIsland: Water Wheel Lodge": 2078,
+ "Chest WamBamIsland: Treasure Varkid 1": 2079,
+ "Chest WamBamIsland: Treasure Varkid 2": 2080,
+ "Chest WamBamIsland: Raid Reward 4": 2081,
+ "Chest WamBamIsland: Raid Reward 5": 2082,
+ "Chest WamBamIsland: Raid Reward 3": 2083,
+ "Chest WamBamIsland: Raid Reward 7": 2084,
+ "Chest WamBamIsland: Raid Reward 6": 2085,
+ "Chest WamBamIsland: Raid Reward 2": 2086,
+ "Chest WamBamIsland: Son of Crawmerax Exit 1": 2087,
+ "Chest WamBamIsland: Raid Reward 1": 2088,
+ "Chest WamBamIsland: Son of Crawmerax Exit 2": 2089,
+ "Chest WamBamIsland: Son of Crawmerax Exit 3": 2090,
+ "Chest WamBamIsland: Near Entrance Cliff": 2091,
+ "Chest WamBamIsland: 57": 2092,
+ "Chest GluttonyGulch: Wattle Gobbler Chest 3": 2093,
+ "Chest GluttonyGulch: Wattle Gobbler Chest 1": 2094,
+ "Chest GluttonyGulch: Freezeburn Fridge": 2095,
+ "Chest GluttonyGulch: Wattle Gobbler Chest 2": 2096,
+ "Chest GluttonyGulch: Stabber Jabber Nest": 2097,
+ "Chest HallowedHollow: Blood Altar": 2098,
+ "Chest HallowedHollow: Forge Exit": 2099,
+ "Chest HallowedHollow: Lavabluffs Broken Bridge": 2100,
+ "Chest HallowedHollow: Church of the Dead": 2101,
+ "Chest MarcusMercenaryShop: Frozen Waterfall": 2102,
+ "Chest MarcusMercenaryShop: Waystation Cliffside": 2103,
+ "Chest MarcusMercenaryShop: Station Rooftop": 2104,
+ "Chest MarcusMercenaryShop: Waystation Rooftop": 2105,
+ "Chest DigistructPeakInner: 1": 2106,
+ "Chest DigistructPeakInner: 39": 2107,
+ "Chest DigistructPeakInner: 50": 2108,
+ "Chest DigistructPeakInner: 52": 2109,
+ "Chest DigistructPeakInner: 53": 2110,
+ "Chest DigistructPeakInner: 61": 2111,
+ "Chest DigistructPeakInner: 7": 2112,
+ "Chest DigistructPeakInner: 70": 2113,
+ "Chest DigistructPeakInner: 71": 2114,
+}
+
+
+item_name_to_id = {
+ "3 Skill Points": 1,
+ "Progressive Money Cap": 2,
+ "Weapon Slot": 3,
+ "$100": 4,
+ "10 Eridium": 5,
+
+ "Progressive Jump": 6,
+ "Melee": 7,
+ "Crouch": 8,
+ "Progressive Sprint": 9,
+ "Gear Leveler": 10,
+ "Vehicle Fire": 11,
+
+ "YellowCandy": 12,
+ "RedCandy": 13,
+ "GreenCandy": 14,
+ "BlueCandy": 15,
+
+ "Filler Gear: Gemstone Pistol": 16,
+ "Filler Gear: Gemstone Shotgun": 17,
+ "Filler Gear: Gemstone SMG": 18,
+ "Filler Gear: Gemstone SniperRifle": 19,
+ "Filler Gear: Gemstone AssaultRifle": 20,
+
+ "10% Exp": 21,
+
+ # Map Areas [501 - 580]
+ # "Travel: Windshear Waste": 501,
+ "Travel: Arid Nexus Boneyard": 502,
+ "Travel: The Holy Spirits": 503,
+ "Travel: Southpaw Steam & Power": 504,
+ "Travel: Sanctuary Hole": 505,
+ "Travel: Hero's Pass": 506,
+ "Travel: Bloodshot Stronghold": 507,
+ "Travel: Three Horns Valley": 508,
+ "Travel: Sanctuary": 509,
+ "Travel: Thousand Cuts": 510,
+ "Travel: End of the Line": 511,
+ "Travel: Wildlife Exploitation Preserve": 512,
+ "Travel: Terramorphous Peak": 513,
+ "Travel: Tundra Express": 514,
+ "Travel: The Fridge": 515,
+ "Travel: Fink's Slaughterhouse": 516,
+ "Travel: Southern Shelf Bay": 517,
+ "Travel: Frostburn Canyon": 518,
+ "Travel: Three Horns Divide": 519,
+ "Travel: Highlands": 520,
+ "Travel: Natural Selection Annex": 521,
+ "Travel: The Dust": 522,
+ "Travel: Friendship Gulag": 523,
+ "Travel: Opportunity": 524,
+ "Travel: Bloodshot Ramparts": 525,
+ "Travel: Arid Nexus Badlands": 526,
+ "Travel: Southern Shelf": 527,
+ "Travel: Highlands Outwash": 528,
+ "Travel: Caustic Caverns": 529,
+ "Travel: Lynchwood": 530,
+ "Travel: Sawtooth Cauldron": 531,
+ "Travel: Ore Chasm": 532,
+ "Travel: The Bunker": 533,
+ "Travel: Control Core Angel": 534,
+ "Travel: Vault of the Warrior": 535,
+ "Travel: Eridium Blight": 536,
+ "Travel: Gluttony Gulch": 537,
+ "Travel: Marcus's Mercenary Shop": 538,
+ "Travel: Helios Fallen": 539,
+ "Travel: FFS Boss Fight": 540,
+ "Travel: The Backburner": 541,
+ "Travel: FFS Intro Sanctuary": 542,
+ "Travel: Dahl Abandon": 543,
+ "Travel: Mt. Scarab Research Center": 544,
+ "Travel: The Burrows": 545,
+ "Travel: Writhing Deep": 546,
+ "Travel: The Forest": 547,
+ "Travel: Immortal Woods": 548,
+ "Travel: Dragon Keep": 549,
+ "Travel: Unassuming Docks": 550,
+ "Travel: Flamerock Refuge": 551,
+ "Travel: Hatred's Shadow": 552,
+ "Travel: Lair of Infinite Agony": 553,
+ "Travel: Murderlin's Temple": 554,
+ "Travel: Mines of Avarice": 555,
+ "Travel: The Winged Storm": 556,
+ "Travel: Hallowed Hollow": 557,
+ "Travel: Torgue Arena Ring": 558,
+ "Travel: Torgue Arena": 559,
+ "Travel: The Beatdown": 560,
+ "Travel: The Forge": 561,
+ "Travel: Badass Crater": 562,
+ "Travel: Southern Raceway": 563,
+ "Travel: Pyro Pete's Bar": 564,
+ "Travel: Badass Crater Bar": 565,
+ "Travel: Digistruct Peak": 566,
+ "Travel: Wam Bam Island": 567,
+ "Travel: Rotgut Distillery": 568,
+ "Travel: The Leviathan's Lair": 569,
+ "Travel: Washburne Refinery": 570,
+ "Travel: Wurmwater": 571,
+ "Travel: Magnys Lighthouse": 572,
+ "Travel: The Rustyards": 573,
+ "Travel: Hayter's Folly": 574,
+ "Travel: Oasis": 575,
+ "Travel: Ardorton Station": 576,
+ "Travel: Hunter's Grotto": 577,
+ "Travel: Candlerakk's Cragg": 578,
+ "Travel: Terminus": 579,
+ "Travel: Scylla's Grove": 580,
+
+ # Traps [600 - 605]
+ "Trap Spawn: Saturn": 600,
+ "Trap Spawn: Black Queen": 601,
+ "Trap Spawn: Assassins": 602,
+ "Trap Spawn: Dukino's Mom": 603,
+ "Trap Spawn: Doc Mercy": 604,
+ "Trap Spawn: Creeper": 605,
+
+ # Filler Gear [1100-1199]
+}
+
+# stitch dictionaries together
+
+loc_name_to_id.update(gear_kind_to_id)
+loc_name_to_id.update(level_name_to_id)
+loc_name_to_id.update({"Quest " + k : v for k, v in quest_name_to_id.items()})
+
+item_name_to_id.update(gear_kind_to_id)
+item_name_to_id.update({"Reward " + k : v for k, v in quest_name_to_id.items()})
+
+item_name_to_id.update({"Filler Gear: " + k : v + 1000 for k, v in gear_kind_to_id.items()}) # Filler Gear 1100-1199
+
+loc_id_to_name = {id: name for name, id in loc_name_to_id.items()}
+item_id_to_name = {id: name for name, id in item_name_to_id.items()}
diff --git a/worlds/borderlands2/archipelago.json b/worlds/borderlands2/archipelago.json
new file mode 100644
index 000000000000..9a8952fa4066
--- /dev/null
+++ b/worlds/borderlands2/archipelago.json
@@ -0,0 +1,6 @@
+{
+ "game": "Borderlands 2",
+ "authors": ["EdricY"],
+ "world_version": "0.4.0",
+ "minimum_ap_version": "0.6.4"
+}
diff --git a/worlds/borderlands2/docs/en_Borderlands 2.md b/worlds/borderlands2/docs/en_Borderlands 2.md
new file mode 100644
index 000000000000..18aa83fcabb7
--- /dev/null
+++ b/worlds/borderlands2/docs/en_Borderlands 2.md
@@ -0,0 +1,10 @@
+# Borderlands 2
+
+TODO: content here
+
+## What is this game?
+
+## 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.
diff --git a/worlds/borderlands2/docs/guide_en.md b/worlds/borderlands2/docs/guide_en.md
new file mode 100644
index 000000000000..e30f3e793084
--- /dev/null
+++ b/worlds/borderlands2/docs/guide_en.md
@@ -0,0 +1,7 @@
+# Borderlands 2 Setup Guide
+TODO: content here
+## Required Software
+
+## Installation Procedures (Windows)
+
+## Joining a MultiWorld Game
diff --git a/worlds/cccharles/__init__.py b/worlds/cccharles/__init__.py
index 6d40c0172d66..0888d8a8505d 100644
--- a/worlds/cccharles/__init__.py
+++ b/worlds/cccharles/__init__.py
@@ -4,7 +4,7 @@
from .Rules import set_rules
from .Regions import create_regions
from BaseClasses import Tutorial, ItemClassification
-from worlds.AutoWorld import World, WebWorld
+from worlds.AutoWorld import InvalidItemError, World, WebWorld
class CCCharlesWeb(WebWorld):
@@ -157,7 +157,7 @@ def create_item(self, name: str) -> CCCharlesItem:
case "Bug Spray":
classification = ItemClassification.progression
case _: # Should not occur
- raise Exception("Unexpected case met: classification cannot be set for unknown item \"" + name + "\"")
+ raise InvalidItemError("Unexpected case met: classification cannot be set for unknown item \"" + name + "\"")
return CCCharlesItem(name, classification, item_id, self.player)
diff --git a/worlds/cccharles/docs/en_Choo-Choo Charles.md b/worlds/cccharles/docs/en_Choo-Choo Charles.md
index 6d8ed39e03ea..f880865ff817 100644
--- a/worlds/cccharles/docs/en_Choo-Choo Charles.md
+++ b/worlds/cccharles/docs/en_Choo-Choo Charles.md
@@ -13,7 +13,7 @@ All scraps or any collectable item on the ground (except from Loot Crates) and i
Beating the evil train from Hell named "Charles".
## How is the game managed in Nightmare mode?
-At death, the player has to restart a brand-new game, giving him the choice to stay under the Nightmare mode or continuing with the Normal mode if considered too hard.
+At death, the player has to restart a brand-new game, giving them the choice to stay under the Nightmare Mode or continuing with the Classic Mode if considered too hard.
In this case, all collected items will be redistributed in the inventory and the missions states will be kept.
The Deathlink is not implemented yet. When this option will be available, a choice will be provided to:
* Disable the Deathlink
diff --git a/worlds/cccharles/docs/fr_Choo-Choo Charles.md b/worlds/cccharles/docs/fr_Choo-Choo Charles.md
index 42f0c48396c6..7d1a3f37d050 100644
--- a/worlds/cccharles/docs/fr_Choo-Choo Charles.md
+++ b/worlds/cccharles/docs/fr_Choo-Choo Charles.md
@@ -3,14 +3,14 @@
## Où est la page d'options ?
La [page d'options du joueur pour ce jeu](../player-options) contient toutes les options pour configurer et exporter un fichier de configuration yaml.
-## Qu'est ce que la randomisation fait au jeu ?
-Tous les débrits ou n'importe quel objet ramassable au sol (excepté les Caisses à Butin) et objets reçus par les missions de PNJs sont considérés comme emplacements à vérifier.
+## Qu'est-ce que la randomisation fait au jeu ?
+Tous les débris ou n'importe quel objet ramassable au sol (excepté les Caisses à Butin) et objets reçus par les missions de PNJs sont considérés comme emplacements à vérifier.
## Quel est le but de Choo-Choo Charles lorsqu'il est randomisé ?
Vaincre le train démoniaque de l'Enfer nommé "Charles".
-## Comment le jeu est-il géré en mode Nightmare ?
-À sa mort, le joueur doit relancer une toute nouvelle partie, lui donnant la possisilité de rester en mode Nightmare ou de poursuivre la partie en mode Normal s'il considère la partie trop difficile.
+## Comment le jeu est-il géré en Mode Cauchemar ?
+À sa mort, le joueur doit relancer une toute nouvelle partie, lui donnant la possibilité de rester en Mode Cauchemar ou de poursuivre la partie en Mode Classique s'il considère la partie trop difficile.
Dans ce cas, tous les objets collectés seront redistribués dans l'inventaire et les états des missions seront conservés.
Le Deathlink n'est pas implémenté pour l'instant. Lorsque cette option sera disponible, un choix sera fourni pour :
* Désactiver le Deathlink
@@ -18,7 +18,7 @@ Le Deathlink n'est pas implémenté pour l'instant. Lorsque cette option sera di
* Activer le Deathlink strict avec suppression de la sauvegarde lorsqu'un évènement Deathlink est reçu
## À quoi ressemble un objet d'un autre monde dans Choo-Choo Charles ?
-Les apparances des objets sont conservés.
+Les apparences des objets sont conservées.
Tout indice qui ne peut pas être représenté normalement dans le jeu est remplacé par l'Easter Egg "DeadDuck" miniaturisé qui peut être vu en dehors des limites murales physiques du jeu original.
## Comment le joueur est-il informé par une transmission d'objet et des indices ?
@@ -29,7 +29,7 @@ La même méthode est utilisée pour les indices.
Non, ceci est un travail en cours.
Les options suivantes seront possibles une fois les implémentations disponibles :
-À n'importe quel moment, le joueur peu appuyer sur l'une des touches suivantes pour afficher la console dans le jeu :
+À n'importe quel moment, le joueur peut appuyer sur l'une des touches suivantes pour afficher la console dans le jeu :
* "~" (qwerty)
* "²" (azerty)
* "F10"
diff --git a/worlds/cccharles/docs/setup_en.md b/worlds/cccharles/docs/setup_en.md
index 8df0188c031b..f5ab8425707d 100644
--- a/worlds/cccharles/docs/setup_en.md
+++ b/worlds/cccharles/docs/setup_en.md
@@ -19,7 +19,7 @@ The Mod can be installed and played by following these steps (see the [Mod Downl
2. Launch the game, if "OFFLINE" is visible in the upper-right corner of the screen, the Mod is working
### Create a Config (.yaml) File
-The purpose of a YAML file is described in the [Basic Multiworld Setup Guide](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game).
+The purpose of a YAML file is described in the [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en#generating-a-game).
The [Player Options page](/games/Choo-Choo%20Charles/player-options) allows to configure personal options and export a config YAML file.
@@ -27,7 +27,7 @@ The [Player Options page](/games/Choo-Choo%20Charles/player-options) allows to c
Before playing, it is highly recommended to check out the **[Known Issues](setup_en#known-issues)** section
* The game console must be opened to type Archipelago commands, press "F10" key or "`" (or "~") key in querty ("²" key in azerty)
* Type ``/connect `` with \ and \ found on the hosting Archipelago web page in the form ``archipelago.gg:XXXXX`` and ``CCCharles``
-* Disconnection is automatic at game closure but can be manually done with ``/disconnect``
+* Disconnection is automatic at game closure, but can be manually done with ``/disconnect``
## Hosting a MultiWorld or Single-Player Game
See the [Mod Download](setup_en#mod-download) section to get the **cccharles.apworld** file.
@@ -38,7 +38,7 @@ Follow these steps to host a remote multiplayer or a local single-player session
1. Double-click the **cccharles.apworld** to automatically install the world randomization logic
2. Put the **CCCharles.yaml** to **Archipelago/Players/** with the YAML of each player to host
3. Launch the Archipelago launcher and click "Generate" to configure a game with the YAMLs in **Archipelago/output/**
-4. For a multiplayer session, go to the [Archipelago HOST GAME page](https://archipelago.gg/uploads)
+4. For a multiplayer session, go to the [Archipelago HOST GAME page](/uploads)
5. Click "Upload File" and select the generated **AP_\.zip** in **Archipelago/output/**
6. Send the generated room page to each player
diff --git a/worlds/cccharles/docs/setup_fr.md b/worlds/cccharles/docs/setup_fr.md
index 386ec02587c6..6ffd0bda6ae2 100644
--- a/worlds/cccharles/docs/setup_fr.md
+++ b/worlds/cccharles/docs/setup_fr.md
@@ -4,7 +4,7 @@ Cette page est un guide simplifié de la [page du Mod Randomiseur Multiworld de
## Exigences et Logiciels Nécessaires
* Un ordinateur utilisant Windows (le Mod n'est pas utilisable sous Linux ou Mac)
* [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
-* Une copie légale du jeu original Choo-Choo Charles (peut être trouvé sur [Steam](https://store.steampowered.com/app/1766740/ChooChoo_Charles/)
+* Une copie légale du jeu original Choo-Choo Charles (peut être trouvé sur [Steam](https://store.steampowered.com/app/1766740/ChooChoo_Charles/))
## Installation du Mod pour jouer
### Téléchargement du Mod
@@ -19,7 +19,7 @@ Le Mod peut être installé et joué en suivant les étapes suivantes (voir la s
2. Lancer le jeu, si "OFFLINE" est visible dans le coin en haut à droite de l'écran, le Mod est actif
### Créer un Fichier de Configuration (.yaml)
-L'objectif d'un fichier YAML est décrit dans le [Guide d'Installation Basique du Multiworld](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game) (en anglais).
+L'objectif d'un fichier YAML est décrit dans le [Guide d'Installation Basique du Multiworld](/tutorial/Archipelago/setup/en#generating-a-game) (en anglais).
La [page d'Options Joueur](/games/Choo-Choo%20Charles/player-options) permet de configurer des options personnelles et exporter un fichier de configuration YAML.
@@ -27,7 +27,7 @@ La [page d'Options Joueur](/games/Choo-Choo%20Charles/player-options) permet de
Avant de jouer, il est fortement recommandé de consulter la section **[Problèmes Connus](setup_fr#probl%C3%A8mes-connus)**.
* La console du jeu doit être ouverte pour taper des commandes Archipelago, appuyer sur la touche "F10" ou "`" (ou "~") en querty (touche "²" en azerty)
* Taper ``/connect `` avec \ et \ trouvés sur la page web d'hébergement Archipelago sous la forme ``archipelago.gg:XXXXX`` et ``CCCharles``
-* La déconnexion est automatique à la fermeture du jeu mais peut être faite manuellement avec ``/disconnect``
+* La déconnexion est automatique à la fermeture du jeu, mais peut être faite manuellement avec ``/disconnect``
## Héberger une partie MultiWorld ou un Seul Joueur
Voir la section [Téléchargement du Mod](setup_fr#téléchargement-du-mod) pour récupérer le fichier **cccharles.apworld**.
@@ -38,8 +38,8 @@ Suivre ces étapes pour héberger une session multijoueur à distance ou locale
1. Double-cliquer sur **cccharles.apworld** pour installer automatiquement la logique de randomisation du monde
2. Placer le **CCCharles.yaml** dans **Archipelago/Players/** avec le YAML de chaque joueur à héberger
3. Exécuter le lanceur Archipelago et cliquer sur "Generate" pour configurer une partie avec les YAML dans **Archipelago/output/**
-4. Pour une session multijoueur, aller à la [page Archipelago HOST GAME](https://archipelago.gg/uploads)
-5. Cliquer sur "Upload File" et selectionner le **AP_\.zip** généré dans **Archipelago/output/**
+4. Pour une session multijoueur, aller à la [page Archipelago HOST GAME](/uploads)
+5. Cliquer sur "Upload File" et sélectionner le **AP_\.zip** généré dans **Archipelago/output/**
6. Envoyer la page de la partie générée à chaque joueur
Pour une session locale à un seul joueur, cliquer sur "Host" dans le lanceur Archipelago en utilisant **AP_\.zip** généré dans **Archipelago/output/**
diff --git a/worlds/celeste_open_world/docs/en_Celeste (Open World).md b/worlds/celeste_open_world/docs/en_Celeste (Open World).md
index c27c4d6480da..6330bd9bff22 100644
--- a/worlds/celeste_open_world/docs/en_Celeste (Open World).md
+++ b/worlds/celeste_open_world/docs/en_Celeste (Open World).md
@@ -95,4 +95,4 @@ Additionally, the following locations can optionally be included in the Location
## How can I get started?
-To get started playing Celeste (Open World) in Archipelago, [go to the setup guide for this game](../../../tutorial/Celeste%20(Open%20World)/guide/en)
+To get started playing Celeste (Open World) in Archipelago, [go to the setup guide for this game](/tutorial/Celeste%20%28Open%20World%29/guide_en)
diff --git a/worlds/civ_6/Container.py b/worlds/civ_6/Container.py
index a5790c1ec474..433145f42fdd 100644
--- a/worlds/civ_6/Container.py
+++ b/worlds/civ_6/Container.py
@@ -44,9 +44,10 @@ def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
opened_zipfile.writestr(filename, yml)
super().write_contents(opened_zipfile)
+
def sanitize_value(value: str) -> str:
- """Removes values that can cause issues in XML"""
- return value.replace('"', "'").replace('&', 'and')
+ """Removes values that can cause issues in XML"""
+ return value.replace('"', "'").replace('&', 'and').replace('{', '').replace('}', '')
def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int:
@@ -87,8 +88,10 @@ def generate_new_items(world: 'CivVIWorld') -> str:
boost_civics = []
if world.options.boostsanity:
- boost_techs = [location for location in locations if location.location_type == CivVICheckType.BOOST and location.name.split("_")[1] == "TECH"]
- boost_civics = [location for location in locations if location.location_type == CivVICheckType.BOOST and location.name.split("_")[1] == "CIVIC"]
+ boost_techs = [location for location in locations if location.location_type ==
+ CivVICheckType.BOOST and location.name.split("_")[1] == "TECH"]
+ boost_civics = [location for location in locations if location.location_type ==
+ CivVICheckType.BOOST and location.name.split("_")[1] == "CIVIC"]
techs += boost_techs
civics += boost_civics
diff --git a/worlds/crystal_project/__init__.py b/worlds/crystal_project/__init__.py
index f11be485f0bb..34d8b6f58dd5 100644
--- a/worlds/crystal_project/__init__.py
+++ b/worlds/crystal_project/__init__.py
@@ -1,650 +1,650 @@
-import logging
-from .constants.mounts import *
-from .constants.jobs import *
-from .constants.keys import *
-from .constants.key_items import *
-from .constants.maps import *
-from .constants.ap_regions import *
-from .constants.display_regions import *
-from .constants.teleport_stones import *
-from .constants.item_groups import *
-from .constants.region_passes import *
-from .items import item_table, optional_scholar_abilities, get_random_starting_jobs, filler_items, \
- get_item_names_per_category, progressive_equipment, non_progressive_equipment, get_starting_jobs, \
- set_jobs_at_default_locations, default_starting_job_list, key_rings, dungeon_keys, singleton_keys, \
- display_region_name_to_pass_dict, job_crystal_beginner_dictionary, job_crystal_advanced_dictionary, job_crystal_expert_dictionary
-from .locations import get_treasure_and_npc_locations, get_boss_locations, get_shop_locations, get_region_completion_locations, LocationData, get_location_names_per_category, \
- get_location_name_to_id, get_crystal_locations
-from .presets import crystal_project_options_presets
-from .regions import init_ap_region_to_display_region_dictionary, init_areas, ap_region_to_display_region_dictionary, display_region_subregions_dictionary
-from .options import CrystalProjectOptions, IncludedRegions, create_option_groups
-from .rules import CrystalProjectLogic
-from .mod_helper import ModLocationData, get_modded_items, get_modded_locations, \
- get_modded_shopsanity_locations, get_modded_bosses, build_condition_rule, update_item_classification, ModIncrementedIdData, get_mod_info, get_removed_locations
-from typing import List, Set, Dict, Any
-from worlds.AutoWorld import World, WebWorld
-from BaseClasses import Item, Tutorial, MultiWorld, CollectionState
-
-class CrystalProjectWeb(WebWorld):
- theme = "jungle"
- bug_report_page = "https://github.com/Emerassi/CrystalProjectAPWorld/issues"
- setup_en = Tutorial(
- "Mod Setup and Use Guide",
- "A guide to setting up the Crystal Project Archipelago Mod.",
- "English",
- "setup_en.md",
- "setup/en",
- ["dragons but also rabbits"]
- )
-
- tutorials = [setup_en]
- option_groups = create_option_groups()
- options_presets = crystal_project_options_presets
-
-class CrystalProjectWorld(World):
- """Crystal Project is a mix of old school job based jRPG mixed with a ton of 3D platforming and exploration."""
- game = "Crystal Project"
- options_dataclass = CrystalProjectOptions
- options: CrystalProjectOptions
- topology_present = True # show path to required location checks in spoiler
-
- item_name_to_id = {item: item_table[item].code for item in item_table}
- location_name_to_id = get_location_name_to_id()
- item_name_groups = get_item_names_per_category()
- location_name_groups = get_location_names_per_category()
- base_game_jobs: set[str] = item_name_groups[JOB].copy()
-
- mod_info = get_mod_info()
- modded_items = get_modded_items(mod_info)
- modded_job_count: int = 0
-
- for modded_item in modded_items:
- if modded_item.name in item_name_to_id and item_name_to_id[modded_item.name] != modded_item.code:
- raise Exception(f"A modded item({modded_item.name}) with id {modded_item.code} tried to change the code of item_name_to_id and it can never change!")
- item_name_to_id[modded_item.name] = modded_item.code
- if 'Job' in modded_item.name:
- item_name_groups.setdefault(JOB, set()).add(modded_item.name)
- modded_job_count += 1
- else:
- item_name_groups.setdefault(MOD, set()).add(modded_item.name)
-
- modded_locations = get_modded_locations(mod_info)
-
- for modded_location in modded_locations:
- location_name_to_id[modded_location.name] = modded_location.code
-
- modded_shops = get_modded_shopsanity_locations(mod_info)
-
- for modded_shop in modded_shops:
- location_name_to_id[modded_shop.name] = modded_shop.code
-
- modded_bosses = get_modded_bosses(mod_info)
-
- for modded_boss in modded_bosses:
- location_name_to_id[modded_boss.name] = modded_boss.code
-
- removed_locations = get_removed_locations(mod_info)
-
- web = CrystalProjectWeb()
-
- # This is how we tell the Universal Tracker we want to use re_gen_passthrough
- @staticmethod
- def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]:
- return slot_data
-
- # and this is how we tell Universal Tracker we don't need the yaml
- ut_can_gen_without_yaml = True
-
- def __init__(self, multiworld: "MultiWorld", player: int):
- super().__init__(multiworld, player)
- self.starter_ap_region: str = ""
- self.starting_jobs: List[str] = []
- self.jobs_not_to_exclude: List[str] = []
- self.included_regions: List[str] = []
- self.statically_placed_jobs: int = 0
- self.starting_progressive_levels: int = 1
-
- def generate_early(self):
- # implement .yaml-less Universal Tracker support
- if hasattr(self.multiworld, "generation_is_fake"):
- if hasattr(self.multiworld, "re_gen_passthrough"):
- if "Crystal Project" in self.multiworld.re_gen_passthrough:
- slot_data = self.multiworld.re_gen_passthrough["Crystal Project"]
- self.options.goal.value = slot_data["goal"]
- self.options.clamshell_goal_quantity.value = slot_data["clamshellGoalQuantity"]
- self.options.extra_clamshells_in_pool.value = slot_data["extraClamshellsInPool"]
- self.options.new_world_stone_job_quantity.value = slot_data["jobGoalAmount"]
- self.options.job_rando.value = slot_data["jobRando"]
- self.starting_jobs = slot_data["startingJobsForUT"]
- self.options.starting_job_quantity.value = slot_data["startingJobQuantity"]
- self.options.kill_bosses_mode.value = slot_data["killBossesMode"]
- self.options.shopsanity.value = slot_data["shopsanity"]
- self.options.regionsanity.value = slot_data["regionsanity"]
- self.options.included_regions.value = slot_data["includedRegionsOption"]
- self.options.progressive_mount_mode.value = slot_data["progressiveMountMode"]
- self.options.level_gating.value = slot_data["levelGating"]
- self.options.level_compared_to_enemies.value = slot_data["levelComparedToEnemies"]
- self.options.progressive_level_size.value = slot_data["progressiveLevelSize"]
- self.options.max_level.value = slot_data["maxLevel"]
- self.options.key_mode.value = slot_data["keyMode"]
- self.options.obscure_routes.value = slot_data["obscureRoutes"]
- self.options.include_summon_abilities.value = slot_data["includeSummonAbilities"]
- self.options.include_scholar_abilities.value = slot_data["includeScholarAbilities"]
- self.options.use_mods.value = slot_data["useMods"]
- # self.modded_locations = slot_data["moddedLocationsForUT"]
- # self.modded_shops = slot_data["moddedShopsForUT"]
- self.starter_ap_region = slot_data["starterRegion"]
-
- if self.options.prioritize_crystals.value == self.options.prioritize_crystals.option_true:
- self.options.priority_locations.value = self.options.priority_locations.value.union(get_location_names_per_category()["Crystals"])
-
- self.multiworld.push_precollected(self.create_item(HOME_POINT_STONE))
- self.multiworld.push_precollected(self.create_item(ARCHIPELAGO_STONE))
-
- # Checks if starting jobs is empty and if so fills it. If this is UT re-gen it won't be empty
- if not self.starting_jobs:
- self.starting_jobs = get_starting_jobs(self)
- for job in self.starting_jobs:
- self.multiworld.push_precollected(self.create_item(job))
-
- if self.options.start_with_treasure_finder.value:
- self.multiworld.push_precollected(self.create_item(TREASURE_FINDER))
-
- if self.options.start_with_maps.value == self.options.start_with_maps.option_true:
- for map_name in self.item_name_groups[MAP]:
- self.multiworld.push_precollected(self.create_item(map_name))
-
- if not self.options.level_gating.value == self.options.level_gating.option_none:
- #3 is Spawning Meadows' level
- starting_level_so_you_can_do_anything = 3 + self.options.level_compared_to_enemies.value
- if starting_level_so_you_can_do_anything < 3:
- starting_level_so_you_can_do_anything = 3
- elif starting_level_so_you_can_do_anything > self.options.max_level.value:
- starting_level_so_you_can_do_anything = self.options.max_level.value
- # Players start with at least 1 Progressive Level, but we add more if their Level Compared to Enemies setting is positive
- self.starting_progressive_levels = ((starting_level_so_you_can_do_anything - 1) // self.options.progressive_level_size.value) + 1
- for _ in range(self.starting_progressive_levels):
- self.multiworld.push_precollected(self.create_item(PROGRESSIVE_LEVEL))
-
- def create_regions(self) -> None:
- init_ap_region_to_display_region_dictionary()
-
- locations = get_treasure_and_npc_locations(self.player, self.options)
- locations.extend(get_crystal_locations(self.player, self.options))
-
- if self.options.kill_bosses_mode.value == self.options.kill_bosses_mode.option_true:
- bosses = get_boss_locations(self.player, self.options)
- locations.extend(bosses)
-
- if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
- shops = get_shop_locations(self.player, self.options)
- locations.extend(shops)
-
- if self.options.use_mods.value == self.options.use_mods.option_true:
- for modded_location in self.modded_locations:
- location = LocationData(display_region_subregions_dictionary[modded_location.display_region][0],
- modded_location.name,
- modded_location.code,
- build_condition_rule(modded_location.display_region, modded_location.rule_condition, self))
- locations.append(location)
-
- if self.options.use_mods.value == self.options.use_mods.option_true and self.options.shopsanity.value != self.options.shopsanity.option_disabled:
- for shop in self.modded_shops:
- location = LocationData(display_region_subregions_dictionary[shop.display_region][0],
- shop.name,
- shop.code,
- build_condition_rule(shop.display_region, shop.rule_condition, self))
- locations.append(location)
-
- if self.options.use_mods.value == self.options.use_mods.option_true and self.options.kill_bosses_mode.value == self.options.kill_bosses_mode.option_true:
- for modded_location in self.modded_bosses:
- location = LocationData(display_region_subregions_dictionary[modded_location.display_region][0],
- modded_location.name,
- modded_location.code,
- build_condition_rule(modded_location.display_region, modded_location.rule_condition, self))
- locations.append(location)
-
- if self.options.use_mods.value == self.options.use_mods.option_true:
- for removed_location in self.removed_locations:
- for location in locations:
- if location.name == removed_location.name:
- locations.remove(location)
-
- #Regionsanity completion locations need to be added after all other locations so they can be removed if the region is empty (e.g. Neptune Shrine w/o Shopsanity)
- if self.options.regionsanity.value != self.options.regionsanity.option_disabled:
- region_completions = get_region_completion_locations(self.player, self.options)
- for completion in region_completions:
- display_region_empty = True
- for location in locations:
- if ap_region_to_display_region_dictionary[completion.ap_region] == ap_region_to_display_region_dictionary[location.ap_region]:
- display_region_empty = False
- break
- if display_region_empty:
- region_completions.remove(completion)
- locations.extend(region_completions)
-
- init_areas(self, locations, self.options)
-
- if self.options.job_rando.value == self.options.job_rando.option_none:
- jobs_earnable, self.jobs_not_to_exclude = set_jobs_at_default_locations(self, self.player_name)
- if self.options.use_mods.value == self.options.use_mods.option_true:
- jobs_earnable += self.modded_job_count
- else:
- jobs_earnable = len(self.item_name_groups[JOB]) - len(self.starting_jobs)
- if self.options.use_mods.value == self.options.use_mods.option_false:
- jobs_earnable -= self.modded_job_count
-
- if (self.options.goal.value == self.options.goal.option_astley or self.options.goal.value == self.options.goal.option_true_astley) and self.options.new_world_stone_job_quantity.value > jobs_earnable:
- message = "For player {2}: newWorldStoneJobQuantity was set to {0} but your options only had {1} jobs in pool. Reduced newWorldStoneJobQuantity to {1}."
- logging.getLogger().info(message.format(self.options.new_world_stone_job_quantity.value, jobs_earnable, self.player_name))
- self.options.new_world_stone_job_quantity.value = jobs_earnable
-
- # pick one display region to give a starting pass to and then save that later
- if self.options.regionsanity.value != self.options.regionsanity.option_disabled:
- starting_passes_list: List[str] = []
- #checking the start inventory for region passes and using the first one to set the starter region, if any
- for item_name in self.options.start_inventory.keys():
- if item_name in self.item_name_groups[PASS]:
- starting_passes_list.append(item_name)
- for item_name in self.options.start_inventory_from_pool.keys():
- if item_name in self.item_name_groups[PASS]:
- starting_passes_list.append(item_name)
- if len(starting_passes_list) > 0:
- for display_region_name in display_region_name_to_pass_dict:
- if display_region_name_to_pass_dict[display_region_name] == starting_passes_list[0]:
- # The first subregion (AP Region) in a display region will be the starter region if a player puts that display region's pass in their starting inventory
- self.starter_ap_region = display_region_subregions_dictionary[display_region_name][0]
- break
-
- # If this is UT re-gen the value isn't empty and we skip trying to pick a starter_region since we already have one
- if self.starter_ap_region == "":
- initially_reachable_regions = []
- # Generate a collection state that is a copy of the current state but also has all the passes so we can
- # check what regions we can access without just getting told none because we have no passes
- all_passes_state: CollectionState = CollectionState(self.multiworld)
- self.origin_region_name = SPAWNING_MEADOWS_AP_REGION
- for region_pass in self.item_name_groups[PASS]:
- all_passes_state.collect(self.create_item(region_pass), prevent_sweep=True)
- for ap_region in self.get_regions():
- #This checks what AP Regions are accessible to the player
- if ap_region.can_reach(all_passes_state) and ap_region.name != MENU_AP_REGION and ap_region.name != MODDED_ZONE_AP_REGION:
- if len(ap_region.locations) > 2:
- initially_reachable_regions.append(ap_region)
- self.starter_ap_region = self.random.choice(initially_reachable_regions).name
- #Until we have a teleport location in every single ap region, for now we take specifically the first ap region in the display regions subregion list
- self.starter_ap_region = display_region_subregions_dictionary[ap_region_to_display_region_dictionary[self.starter_ap_region]][0]
- logging.getLogger().info("Starting region is " + self.starter_ap_region)
- self.origin_region_name = self.starter_ap_region
- #only push if player doesn't already have the pass from their starting inventory
- if len(starting_passes_list) == 0:
- #Converts the AP Region that was picked as the starting region to the Display Region containing that AP Region
- self.multiworld.push_precollected(self.create_item(display_region_name_to_pass_dict[ap_region_to_display_region_dictionary[self.starter_ap_region]]))
-
- def create_item(self, name: str) -> Item:
- if name in item_table:
- data = item_table[name]
- return Item(name, data.classification, data.code, self.player)
- else:
- matches = [item for (index, item) in enumerate(self.modded_items) if item.name == name]
- return Item(matches[0].name, matches[0].classification, matches[0].code, self.player)
-
- def create_items(self) -> None:
- pool = self.get_item_pool(self.get_excluded_items())
-
- self.multiworld.itempool += pool
-
- NON_CLAM_GOAL_CLAMSHELLS_GOAL = 2
- NON_CLAM_GOAL_CLAMSHELLS_TOTAL = 3
-
- def get_goal_clamshells(self) -> int:
- goal_clamshell_quantity = self.NON_CLAM_GOAL_CLAMSHELLS_GOAL
- if self.options.goal.value == self.options.goal.option_clamshells:
- goal_clamshell_quantity = self.options.clamshell_goal_quantity.value
- return goal_clamshell_quantity
-
- def get_extra_clamshells(self) -> int:
- extra_clamshells = self.NON_CLAM_GOAL_CLAMSHELLS_TOTAL
- if self.options.goal.value == self.options.goal.option_clamshells:
- extra_clamshells = self.options.extra_clamshells_in_pool.value
- return extra_clamshells
-
- def get_total_clamshells(self, max_clamshells: int) -> int:
- total_clamshell_quantity = self.NON_CLAM_GOAL_CLAMSHELLS_TOTAL
- if self.options.goal.value == self.options.goal.option_clamshells:
- total_clamshell_quantity = self.options.clamshell_goal_quantity.value + self.options.extra_clamshells_in_pool.value
- # If the player's options ask to put more clamshells in the pool than there is room, reduce their options proportionally so they fit
- if total_clamshell_quantity > max_clamshells:
- percent_goal_clamshells = self.options.clamshell_goal_quantity.value / total_clamshell_quantity
- self.options.clamshell_goal_quantity.value = int(percent_goal_clamshells * max_clamshells)
- if self.options.clamshell_goal_quantity.value < 2:
- self.options.clamshell_goal_quantity.value = 2
- self.options.extra_clamshells_in_pool.value = int(max_clamshells - self.options.clamshell_goal_quantity.value)
- if self.options.extra_clamshells_in_pool.value < 0:
- self.options.extra_clamshells_in_pool.value = 0
-
- # Log the change to player settings
- message = ("For player {2}: total_clamshells was {0} but there was only room for {1} clamshells in the pool. "
- "Reduced clamshellGoalQuantity to {3} and extraClamshellsInPool to {4}.")
- logging.getLogger().info(message.format(total_clamshell_quantity, max_clamshells, self.player_name,
- self.options.clamshell_goal_quantity.value, self.options.extra_clamshells_in_pool.value))
-
- total_clamshell_quantity = self.options.clamshell_goal_quantity.value + self.options.extra_clamshells_in_pool.value
- return total_clamshell_quantity
-
- def get_total_progressive_levels(self, max_progressive_levels: int) -> int:
- if max_progressive_levels < 1:
- max_progressive_levels = 1
- # this formula is how we can do ceiling division in Python
- progressive_levels = -(self.options.max_level.value // -self.options.progressive_level_size.value)
- #don't forget to -1
- if progressive_levels > max_progressive_levels:
- potential_progressive_level_size = -(self.options.max_level.value // -max_progressive_levels)
- potential_max_level = self.options.max_level.value
-
- if potential_progressive_level_size > self.options.progressive_level_size.range_end:
- potential_progressive_level_size = self.options.progressive_level_size.range_end
- potential_max_level = max_progressive_levels * potential_progressive_level_size
-
- if self.options.max_level.value > potential_max_level:
- raise Exception(f"For player {self.player_name}: yaml settings were too restrictive. Needed at least {-(self.options.max_level.value // -potential_progressive_level_size)} Progressive Levels, but only room for {max_progressive_levels} Progressive Levels in the pool. "
- f"This is usually caused by mods that add more items than locations. Change settings and regenerate.")
- else:
- message = (f"For player {self.player_name}: yaml settings were too restrictive. Only room for {max_progressive_levels} Progressive Levels in the pool. "
- f"Increased progressive_level_size to {potential_progressive_level_size}.")
- logging.getLogger().info(message)
-
- self.options.progressive_level_size.value = potential_progressive_level_size
-
- progressive_levels = -(self.options.max_level.value // -self.options.progressive_level_size.value)
- return progressive_levels
-
- #making randomized scholar ability pool
- def get_optional_scholar_abilities(self, count: int):
- return self.random.sample(optional_scholar_abilities, count)
-
- def get_filler_item_name(self) -> str:
- trap_chance: int = self.options.trap_likelihood.value
-
- if trap_chance > 0 and self.random.random() < (trap_chance / 100):
- return self.random.choice(list(self.item_name_groups[TRAP]))
- else:
- return self.random.choice(filler_items)
-
- def get_excluded_items(self) -> Set[str]:
- excluded_items: Set[str] = set()
- excluded_items.add(HOME_POINT_STONE)
- excluded_items.add(ARCHIPELAGO_STONE)
-
- for job in self.starting_jobs:
- excluded_items.add(job)
-
- if self.options.progressive_mount_mode.value == self.options.progressive_mount_mode.option_true:
- for mount in self.item_name_groups[MOUNT]:
- if mount != PROGRESSIVE_MOUNT:
- excluded_items.add(mount)
- else:
- excluded_items.add(PROGRESSIVE_MOUNT)
-
- if self.options.level_gating.value == self.options.level_gating.option_none:
- excluded_items.add(PROGRESSIVE_LEVEL)
-
- if self.options.start_with_treasure_finder.value == self.options.start_with_treasure_finder.option_true:
- excluded_items.add(TREASURE_FINDER)
-
- if self.options.start_with_maps.value == self.options.start_with_maps.option_true:
- for map_name in self.item_name_groups[MAP]:
- excluded_items.add(map_name)
-
- if self.options.goal.value == self.options.goal.option_astley:
- excluded_items.add(NEW_WORLD_STONE)
-
- if self.options.goal.value == self.options.goal.option_true_astley:
- excluded_items.add(NEW_WORLD_STONE)
- excluded_items.add(OLD_WORLD_STONE)
-
- if self.options.include_summon_abilities.value == self.options.include_summon_abilities.option_false:
- for summon in self.item_name_groups[SUMMON]:
- excluded_items.add(summon)
-
- if self.options.include_scholar_abilities.value == self.options.include_scholar_abilities.option_false:
- for scholar_ability in self.item_name_groups[SCHOLAR_ABILITY]:
- excluded_items.add(scholar_ability)
-
- #Progressive Equipment Mode
- if self.options.progressive_equipment_mode.value == self.options.progressive_equipment_mode.option_false:
- for progressive_equipment_piece in progressive_equipment:
- excluded_items.add(progressive_equipment_piece)
- else:
- for equipment_piece in non_progressive_equipment:
- excluded_items.add(equipment_piece)
-
- #For non-keyring modes
- if (self.options.key_mode.value != self.options.key_mode.option_key_ring and
- self.options.key_mode.value != self.options.key_mode.option_key_ring_skelefree):
- for keyring in key_rings:
- excluded_items.add(keyring)
-
- #For non-vanilla key modes
- if (self.options.key_mode.value != self.options.key_mode.option_vanilla and
- self.options.key_mode.value != self.options.key_mode.option_vanilla_skelefree):
- for key in dungeon_keys:
- excluded_items.add(key)
-
- #For skeleton key mode
- if self.options.key_mode.value == self.options.key_mode.option_skeleton:
- for key in singleton_keys:
- excluded_items.add(key)
-
- if (self.options.key_mode.value == self.options.key_mode.option_vanilla_skelefree or
- self.options.key_mode.value == self.options.key_mode.option_key_ring_skelefree):
- excluded_items.add(SKELETON_KEY)
-
- if self.options.job_rando.value == self.options.job_rando.option_none:
- # Summoner job needs to be available if kill bosses mode is on.
- # If job rando is off and regions are beginner or advanced, then the summoner crystal would be missing. Do not exclude in this context.
- if (SUMMONER_JOB not in self.jobs_not_to_exclude and
- ((self.options.included_regions.value == self.options.included_regions.option_beginner or
- self.options.included_regions.value == self.options.included_regions.option_advanced) and
- self.options.kill_bosses_mode.value == self.options.kill_bosses_mode.option_true)):
- self.jobs_not_to_exclude.append(SUMMONER_JOB)
-
- job_crystal_dictionary: Dict[str, str] = job_crystal_beginner_dictionary.copy()
- job_crystal_dictionary.update(job_crystal_advanced_dictionary)
- job_crystal_dictionary.update(job_crystal_expert_dictionary)
-
- for job_name in job_crystal_dictionary.keys():
- if job_name not in self.jobs_not_to_exclude:
- excluded_items.add(job_name)
-
- #regionsanity items
- if self.options.regionsanity.value == self.options.regionsanity.option_disabled:
- for region_pass in self.item_name_groups[PASS]:
- excluded_items.add(region_pass)
- else:
- excluded_items.add(display_region_name_to_pass_dict[ap_region_to_display_region_dictionary[self.starter_ap_region]])
-
- return excluded_items
-
- def get_item_pool(self, excluded_items: Set[str]) -> List[Item]:
- pool: List[Item] = []
-
- for name, data in item_table.items():
- if name not in excluded_items:
- #Check region type and add the region-type amounts; then check Shopsanity and add the shop amounts
- amount:int = int(data.beginnerAmount or 0)
- if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
- amount = amount + int(data.beginnerShops or 0)
- if self.options.included_regions == self.options.included_regions.option_advanced:
- amount = amount + int(data.advancedAmount or 0)
- if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
- amount = amount + int(data.advancedShops or 0)
- elif self.options.included_regions == self.options.included_regions.option_expert:
- amount = amount + int(data.advancedAmount or 0) + int(data.expertAmount or 0)
- if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
- amount = amount + int(data.expertShops or 0)
- elif self.options.included_regions == self.options.included_regions.option_all:
- amount = amount + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
- #atm there are no end-game specific shopsanity items
- if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
- amount = amount + int(data.endGameShops or 0)
-
- # Make sure new world pass is included if regionsanity is on and its required for the goal
- if (self.options.regionsanity.value != self.options.regionsanity.option_disabled and
- (self.options.goal.value == self.options.goal.option_astley or self.options.goal.value == self.options.goal.option_true_astley) and
- name == THE_NEW_WORLD_PASS):
- amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
- # Same goes for old world pass, the depths pass, the deep sea pass, and the open sea pass (so you can get to Gabriel)
- elif (self.options.regionsanity.value != self.options.regionsanity.option_disabled and
- self.options.goal.value == self.options.goal.option_true_astley and
- (name == THE_OLD_WORLD_PASS or name == THE_DEPTHS_PASS or name == THE_DEEP_SEA_PASS or name == THE_OPEN_SEA_PASS)):
- amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
- # adds Astley goal required item if it doesn't already exist (aka the map!)
- if self.options.goal.value == self.options.goal.option_astley and name == THE_NEW_WORLD_MAP:
- amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
- # adds true Astley goal required items if they don't already exist (including maps!)
- if self.options.goal.value == self.options.goal.option_true_astley and (name == STEM_WARD or name == DEITY_EYE or name == THE_OLD_WORLD_MAP or name == THE_NEW_WORLD_MAP):
- amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
-
- for _ in range(amount):
- item = self.set_classifications(name)
- pool.append(item)
-
- #7 spells randomly chosen from the entire pool (they have Reverse Polarity as default to merc Gran)
- if self.options.included_regions == self.options.included_regions.option_beginner:
- for scholar_ability in self.get_optional_scholar_abilities(7):
- item = self.create_item(scholar_ability)
- pool.append(item)
-
- if self.options.use_mods:
- combined_locations: List[ModLocationData] = self.modded_locations
- combined_locations.extend(self.modded_shops)
-
- for modded_item in self.modded_items:
- update_item_classification(modded_item, [location.rule_condition for location in combined_locations], self)
- item = self.create_item(modded_item.name)
- pool.append(item)
-
- if not self.options.level_gating.value == self.options.level_gating.option_none:
- #guarantee space for 2 clamshells, one on the following line and one because you start with 1 progressive level already
- max_progressive_levels: int = len(self.multiworld.get_unfilled_locations(self.player)) - len(pool) - 1
- #players start with one or more, depending on their Level Compared to Enemies setting
- for _ in range (self.get_total_progressive_levels(max_progressive_levels) - self.starting_progressive_levels):
- item = self.set_classifications(PROGRESSIVE_LEVEL)
- pool.append(item)
-
- max_clamshells: int = len(self.multiworld.get_unfilled_locations(self.player)) - len(pool)
- for _ in range(self.get_total_clamshells(max_clamshells)):
- item = self.set_classifications(CLAMSHELL)
- pool.append(item)
-
- for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool)):
- item = self.create_item(self.get_filler_item_name())
- pool.append(item)
-
- return pool
-
- def set_classifications(self, name: str) -> Item:
- data = item_table[name]
- item = Item(name, data.classification, data.code, self.player)
-
- return item
-
- def set_rules(self) -> None:
- logic = CrystalProjectLogic(self.player, self.options)
- if self.options.goal == self.options.goal.option_astley:
- self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(THE_NEW_WORLD_AP_REGION, player=self.player)
- self.included_regions.append(THE_NEW_WORLD_DISPLAY_NAME)
- elif self.options.goal == self.options.goal.option_true_astley:
- self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(THE_OLD_WORLD_AP_REGION, player=self.player) and state.can_reach(THE_NEW_WORLD_AP_REGION, player=self.player)
- self.included_regions.append(THE_OLD_WORLD_DISPLAY_NAME)
- elif self.options.goal == self.options.goal.option_clamshells:
- self.multiworld.completion_condition[self.player] = lambda state: state.has(CLAMSHELL, self.player, self.options.clamshell_goal_quantity.value) and state.can_reach(SEASIDE_CLIFFS_AP_REGION, player=self.player)
-
- def get_job_id_list(self) -> List[int]:
- job_ids: List[int] = []
- for job in self.starting_jobs:
- job_ids.append(self.item_name_to_id[job])
-
- return job_ids
-
- # This is data that needs to be readable from within the modded version of the game.
- # Example job rando makes the crystals behave differently, so the game needs to know about it.
- def fill_slot_data(self) -> Dict[str, Any]:
- mod_info = []
- slot_data_locations = []
- slot_data_removed_locations = []
- if self.options.use_mods:
- for mod in self.mod_info:
- mod_info.append({ "Id": mod.mod_id, "Name": mod.mod_name, "LoadOrder": mod.load_order })
-
- for modded_location in self.modded_locations:
- slot_data_locations.append({"Id": modded_location.offsetless_code,
- "APRegion": display_region_subregions_dictionary[modded_location.display_region][0],
- "Name": modded_location.name,
- "Coordinates": modded_location.coordinates,
- "biomeId": modded_location.biomeId,
- "Rule": None })
- if self.options.shopsanity != self.options.shopsanity.option_disabled:
- for shop in self.modded_shops:
- slot_data_locations.append({ "Id": shop.offsetless_code,
- "APRegion": display_region_subregions_dictionary[shop.display_region][0],
- "Name": shop.name,
- "Coordinates": shop.coordinates,
- "BiomeId": shop.biomeId,
- "Rule": None })
-
- if self.options.kill_bosses_mode == self.options.kill_bosses_mode.option_true:
- for boss in self.modded_bosses:
- slot_data_locations.append({ "Id": boss.offsetless_code,
- "APRegion": display_region_subregions_dictionary[boss.display_region][0],
- "Name": boss.name,
- "Coordinates": boss.coordinates,
- "BiomeId": boss.biomeId,
- "Rule": None })
-
- for location in self.removed_locations:
- slot_data_removed_locations.append({"Id": location.code,
- "APRegion": location.ap_region})
-
- # look into replacing this big chonky return block with self.options.as_dict() and then just adding the extras to the dict after
- return {
- "apworldVersion": self.world_version.as_simple_string(),
- "goal": self.options.goal.value,
- "clamshellGoalQuantity": self.get_goal_clamshells(),
- "extraClamshellsInPool": self.get_extra_clamshells(),
- "jobGoalAmount": self.options.new_world_stone_job_quantity.value,
- "jobRando": self.options.job_rando.value,
- "startingJobsForUT": self.starting_jobs,
- "startingJobQuantity": self.options.starting_job_quantity.value,
- "randomizeStartingJobs": bool(self.options.job_rando.value == self.options.job_rando.option_full),
- "startingJobs": self.get_job_id_list(),
- "killBossesMode" : bool(self.options.kill_bosses_mode.value),
- "shopsanity": self.options.shopsanity.value,
- "regionsanity": self.options.regionsanity.value,
- "includedRegions": self.included_regions,
- "includedRegionsOption": self.options.included_regions.value,
- "progressiveMountMode": self.options.progressive_mount_mode.value,
- "levelGating": self.options.level_gating.value,
- "levelComparedToEnemies": self.options.level_compared_to_enemies.value,
- "progressiveLevelSize": self.options.progressive_level_size.value,
- "maxLevel": self.options.max_level.value,
- "keyMode": self.options.key_mode.value,
- "obscureRoutes": bool(self.options.obscure_routes.value),
- "autoSpendLP": bool(self.options.auto_spend_lp.value),
- "autoEquipPassives": bool(self.options.auto_equip_passives.value),
- "easyLeveling": bool(self.options.easy_leveling.value),
- "startWithMaps": bool(self.options.start_with_maps.value),
- "includeSummonAbilities": self.options.include_summon_abilities.value,
- "includeScholarAbilities": self.options.include_scholar_abilities.value,
- "itemInfoMode": self.options.item_info_mode.value,
- "randomizeMusic": bool(self.options.randomize_music.value),
- "useMods": self.options.use_mods.value,
- "modInfo": mod_info,
- "moddedLocations": slot_data_locations,
- "removedLocations": slot_data_removed_locations,
- # "moddedLocationsForUT": self.modded_locations,
- # "moddedShopsForUT": self.modded_shops,
- "starterRegion": self.starter_ap_region, # stored for UT re-gen
- "prefillMap": bool(self.options.fill_full_map.value),
- "disableSparks": bool(self.options.disable_sparks.value),
+import logging
+from .constants.mounts import *
+from .constants.jobs import *
+from .constants.keys import *
+from .constants.key_items import *
+from .constants.maps import *
+from .constants.ap_regions import *
+from .constants.display_regions import *
+from .constants.teleport_stones import *
+from .constants.item_groups import *
+from .constants.region_passes import *
+from .items import item_table, optional_scholar_abilities, get_random_starting_jobs, filler_items, \
+ get_item_names_per_category, progressive_equipment, non_progressive_equipment, get_starting_jobs, \
+ set_jobs_at_default_locations, default_starting_job_list, key_rings, dungeon_keys, singleton_keys, \
+ display_region_name_to_pass_dict, job_crystal_beginner_dictionary, job_crystal_advanced_dictionary, job_crystal_expert_dictionary
+from .locations import get_treasure_and_npc_locations, get_boss_locations, get_shop_locations, get_region_completion_locations, LocationData, get_location_names_per_category, \
+ get_location_name_to_id, get_crystal_locations
+from .presets import crystal_project_options_presets
+from .regions import init_ap_region_to_display_region_dictionary, init_areas, ap_region_to_display_region_dictionary, display_region_subregions_dictionary
+from .options import CrystalProjectOptions, IncludedRegions, create_option_groups
+from .rules import CrystalProjectLogic
+from .mod_helper import ModLocationData, get_modded_items, get_modded_locations, \
+ get_modded_shopsanity_locations, get_modded_bosses, build_condition_rule, update_item_classification, ModIncrementedIdData, get_mod_info, get_removed_locations
+from typing import List, Set, Dict, Any
+from worlds.AutoWorld import World, WebWorld
+from BaseClasses import Item, Tutorial, MultiWorld, CollectionState
+
+class CrystalProjectWeb(WebWorld):
+ theme = "jungle"
+ bug_report_page = "https://github.com/Emerassi/CrystalProjectAPWorld/issues"
+ setup_en = Tutorial(
+ "Mod Setup and Use Guide",
+ "A guide to setting up the Crystal Project Archipelago Mod.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["dragons but also rabbits"]
+ )
+
+ tutorials = [setup_en]
+ option_groups = create_option_groups()
+ options_presets = crystal_project_options_presets
+
+class CrystalProjectWorld(World):
+ """Crystal Project is a mix of old school job based jRPG mixed with a ton of 3D platforming and exploration."""
+ game = "Crystal Project"
+ options_dataclass = CrystalProjectOptions
+ options: CrystalProjectOptions
+ topology_present = True # show path to required location checks in spoiler
+
+ item_name_to_id = {item: item_table[item].code for item in item_table}
+ location_name_to_id = get_location_name_to_id()
+ item_name_groups = get_item_names_per_category()
+ location_name_groups = get_location_names_per_category()
+ base_game_jobs: set[str] = item_name_groups[JOB].copy()
+
+ mod_info = get_mod_info()
+ modded_items = get_modded_items(mod_info)
+ modded_job_count: int = 0
+
+ for modded_item in modded_items:
+ if modded_item.name in item_name_to_id and item_name_to_id[modded_item.name] != modded_item.code:
+ raise Exception(f"A modded item({modded_item.name}) with id {modded_item.code} tried to change the code of item_name_to_id and it can never change!")
+ item_name_to_id[modded_item.name] = modded_item.code
+ if 'Job' in modded_item.name:
+ item_name_groups.setdefault(JOB, set()).add(modded_item.name)
+ modded_job_count += 1
+ else:
+ item_name_groups.setdefault(MOD, set()).add(modded_item.name)
+
+ modded_locations = get_modded_locations(mod_info)
+
+ for modded_location in modded_locations:
+ location_name_to_id[modded_location.name] = modded_location.code
+
+ modded_shops = get_modded_shopsanity_locations(mod_info)
+
+ for modded_shop in modded_shops:
+ location_name_to_id[modded_shop.name] = modded_shop.code
+
+ modded_bosses = get_modded_bosses(mod_info)
+
+ for modded_boss in modded_bosses:
+ location_name_to_id[modded_boss.name] = modded_boss.code
+
+ removed_locations = get_removed_locations(mod_info)
+
+ web = CrystalProjectWeb()
+
+ # This is how we tell the Universal Tracker we want to use re_gen_passthrough
+ @staticmethod
+ def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]:
+ return slot_data
+
+ # and this is how we tell Universal Tracker we don't need the yaml
+ ut_can_gen_without_yaml = True
+
+ def __init__(self, multiworld: "MultiWorld", player: int):
+ super().__init__(multiworld, player)
+ self.starter_ap_region: str = ""
+ self.starting_jobs: List[str] = []
+ self.jobs_not_to_exclude: List[str] = []
+ self.included_regions: List[str] = []
+ self.statically_placed_jobs: int = 0
+ self.starting_progressive_levels: int = 1
+
+ def generate_early(self):
+ # implement .yaml-less Universal Tracker support
+ if hasattr(self.multiworld, "generation_is_fake"):
+ if hasattr(self.multiworld, "re_gen_passthrough"):
+ if "Crystal Project" in self.multiworld.re_gen_passthrough:
+ slot_data = self.multiworld.re_gen_passthrough["Crystal Project"]
+ self.options.goal.value = slot_data["goal"]
+ self.options.clamshell_goal_quantity.value = slot_data["clamshellGoalQuantity"]
+ self.options.extra_clamshells_in_pool.value = slot_data["extraClamshellsInPool"]
+ self.options.new_world_stone_job_quantity.value = slot_data["jobGoalAmount"]
+ self.options.job_rando.value = slot_data["jobRando"]
+ self.starting_jobs = slot_data["startingJobsForUT"]
+ self.options.starting_job_quantity.value = slot_data["startingJobQuantity"]
+ self.options.kill_bosses_mode.value = slot_data["killBossesMode"]
+ self.options.shopsanity.value = slot_data["shopsanity"]
+ self.options.regionsanity.value = slot_data["regionsanity"]
+ self.options.included_regions.value = slot_data["includedRegionsOption"]
+ self.options.progressive_mount_mode.value = slot_data["progressiveMountMode"]
+ self.options.level_gating.value = slot_data["levelGating"]
+ self.options.level_compared_to_enemies.value = slot_data["levelComparedToEnemies"]
+ self.options.progressive_level_size.value = slot_data["progressiveLevelSize"]
+ self.options.max_level.value = slot_data["maxLevel"]
+ self.options.key_mode.value = slot_data["keyMode"]
+ self.options.obscure_routes.value = slot_data["obscureRoutes"]
+ self.options.include_summon_abilities.value = slot_data["includeSummonAbilities"]
+ self.options.include_scholar_abilities.value = slot_data["includeScholarAbilities"]
+ self.options.use_mods.value = slot_data["useMods"]
+ # self.modded_locations = slot_data["moddedLocationsForUT"]
+ # self.modded_shops = slot_data["moddedShopsForUT"]
+ self.starter_ap_region = slot_data["starterRegion"]
+
+ if self.options.prioritize_crystals.value == self.options.prioritize_crystals.option_true:
+ self.options.priority_locations.value = self.options.priority_locations.value.union(get_location_names_per_category()["Crystals"])
+
+ self.multiworld.push_precollected(self.create_item(HOME_POINT_STONE))
+ self.multiworld.push_precollected(self.create_item(ARCHIPELAGO_STONE))
+
+ # Checks if starting jobs is empty and if so fills it. If this is UT re-gen it won't be empty
+ if not self.starting_jobs:
+ self.starting_jobs = get_starting_jobs(self)
+ for job in self.starting_jobs:
+ self.multiworld.push_precollected(self.create_item(job))
+
+ if self.options.start_with_treasure_finder.value:
+ self.multiworld.push_precollected(self.create_item(TREASURE_FINDER))
+
+ if self.options.start_with_maps.value == self.options.start_with_maps.option_true:
+ for map_name in self.item_name_groups[MAP]:
+ self.multiworld.push_precollected(self.create_item(map_name))
+
+ if not self.options.level_gating.value == self.options.level_gating.option_none:
+ #3 is Spawning Meadows' level
+ starting_level_so_you_can_do_anything = 3 + self.options.level_compared_to_enemies.value
+ if starting_level_so_you_can_do_anything < 3:
+ starting_level_so_you_can_do_anything = 3
+ elif starting_level_so_you_can_do_anything > self.options.max_level.value:
+ starting_level_so_you_can_do_anything = self.options.max_level.value
+ # Players start with at least 1 Progressive Level, but we add more if their Level Compared to Enemies setting is positive
+ self.starting_progressive_levels = ((starting_level_so_you_can_do_anything - 1) // self.options.progressive_level_size.value) + 1
+ for _ in range(self.starting_progressive_levels):
+ self.multiworld.push_precollected(self.create_item(PROGRESSIVE_LEVEL))
+
+ def create_regions(self) -> None:
+ init_ap_region_to_display_region_dictionary()
+
+ locations = get_treasure_and_npc_locations(self.player, self.options)
+ locations.extend(get_crystal_locations(self.player, self.options))
+
+ if self.options.kill_bosses_mode.value == self.options.kill_bosses_mode.option_true:
+ bosses = get_boss_locations(self.player, self.options)
+ locations.extend(bosses)
+
+ if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
+ shops = get_shop_locations(self.player, self.options)
+ locations.extend(shops)
+
+ if self.options.use_mods.value == self.options.use_mods.option_true:
+ for modded_location in self.modded_locations:
+ location = LocationData(display_region_subregions_dictionary[modded_location.display_region][0],
+ modded_location.name,
+ modded_location.code,
+ build_condition_rule(modded_location.display_region, modded_location.rule_condition, self))
+ locations.append(location)
+
+ if self.options.use_mods.value == self.options.use_mods.option_true and self.options.shopsanity.value != self.options.shopsanity.option_disabled:
+ for shop in self.modded_shops:
+ location = LocationData(display_region_subregions_dictionary[shop.display_region][0],
+ shop.name,
+ shop.code,
+ build_condition_rule(shop.display_region, shop.rule_condition, self))
+ locations.append(location)
+
+ if self.options.use_mods.value == self.options.use_mods.option_true and self.options.kill_bosses_mode.value == self.options.kill_bosses_mode.option_true:
+ for modded_location in self.modded_bosses:
+ location = LocationData(display_region_subregions_dictionary[modded_location.display_region][0],
+ modded_location.name,
+ modded_location.code,
+ build_condition_rule(modded_location.display_region, modded_location.rule_condition, self))
+ locations.append(location)
+
+ if self.options.use_mods.value == self.options.use_mods.option_true:
+ for removed_location in self.removed_locations:
+ for location in locations:
+ if location.name == removed_location.name:
+ locations.remove(location)
+
+ #Regionsanity completion locations need to be added after all other locations so they can be removed if the region is empty (e.g. Neptune Shrine w/o Shopsanity)
+ if self.options.regionsanity.value != self.options.regionsanity.option_disabled:
+ region_completions = get_region_completion_locations(self.player, self.options)
+ for completion in region_completions:
+ display_region_empty = True
+ for location in locations:
+ if ap_region_to_display_region_dictionary[completion.ap_region] == ap_region_to_display_region_dictionary[location.ap_region]:
+ display_region_empty = False
+ break
+ if display_region_empty:
+ region_completions.remove(completion)
+ locations.extend(region_completions)
+
+ init_areas(self, locations, self.options)
+
+ if self.options.job_rando.value == self.options.job_rando.option_none:
+ jobs_earnable, self.jobs_not_to_exclude = set_jobs_at_default_locations(self, self.player_name)
+ if self.options.use_mods.value == self.options.use_mods.option_true:
+ jobs_earnable += self.modded_job_count
+ else:
+ jobs_earnable = len(self.item_name_groups[JOB]) - len(self.starting_jobs)
+ if self.options.use_mods.value == self.options.use_mods.option_false:
+ jobs_earnable -= self.modded_job_count
+
+ if (self.options.goal.value == self.options.goal.option_astley or self.options.goal.value == self.options.goal.option_true_astley) and self.options.new_world_stone_job_quantity.value > jobs_earnable:
+ message = "For player {2}: newWorldStoneJobQuantity was set to {0} but your options only had {1} jobs in pool. Reduced newWorldStoneJobQuantity to {1}."
+ logging.getLogger().info(message.format(self.options.new_world_stone_job_quantity.value, jobs_earnable, self.player_name))
+ self.options.new_world_stone_job_quantity.value = jobs_earnable
+
+ # pick one display region to give a starting pass to and then save that later
+ if self.options.regionsanity.value != self.options.regionsanity.option_disabled:
+ starting_passes_list: List[str] = []
+ #checking the start inventory for region passes and using the first one to set the starter region, if any
+ for item_name in self.options.start_inventory.keys():
+ if item_name in self.item_name_groups[PASS]:
+ starting_passes_list.append(item_name)
+ for item_name in self.options.start_inventory_from_pool.keys():
+ if item_name in self.item_name_groups[PASS]:
+ starting_passes_list.append(item_name)
+ if len(starting_passes_list) > 0:
+ for display_region_name in display_region_name_to_pass_dict:
+ if display_region_name_to_pass_dict[display_region_name] == starting_passes_list[0]:
+ # The first subregion (AP Region) in a display region will be the starter region if a player puts that display region's pass in their starting inventory
+ self.starter_ap_region = display_region_subregions_dictionary[display_region_name][0]
+ break
+
+ # If this is UT re-gen the value isn't empty and we skip trying to pick a starter_region since we already have one
+ if self.starter_ap_region == "":
+ initially_reachable_regions = []
+ # Generate a collection state that is a copy of the current state but also has all the passes so we can
+ # check what regions we can access without just getting told none because we have no passes
+ all_passes_state: CollectionState = CollectionState(self.multiworld)
+ self.origin_region_name = SPAWNING_MEADOWS_AP_REGION
+ for region_pass in self.item_name_groups[PASS]:
+ all_passes_state.collect(self.create_item(region_pass), prevent_sweep=True)
+ for ap_region in self.get_regions():
+ #This checks what AP Regions are accessible to the player
+ if ap_region.can_reach(all_passes_state) and ap_region.name != MENU_AP_REGION and ap_region.name != MODDED_ZONE_AP_REGION:
+ if len(ap_region.locations) > 2:
+ initially_reachable_regions.append(ap_region)
+ self.starter_ap_region = self.random.choice(initially_reachable_regions).name
+ #Until we have a teleport location in every single ap region, for now we take specifically the first ap region in the display regions subregion list
+ self.starter_ap_region = display_region_subregions_dictionary[ap_region_to_display_region_dictionary[self.starter_ap_region]][0]
+ logging.getLogger().info("Starting region is " + self.starter_ap_region)
+ self.origin_region_name = self.starter_ap_region
+ #only push if player doesn't already have the pass from their starting inventory
+ if len(starting_passes_list) == 0:
+ #Converts the AP Region that was picked as the starting region to the Display Region containing that AP Region
+ self.multiworld.push_precollected(self.create_item(display_region_name_to_pass_dict[ap_region_to_display_region_dictionary[self.starter_ap_region]]))
+
+ def create_item(self, name: str) -> Item:
+ if name in item_table:
+ data = item_table[name]
+ return Item(name, data.classification, data.code, self.player)
+ else:
+ matches = [item for (index, item) in enumerate(self.modded_items) if item.name == name]
+ return Item(matches[0].name, matches[0].classification, matches[0].code, self.player)
+
+ def create_items(self) -> None:
+ pool = self.get_item_pool(self.get_excluded_items())
+
+ self.multiworld.itempool += pool
+
+ NON_CLAM_GOAL_CLAMSHELLS_GOAL = 2
+ NON_CLAM_GOAL_CLAMSHELLS_TOTAL = 3
+
+ def get_goal_clamshells(self) -> int:
+ goal_clamshell_quantity = self.NON_CLAM_GOAL_CLAMSHELLS_GOAL
+ if self.options.goal.value == self.options.goal.option_clamshells:
+ goal_clamshell_quantity = self.options.clamshell_goal_quantity.value
+ return goal_clamshell_quantity
+
+ def get_extra_clamshells(self) -> int:
+ extra_clamshells = self.NON_CLAM_GOAL_CLAMSHELLS_TOTAL
+ if self.options.goal.value == self.options.goal.option_clamshells:
+ extra_clamshells = self.options.extra_clamshells_in_pool.value
+ return extra_clamshells
+
+ def get_total_clamshells(self, max_clamshells: int) -> int:
+ total_clamshell_quantity = self.NON_CLAM_GOAL_CLAMSHELLS_TOTAL
+ if self.options.goal.value == self.options.goal.option_clamshells:
+ total_clamshell_quantity = self.options.clamshell_goal_quantity.value + self.options.extra_clamshells_in_pool.value
+ # If the player's options ask to put more clamshells in the pool than there is room, reduce their options proportionally so they fit
+ if total_clamshell_quantity > max_clamshells:
+ percent_goal_clamshells = self.options.clamshell_goal_quantity.value / total_clamshell_quantity
+ self.options.clamshell_goal_quantity.value = int(percent_goal_clamshells * max_clamshells)
+ if self.options.clamshell_goal_quantity.value < 2:
+ self.options.clamshell_goal_quantity.value = 2
+ self.options.extra_clamshells_in_pool.value = int(max_clamshells - self.options.clamshell_goal_quantity.value)
+ if self.options.extra_clamshells_in_pool.value < 0:
+ self.options.extra_clamshells_in_pool.value = 0
+
+ # Log the change to player settings
+ message = ("For player {2}: total_clamshells was {0} but there was only room for {1} clamshells in the pool. "
+ "Reduced clamshellGoalQuantity to {3} and extraClamshellsInPool to {4}.")
+ logging.getLogger().info(message.format(total_clamshell_quantity, max_clamshells, self.player_name,
+ self.options.clamshell_goal_quantity.value, self.options.extra_clamshells_in_pool.value))
+
+ total_clamshell_quantity = self.options.clamshell_goal_quantity.value + self.options.extra_clamshells_in_pool.value
+ return total_clamshell_quantity
+
+ def get_total_progressive_levels(self, max_progressive_levels: int) -> int:
+ if max_progressive_levels < 1:
+ max_progressive_levels = 1
+ # this formula is how we can do ceiling division in Python
+ progressive_levels = -(self.options.max_level.value // -self.options.progressive_level_size.value)
+ #don't forget to -1
+ if progressive_levels > max_progressive_levels:
+ potential_progressive_level_size = -(self.options.max_level.value // -max_progressive_levels)
+ potential_max_level = self.options.max_level.value
+
+ if potential_progressive_level_size > self.options.progressive_level_size.range_end:
+ potential_progressive_level_size = self.options.progressive_level_size.range_end
+ potential_max_level = max_progressive_levels * potential_progressive_level_size
+
+ if self.options.max_level.value > potential_max_level:
+ raise Exception(f"For player {self.player_name}: yaml settings were too restrictive. Needed at least {-(self.options.max_level.value // -potential_progressive_level_size)} Progressive Levels, but only room for {max_progressive_levels} Progressive Levels in the pool. "
+ f"This is usually caused by mods that add more items than locations. Change settings and regenerate.")
+ else:
+ message = (f"For player {self.player_name}: yaml settings were too restrictive. Only room for {max_progressive_levels} Progressive Levels in the pool. "
+ f"Increased progressive_level_size to {potential_progressive_level_size}.")
+ logging.getLogger().info(message)
+
+ self.options.progressive_level_size.value = potential_progressive_level_size
+
+ progressive_levels = -(self.options.max_level.value // -self.options.progressive_level_size.value)
+ return progressive_levels
+
+ #making randomized scholar ability pool
+ def get_optional_scholar_abilities(self, count: int):
+ return self.random.sample(optional_scholar_abilities, count)
+
+ def get_filler_item_name(self) -> str:
+ trap_chance: int = self.options.trap_likelihood.value
+
+ if trap_chance > 0 and self.random.random() < (trap_chance / 100):
+ return self.random.choice(list(self.item_name_groups[TRAP]))
+ else:
+ return self.random.choice(filler_items)
+
+ def get_excluded_items(self) -> Set[str]:
+ excluded_items: Set[str] = set()
+ excluded_items.add(HOME_POINT_STONE)
+ excluded_items.add(ARCHIPELAGO_STONE)
+
+ for job in self.starting_jobs:
+ excluded_items.add(job)
+
+ if self.options.progressive_mount_mode.value == self.options.progressive_mount_mode.option_true:
+ for mount in self.item_name_groups[MOUNT]:
+ if mount != PROGRESSIVE_MOUNT:
+ excluded_items.add(mount)
+ else:
+ excluded_items.add(PROGRESSIVE_MOUNT)
+
+ if self.options.level_gating.value == self.options.level_gating.option_none:
+ excluded_items.add(PROGRESSIVE_LEVEL)
+
+ if self.options.start_with_treasure_finder.value == self.options.start_with_treasure_finder.option_true:
+ excluded_items.add(TREASURE_FINDER)
+
+ if self.options.start_with_maps.value == self.options.start_with_maps.option_true:
+ for map_name in self.item_name_groups[MAP]:
+ excluded_items.add(map_name)
+
+ if self.options.goal.value == self.options.goal.option_astley:
+ excluded_items.add(NEW_WORLD_STONE)
+
+ if self.options.goal.value == self.options.goal.option_true_astley:
+ excluded_items.add(NEW_WORLD_STONE)
+ excluded_items.add(OLD_WORLD_STONE)
+
+ if self.options.include_summon_abilities.value == self.options.include_summon_abilities.option_false:
+ for summon in self.item_name_groups[SUMMON]:
+ excluded_items.add(summon)
+
+ if self.options.include_scholar_abilities.value == self.options.include_scholar_abilities.option_false:
+ for scholar_ability in self.item_name_groups[SCHOLAR_ABILITY]:
+ excluded_items.add(scholar_ability)
+
+ #Progressive Equipment Mode
+ if self.options.progressive_equipment_mode.value == self.options.progressive_equipment_mode.option_false:
+ for progressive_equipment_piece in progressive_equipment:
+ excluded_items.add(progressive_equipment_piece)
+ else:
+ for equipment_piece in non_progressive_equipment:
+ excluded_items.add(equipment_piece)
+
+ #For non-keyring modes
+ if (self.options.key_mode.value != self.options.key_mode.option_key_ring and
+ self.options.key_mode.value != self.options.key_mode.option_key_ring_skelefree):
+ for keyring in key_rings:
+ excluded_items.add(keyring)
+
+ #For non-vanilla key modes
+ if (self.options.key_mode.value != self.options.key_mode.option_vanilla and
+ self.options.key_mode.value != self.options.key_mode.option_vanilla_skelefree):
+ for key in dungeon_keys:
+ excluded_items.add(key)
+
+ #For skeleton key mode
+ if self.options.key_mode.value == self.options.key_mode.option_skeleton:
+ for key in singleton_keys:
+ excluded_items.add(key)
+
+ if (self.options.key_mode.value == self.options.key_mode.option_vanilla_skelefree or
+ self.options.key_mode.value == self.options.key_mode.option_key_ring_skelefree):
+ excluded_items.add(SKELETON_KEY)
+
+ if self.options.job_rando.value == self.options.job_rando.option_none:
+ # Summoner job needs to be available if kill bosses mode is on.
+ # If job rando is off and regions are beginner or advanced, then the summoner crystal would be missing. Do not exclude in this context.
+ if (SUMMONER_JOB not in self.jobs_not_to_exclude and
+ ((self.options.included_regions.value == self.options.included_regions.option_beginner or
+ self.options.included_regions.value == self.options.included_regions.option_advanced) and
+ self.options.kill_bosses_mode.value == self.options.kill_bosses_mode.option_true)):
+ self.jobs_not_to_exclude.append(SUMMONER_JOB)
+
+ job_crystal_dictionary: Dict[str, str] = job_crystal_beginner_dictionary.copy()
+ job_crystal_dictionary.update(job_crystal_advanced_dictionary)
+ job_crystal_dictionary.update(job_crystal_expert_dictionary)
+
+ for job_name in job_crystal_dictionary.keys():
+ if job_name not in self.jobs_not_to_exclude:
+ excluded_items.add(job_name)
+
+ #regionsanity items
+ if self.options.regionsanity.value == self.options.regionsanity.option_disabled:
+ for region_pass in self.item_name_groups[PASS]:
+ excluded_items.add(region_pass)
+ else:
+ excluded_items.add(display_region_name_to_pass_dict[ap_region_to_display_region_dictionary[self.starter_ap_region]])
+
+ return excluded_items
+
+ def get_item_pool(self, excluded_items: Set[str]) -> List[Item]:
+ pool: List[Item] = []
+
+ for name, data in item_table.items():
+ if name not in excluded_items:
+ #Check region type and add the region-type amounts; then check Shopsanity and add the shop amounts
+ amount:int = int(data.beginnerAmount or 0)
+ if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
+ amount = amount + int(data.beginnerShops or 0)
+ if self.options.included_regions == self.options.included_regions.option_advanced:
+ amount = amount + int(data.advancedAmount or 0)
+ if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
+ amount = amount + int(data.advancedShops or 0)
+ elif self.options.included_regions == self.options.included_regions.option_expert:
+ amount = amount + int(data.advancedAmount or 0) + int(data.expertAmount or 0)
+ if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
+ amount = amount + int(data.expertShops or 0)
+ elif self.options.included_regions == self.options.included_regions.option_all:
+ amount = amount + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
+ #atm there are no end-game specific shopsanity items
+ if self.options.shopsanity.value != self.options.shopsanity.option_disabled:
+ amount = amount + int(data.endGameShops or 0)
+
+ # Make sure new world pass is included if regionsanity is on and its required for the goal
+ if (self.options.regionsanity.value != self.options.regionsanity.option_disabled and
+ (self.options.goal.value == self.options.goal.option_astley or self.options.goal.value == self.options.goal.option_true_astley) and
+ name == THE_NEW_WORLD_PASS):
+ amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
+ # Same goes for old world pass, the depths pass, the deep sea pass, and the open sea pass (so you can get to Gabriel)
+ elif (self.options.regionsanity.value != self.options.regionsanity.option_disabled and
+ self.options.goal.value == self.options.goal.option_true_astley and
+ (name == THE_OLD_WORLD_PASS or name == THE_DEPTHS_PASS or name == THE_DEEP_SEA_PASS or name == THE_OPEN_SEA_PASS)):
+ amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
+ # adds Astley goal required item if it doesn't already exist (aka the map!)
+ if self.options.goal.value == self.options.goal.option_astley and name == THE_NEW_WORLD_MAP:
+ amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
+ # adds true Astley goal required items if they don't already exist (including maps!)
+ if self.options.goal.value == self.options.goal.option_true_astley and (name == STEM_WARD or name == DEITY_EYE or name == THE_OLD_WORLD_MAP or name == THE_NEW_WORLD_MAP):
+ amount = int(data.beginnerAmount or 0) + int(data.advancedAmount or 0) + int(data.expertAmount or 0) + int(data.endGameAmount or 0)
+
+ for _ in range(amount):
+ item = self.set_classifications(name)
+ pool.append(item)
+
+ #7 spells randomly chosen from the entire pool (they have Reverse Polarity as default to merc Gran)
+ if self.options.included_regions == self.options.included_regions.option_beginner:
+ for scholar_ability in self.get_optional_scholar_abilities(7):
+ item = self.create_item(scholar_ability)
+ pool.append(item)
+
+ if self.options.use_mods:
+ combined_locations: List[ModLocationData] = self.modded_locations
+ combined_locations.extend(self.modded_shops)
+
+ for modded_item in self.modded_items:
+ update_item_classification(modded_item, [location.rule_condition for location in combined_locations], self)
+ item = self.create_item(modded_item.name)
+ pool.append(item)
+
+ if not self.options.level_gating.value == self.options.level_gating.option_none:
+ #guarantee space for 2 clamshells, one on the following line and one because you start with 1 progressive level already
+ max_progressive_levels: int = len(self.multiworld.get_unfilled_locations(self.player)) - len(pool) - 1
+ #players start with one or more, depending on their Level Compared to Enemies setting
+ for _ in range (self.get_total_progressive_levels(max_progressive_levels) - self.starting_progressive_levels):
+ item = self.set_classifications(PROGRESSIVE_LEVEL)
+ pool.append(item)
+
+ max_clamshells: int = len(self.multiworld.get_unfilled_locations(self.player)) - len(pool)
+ for _ in range(self.get_total_clamshells(max_clamshells)):
+ item = self.set_classifications(CLAMSHELL)
+ pool.append(item)
+
+ for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool)):
+ item = self.create_item(self.get_filler_item_name())
+ pool.append(item)
+
+ return pool
+
+ def set_classifications(self, name: str) -> Item:
+ data = item_table[name]
+ item = Item(name, data.classification, data.code, self.player)
+
+ return item
+
+ def set_rules(self) -> None:
+ logic = CrystalProjectLogic(self.player, self.options)
+ if self.options.goal == self.options.goal.option_astley:
+ self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(THE_NEW_WORLD_AP_REGION, player=self.player)
+ self.included_regions.append(THE_NEW_WORLD_DISPLAY_NAME)
+ elif self.options.goal == self.options.goal.option_true_astley:
+ self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(THE_OLD_WORLD_AP_REGION, player=self.player) and state.can_reach(THE_NEW_WORLD_AP_REGION, player=self.player)
+ self.included_regions.append(THE_OLD_WORLD_DISPLAY_NAME)
+ elif self.options.goal == self.options.goal.option_clamshells:
+ self.multiworld.completion_condition[self.player] = lambda state: state.has(CLAMSHELL, self.player, self.options.clamshell_goal_quantity.value) and state.can_reach(SEASIDE_CLIFFS_AP_REGION, player=self.player)
+
+ def get_job_id_list(self) -> List[int]:
+ job_ids: List[int] = []
+ for job in self.starting_jobs:
+ job_ids.append(self.item_name_to_id[job])
+
+ return job_ids
+
+ # This is data that needs to be readable from within the modded version of the game.
+ # Example job rando makes the crystals behave differently, so the game needs to know about it.
+ def fill_slot_data(self) -> Dict[str, Any]:
+ mod_info = []
+ slot_data_locations = []
+ slot_data_removed_locations = []
+ if self.options.use_mods:
+ for mod in self.mod_info:
+ mod_info.append({ "Id": mod.mod_id, "Name": mod.mod_name, "LoadOrder": mod.load_order })
+
+ for modded_location in self.modded_locations:
+ slot_data_locations.append({"Id": modded_location.offsetless_code,
+ "APRegion": display_region_subregions_dictionary[modded_location.display_region][0],
+ "Name": modded_location.name,
+ "Coordinates": modded_location.coordinates,
+ "biomeId": modded_location.biomeId,
+ "Rule": None })
+ if self.options.shopsanity != self.options.shopsanity.option_disabled:
+ for shop in self.modded_shops:
+ slot_data_locations.append({ "Id": shop.offsetless_code,
+ "APRegion": display_region_subregions_dictionary[shop.display_region][0],
+ "Name": shop.name,
+ "Coordinates": shop.coordinates,
+ "BiomeId": shop.biomeId,
+ "Rule": None })
+
+ if self.options.kill_bosses_mode == self.options.kill_bosses_mode.option_true:
+ for boss in self.modded_bosses:
+ slot_data_locations.append({ "Id": boss.offsetless_code,
+ "APRegion": display_region_subregions_dictionary[boss.display_region][0],
+ "Name": boss.name,
+ "Coordinates": boss.coordinates,
+ "BiomeId": boss.biomeId,
+ "Rule": None })
+
+ for location in self.removed_locations:
+ slot_data_removed_locations.append({"Id": location.code,
+ "APRegion": location.ap_region})
+
+ # look into replacing this big chonky return block with self.options.as_dict() and then just adding the extras to the dict after
+ return {
+ "apworldVersion": self.world_version.as_simple_string(),
+ "goal": self.options.goal.value,
+ "clamshellGoalQuantity": self.get_goal_clamshells(),
+ "extraClamshellsInPool": self.get_extra_clamshells(),
+ "jobGoalAmount": self.options.new_world_stone_job_quantity.value,
+ "jobRando": self.options.job_rando.value,
+ "startingJobsForUT": self.starting_jobs,
+ "startingJobQuantity": self.options.starting_job_quantity.value,
+ "randomizeStartingJobs": bool(self.options.job_rando.value == self.options.job_rando.option_full),
+ "startingJobs": self.get_job_id_list(),
+ "killBossesMode" : bool(self.options.kill_bosses_mode.value),
+ "shopsanity": self.options.shopsanity.value,
+ "regionsanity": self.options.regionsanity.value,
+ "includedRegions": self.included_regions,
+ "includedRegionsOption": self.options.included_regions.value,
+ "progressiveMountMode": self.options.progressive_mount_mode.value,
+ "levelGating": self.options.level_gating.value,
+ "levelComparedToEnemies": self.options.level_compared_to_enemies.value,
+ "progressiveLevelSize": self.options.progressive_level_size.value,
+ "maxLevel": self.options.max_level.value,
+ "keyMode": self.options.key_mode.value,
+ "obscureRoutes": bool(self.options.obscure_routes.value),
+ "autoSpendLP": bool(self.options.auto_spend_lp.value),
+ "autoEquipPassives": bool(self.options.auto_equip_passives.value),
+ "easyLeveling": bool(self.options.easy_leveling.value),
+ "startWithMaps": bool(self.options.start_with_maps.value),
+ "includeSummonAbilities": self.options.include_summon_abilities.value,
+ "includeScholarAbilities": self.options.include_scholar_abilities.value,
+ "itemInfoMode": self.options.item_info_mode.value,
+ "randomizeMusic": bool(self.options.randomize_music.value),
+ "useMods": self.options.use_mods.value,
+ "modInfo": mod_info,
+ "moddedLocations": slot_data_locations,
+ "removedLocations": slot_data_removed_locations,
+ # "moddedLocationsForUT": self.modded_locations,
+ # "moddedShopsForUT": self.modded_shops,
+ "starterRegion": self.starter_ap_region, # stored for UT re-gen
+ "prefillMap": bool(self.options.fill_full_map.value),
+ "disableSparks": bool(self.options.disable_sparks.value),
}
\ No newline at end of file
diff --git a/worlds/crystal_project/archipelago.json b/worlds/crystal_project/archipelago.json
index cedeb018654d..c8466857640a 100644
--- a/worlds/crystal_project/archipelago.json
+++ b/worlds/crystal_project/archipelago.json
@@ -1 +1 @@
-{"game": "Crystal Project", "authors": ["Dragons but also Rabbits, FabulaFey, Emerassi"], "minimum_ap_version": "0.6.4", "world_version": "0.11.0", "compatible_version": 7, "version": 7}
\ No newline at end of file
+{"game": "Crystal Project", "authors": ["Dragons but also Rabbits, FabulaFey, Emerassi"], "minimum_ap_version": "0.6.4", "world_version": "0.11.4", "compatible_version": 7, "version": 7}
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/ap_regions.py b/worlds/crystal_project/constants/ap_regions.py
index bc0af8382000..d8b5a981c2b6 100644
--- a/worlds/crystal_project/constants/ap_regions.py
+++ b/worlds/crystal_project/constants/ap_regions.py
@@ -1,275 +1,275 @@
-#If making a new AP Region, remember to add it to display_regions
-#Make sure the teleport location constant variable name matches the AP Region it's in + _TELEPORT_LOCATION perfectly
-#Beginner
-MENU_AP_REGION = "Menu"
-SPAWNING_MEADOWS_AP_REGION = "Spawning Meadows"
-SPAWNING_MEADOWS_AP_REGION_TELEPORT_LOCATION = "0,99,0"
-#Delende start
-DELENDE_PLAINS_AP_REGION = "Delende"
-#Fish Hatchery save point
-DELENDE_PLAINS_AP_REGION_TELEPORT_LOCATION = "173,132,-194"
-ATOP_FISH_HATCHERY_AP_REGION = "Atop Fish Hatchery"
-DELENDE_HIGH_BRIDGES_AP_REGION = "Delende High Bridges"
-GRAN_AP_REGION = "Gran"
-BELOW_GRAN_AP_REGION = "Below Gran"
-DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION = "Delende Mesa Over Spooky Cave"
-HEART_TARN_AP_REGION = "Heart Tarn"
-DELENDE_PEAK_AP_REGION = "Delende Peak"
-#Delende end
-MERCURY_SHRINE_AP_REGION = "Mercury Shrine"
-MERCURY_SHRINE_AP_REGION_TELEPORT_LOCATION = "204,128,7"
-#Soiled Den start
-SOILED_DENLENDE_AP_REGION = "Soiled Den"
-SOILED_DENLENDE_AP_REGION_TELEPORT_LOCATION = "269,118,-139"
-THE_BANGLER_AP_REGION = "Huntie the Bangler's Seaside Cavern"
-THE_BANGLER_AP_REGION_TELEPORT_LOCATION = "318,113,-100"
-#Soiled Den End
-THE_PALE_GROTTO_AP_REGION = "The Pale Grotto"
-THE_PALE_GROTTO_AP_REGION_TELEPORT_LOCATION = "279,122,-327"
-#Seaside Cliffs start
-SEASIDE_CLIFFS_AP_REGION = "Seaside Cliffs"
-SEASIDE_CLIFFS_AP_REGION_TELEPORT_LOCATION = "308,119,-63"
-CLIFF_OVER_SEASIDE_CAMP_AP_REGION = "Cliff Over Seaside Camp"
-#Seaside Cliffs end
-DRAFT_SHAFT_CONDUIT_AP_REGION = "Draft Shaft Conduit"
-DRAFT_SHAFT_CONDUIT_AP_REGION_TELEPORT_LOCATION = "332,95,-33"
-#Yamagawa M.A. start
-YAMAGAWA_MA_AP_REGION = "Yamagawa M.A."
-YAMAGAWA_MA_AP_REGION_TELEPORT_LOCATION = "66,183,-95"
-FENCERS_KEEP_CHEST_AP_REGION = "Fencer's Keep Chest"
-#Yamagawa M.A. end
-#Proving Meadows start
-PROVING_MEADOWS_AP_REGION = "Proving Meadows"
-PROVING_MEADOWS_AP_REGION_TELEPORT_LOCATION = "394,109,-187"
-PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION = "Proving Meadows Skumparadise Connector"
-#Proving Meadows end
-SKUMPARADISE_AP_REGION = "Skumparadise"
-SKUMPARADISE_AP_REGION_TELEPORT_LOCATION = "442,102,-245"
-#Advanced
-#Capital Sequoia start
-CAPITAL_SEQUOIA_AP_REGION = "Capital Sequoia"
-CAPITAL_SEQUOIA_AP_REGION_TELEPORT_LOCATION = "409,169,-286"
-MOAT_SHALLOWS_AP_REGION = "Capital Moat Shallows"
-CAPITAL_MOAT_AP_REGION = "Capital Moat"
-BEATSMITH_DISCO_AP_REGION = "Beatsmith Disco"
-#Capital Sequoia end
-#Jojo Sewers start
-JOJO_SEWERS_AP_REGION = "Jojo Sewers Main (lol)"
-JOJO_SEWERS_AP_REGION_TELEPORT_LOCATION = "426,155,-300"
-SEWERS_SECRET_PASSWORD_AP_REGION = "Sewers Secret Password"
-SEWERS_TO_BOOMER_SOCIETY_AP_REGION = "Sewers to Boomer Society"
-#Jojo Sewers end
-BOOMER_SOCIETY_AP_REGION = "Boomer Society"
-BOOMER_SOCIETY_AP_REGION_TELEPORT_LOCATION = "335,195,-324"
-#Rolling Quintar Fields start
-ROLLING_QUINTAR_FIELDS_AP_REGION = "Rolling Quintar Fields"
-ROLLING_QUINTAR_FIELDS_AP_REGION_TELEPORT_LOCATION = "467,169,-287"
-ROLLING_TREETOP_HIGHWAY_AP_REGION = "Rolling Treetop Highway"
-SANCTUM_ENTRANCE_AP_REGION = "Quintar Sanctum Entrance"
-HUNTERS_TOWER_AP_REGION = "Hunter's Tower"
-#Rolling Quintar Fields end
-QUINTAR_NEST_AP_REGION = "Quintar Nest"
-QUINTAR_NEST_AP_REGION_TELEPORT_LOCATION = "505,156,-295"
-#Quintar Sanctum start
-QUINTAR_SANCTUM_AP_REGION = "Quintar Sanctum"
-QUINTAR_SANCTUM_AP_REGION_TELEPORT_LOCATION = "560,188,-478"
-SANCTUM_EXIT_CLIFFTOP_AP_REGION = "Quintar Sanctum Exit Clifftop"
-#Quintar Sanctum end
-#Capital Jail start
-CAPITAL_JAIL_AP_REGION = "Capital Jail"
-CAPITAL_JAIL_AP_REGION_TELEPORT_LOCATION = "405,95,-301"
-JAIL_SOUTH_WING_AP_REGION = "Capital Jail South Wing"
-JAIL_SOUTH_WING_RUBBLE_AP_REGION = "Capital Jail South Wing Rubble"
-#Capital Jail end
-#Capital Pipeline start
-PIPELINE_NORTH_AP_REGION = "Capital Pipeline North"
-PIPELINE_SOUTH_AP_REGION = "Capital Pipeline South"
-#East Pipeline home point
-PIPELINE_SOUTH_AP_REGION_TELEPORT_LOCATION = "653,66,66"
-PIPELINE_JIDAMBA_CONNECTOR_AP_REGION = "Capital Pipeline Jidamba Connector"
-#Capital Pipeline End
-COBBLESTONE_CRAG_AP_REGION = "Cobblestone Crag"
-COBBLESTONE_CRAG_AP_REGION_TELEPORT_LOCATION = "491,147,-189"
-OKIMOTO_NS_AP_REGION = "Okimoto N.S."
-OKIMOTO_NS_AP_REGION_TELEPORT_LOCATION = "628,160,-178"
-GREENSHIRE_REPRISE_AP_REGION = "Greenshire Reprise"
-GREENSHIRE_REPRISE_AP_REGION_TELEPORT_LOCATION = "286,160,-253"
-#Salmon Pass start
-SALMON_PASS_EAST_AP_REGION = "Salmon Pass East"
-SALMON_PASS_EAST_AP_REGION_TELEPORT_LOCATION = "222,161,-257"
-SALMON_PASS_WEST_AP_REGION = "Salmon Pass West"
-#Salmon Pass end
-#Salmon River start
-SALMON_RIVER_AP_REGION = "Salmon River"
-#Salmon Shack home point
-SALMON_RIVER_AP_REGION_TELEPORT_LOCATION = "121,174,-363"
-POSEIDON_SHRINE_PROPER_AP_REGION = "Poseidon Shrine"
-POSEIDON_SHRINE_ROOF_AP_REGION = "Poseidon Shrine Roof"
-RIVER_CATS_EGO_AP_REGION = "River Cat's Ego"
-#if you add a Mushroom Mountain Teleport location the rules around locations and regions need to be updated; e.g. the checks that require only fish no longer make sense
-MUSHROOM_MOUNTAIN_AP_REGION = "Mushroom Mountain"
-SOUTH_SALMON_RIVER_AP_REGION = "South Salmon River"
-#Salmon River end
-SHOUDU_WATERFRONT_AP_REGION = "Shoudu Waterfront"
-SHOUDU_WATERFRONT_AP_REGION_TELEPORT_LOCATION = "722,98,-168"
-#Poko Poko Desert start
-POKO_POKO_DESERT_AP_REGION = "Poko Poko Desert"
-#Mars Shrine entrance
-POKO_POKO_DESERT_AP_REGION_TELEPORT_LOCATION = "-189,111,-94"
-POKO_POKO_EAST_PLATEAU_AP_REGION = "Poko Poko Eastern Plateau"
-POKO_POKO_BEACH_WEST_PASS_AP_REGION = "Poko Poko Sara Sara Beach West Mountain Pass"
-TOWER_OF_ZOT_AP_REGION = "Tower of Zot"
-POKO_POKO_LAKE_DELENDE_PASS_AP_REGION = "Poko Poko Lake Delende Mountain Pass"
-POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION = "Poko Poko Spawning Meadows Mountain Pass"
-#Poko Poko Desert end
-SARA_SARA_BAZAAR_AP_REGION = "Sara Sara Bazaar"
-SARA_SARA_BAZAAR_AP_REGION_TELEPORT_LOCATION = "-137,101,54"
-SARA_SARA_BEACH_EAST_AP_REGION = "Sara Sara Beach East"
-SARA_SARA_BEACH_EAST_AP_REGION_TELEPORT_LOCATION = "-78,97,61"
-SARA_SARA_BEACH_WEST_AP_REGION = "Sara Sara Beach West"
-SARA_SARA_BEACH_WEST_AP_REGION_TELEPORT_LOCATION = "-265,97,-37"
-ANCIENT_RESERVOIR_AP_REGION = "Ancient Reservoir"
-ANCIENT_RESERVOIR_AP_REGION_TELEPORT_LOCATION = "-42,102,-136"
-IBEK_CAVE_AP_REGION = "Ibek Cave"
-#Salmon Bay start
-SALMON_BAY_AP_REGION = "Salmon Bay"
-SALMON_BAY_AP_REGION_TELEPORT_LOCATION = "-37,97,-265"
-SALMON_RIVER_MOUTH_AP_REGION = "Salmon River Mouth"
-#Salmon Bay end
-#Expert
-#The Open Sea start
-THE_OPEN_SEA_AP_REGION = "The Open Sea"
-THE_OPEN_SEA_AP_REGION_TELEPORT_LOCATION = "929,91,253"
-JIDAMBA_ATOLLS_AP_REGION = "Jidamba Atolls"
-BAZAAR_COAST_AP_REGION = "Sara Sara Bazaar Coast"
-#The Open Sea end
-SHOUDU_PROVINCE_AP_REGION = "Shoudu Province"
-SHOUDU_PROVINCE_AP_REGION_TELEPORT_LOCATION = "756,101,-166"
-THE_UNDERCITY_AP_REGION = "The Undercity"
-THE_UNDERCITY_AP_REGION_TELEPORT_LOCATION = "732,97,-197"
-GANYMEDE_SHRINE_AP_REGION = "Ganymede Shrine"
-GANYMEDE_SHRINE_AP_REGION_TELEPORT_LOCATION = "689,158,-240"
-BEAURIOR_VOLCANO_AP_REGION = "Beaurior Volcano"
-BEAURIOR_VOLCANO_AP_REGION_TELEPORT_LOCATION = "96,100,119"
-BEAURIOR_ROCK_AP_REGION = "Beaurior Rock"
-BEAURIOR_ROCK_AP_REGION_TELEPORT_LOCATION = "95,100,102"
-#Lake Delende start
-LAKE_DELENDE_AP_REGION = "Lake Delende"
-LAKE_DELENDE_AP_REGION_TELEPORT_LOCATION = "105,128,-201"
-ATOP_DAM_AP_REGION = "Atop Salmon River Dam"
-#Lake Delende end
-#Quintar Reserve start
-QUINTAR_RESERVE_AP_REGION = "Quintar Reserve"
-QUINTAR_RESERVE_AP_REGION_TELEPORT_LOCATION = "683,204,-437"
-RESERVE_TREETOPS_AP_REGION = "Quintar Reserve Treetops"
-#Quintar Reserve end
-#Dione Shrine start
-DIONE_SHRINE_AP_REGION = "Dione Shrine"
-DIONE_ROOF_AP_REGION = "Dione Shrine Roof"
-DIONE_ROOF_AP_REGION_TELEPORT_LOCATION = "680,221,-447"
-#Dione Shrine end
-QUINTAR_MAUSOLEUM_AP_REGION = "Quintar Mausoleum"
-QUINTAR_MAUSOLEUM_AP_REGION_TELEPORT_LOCATION = "700,181,-327"
-EASTERN_CHASM_AP_REGION = "Eastern Chasm"
-EASTERN_CHASM_AP_REGION_TELEPORT_LOCATION = "721,239,-466"
-#Tall, Tall Heights start
-BOOMER_OVERLOOK_AP_REGION = "Boomer Overlook"
-TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION = "Tall, Tall Ramparts Crag"
-GREENSHIRE_OVERLOOK_AP_REGION = "Greenshire Overlook"
-TALL_TALL_SAVE_POINT_AP_REGION = "Tall, Tall Heights Save Point"
-LOWER_ICE_LAKES_AP_REGION = "Lower Ice Lakes"
-#Ice Fisher's Hut
-LOWER_ICE_LAKES_AP_REGION_TELEPORT_LOCATION = "195,192,-435"
-UPPER_ICE_LAKES_AP_REGION = "Upper Ice Lakes"
-LANDS_END_COTTAGE_RIDGE_AP_REGION = "Land's End Cottage Ridge"
-TALL_TALL_DIAMONDSMITH_AP_REGION = "Tall, Tall Diamondsmith"
-SLIP_GLIDE_RIDE_EXIT_AP_REGION = "Slip Glide Ride Exit"
-SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION = "Slip to Cave or Souvenirs"
-SOUVENIR_SHOP_AP_REGION = "Souvenir Shop"
-SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION = "Sequoia Athenaeum Entrance"
-SEQUOIA_ATHENAEUM_BALCONY_AP_REGION = "Sequoia Athenaeum Balcony"
-TALL_TALL_TALL_CHEST_AP_REGION = "Tall, Tall Tall Chest"
-ICY_SPIKES_MADNESS_AP_REGION = "Icy Spikes Madness"
-PAMOA_TREE_AP_REGION = "Pamoa Tree"
-#Tall, Tall Heights end
-#Northern Cave Start
-UPPER_NORTHERN_CAVE_AP_REGION = "Upper Northern Cave"
-#Uppermost cave entrance
-UPPER_NORTHERN_CAVE_AP_REGION_TELEPORT_LOCATION = "274,205,-563"
-MIDDLE_NORTHERN_CAVE_AP_REGION = "Middle Northern Cave"
-ICE_CELL_AP_REGION = "Ice Cell"
-LOWER_NORTHERN_CAVE_AP_REGION = "Lower Northern Cave"
-#Northern Cave End
-#Land's End start
-LANDS_END_AP_REGION = "Land's End"
-#On the Land's End side of the broken bridge from Tall, Tall Heights
-LANDS_END_AP_REGION_TELEPORT_LOCATION = "124,192,-488"
-LANDS_END_NORTHERN_PEAK_AP_REGION = "Land's End Northern Peak"
-OWL_TREE_AP_REGION = "Owl Tree"
-#Land's End end
-#Slip Glide Ride start
-SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION = "Slip Glide Ride Entrance"
-SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION_TELEPORT_LOCATION = "119,182,-520"
-SLIP_GLIDE_RIDE_AP_REGION = "Slip Glide Ride"
-#Slip Glide Ride end
-SEQUOIA_ATHENAEUM_AP_REGION = "Sequoia Athenaeum"
-SEQUOIA_ATHENAEUM_AP_REGION_TELEPORT_LOCATION = "415,196,-546"
-#Northern Stretch start
-NORTHERN_STRETCH_RACE_START_AP_REGION = "Northern Stretch Race Start"
-NORTHERN_STRETCH_RACE_START_AP_REGION_TELEPORT_LOCATION = "211,222,-567"
-NORTHERN_STRETCH_RACE_FINISH_AP_REGION = "Northern Stretch Race Finish"
-SUMMONERS_TOWER_AP_REGION = "Summoner's Tower"
-#Northern Stretch end
-#Castle Ramparts start
-RAMPART_ATOP_PORTCULLIS_AP_REGION = "Rampart Atop Portcullis"
-LONE_CHEST_RAMPART_AP_REGION = "Lone Chest Rampart"
-RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION = "Ramparts Tall, Tall Traverse"
-PEAK_RAMPARTS_AP_REGION = "Peak Ramparts"
-#West Ramparts save point
-PEAK_RAMPARTS_AP_REGION_TELEPORT_LOCATION = "360,234,-392"
-#Castle Ramparts end
-THE_CHALICE_OF_TAR_AP_REGION = "The Chalice of Tar"
-THE_CHALICE_OF_TAR_AP_REGION_TELEPORT_LOCATION = "572,221,-426"
-FLYERS_CRAG_AP_REGION = "Flyer's Crag"
-FLYERS_CRAG_AP_REGION_TELEPORT_LOCATION = "657,217,-170"
-JIDAMBA_FOREST_FLOOR_AP_REGION = "Jidamba Forest Floor"
-JIDAMBA_DIAMONDSMITH_AP_REGION = "Jidamba Diamondsmith"
-JIDAMBA_SOUTHWEST_BEACH_AP_REGION = "Jidamba Southwest Beach"
-EUROPA_SHRINE_AP_REGION = "Europa Shrine"
-EUROPA_SHRINE_AP_REGION_TELEPORT_LOCATION = "762,100,43"
-JIDAMBA_CAVE_AP_REGION = "Jidamba Cave"
-JIDAMBA_SOUTH_CLIFF_AP_REGION = "Jidamba South Cliff"
-JIDAMBA_EACLANEYA_COURTYARD_AP_REGION = "Jidamba Eaclaneya Courtyard"
-JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION = "Jidamba Southwest Beach Cliff"
-JIDAMBA_SUMMIT_AP_REGION = "Jidamba Summit"
-JIDAMBA_CANOPY_AP_REGION = "Jidamba Canopy"
-JIDAMBA_WATERWAYS_AP_REGION = "Jidamba Waterways"
-TANGLE_EACLANEYA_CONNECTOR_AP_REGION = "Jidamba Tangle Eaclaneya Connector"
-JIDAMBA_EACLANEYA_AP_REGION = "Jidamba Eaclaneya"
-JIDAMBA_EACLANEYA_AP_REGION_TELEPORT_LOCATION = "739,103,124"
-THE_DEEP_SEA_AP_REGION = "The Deep Sea"
-THE_DEEP_SEA_AP_REGION_TELEPORT_LOCATION = "-323,67,-618"
-NEPTUNE_SHRINE_AP_REGION = "Neptune Shrine"
-NEPTUNE_SHRINE_AP_REGION_TELEPORT_LOCATION = "-270,58,221"
-JADE_CAVERN_AP_REGION = "Jade Cavern"
-JADE_CAVERN_AP_REGION_TELEPORT_LOCATION = "239,95,-113"
-JADE_WATERWAYS_AP_REGION = "Jade Waterways"
-JADE_WATERWAYS_AP_REGION_TELEPORT_LOCATION = "239,95,-113"
-CONTINENTAL_TRAM_AP_REGION = "Continental Tram"
-CONTINENTAL_TRAM_AP_REGION_TELEPORT_LOCATION = "-129,68,64"
-#End Game
-ANCIENT_LABYRINTH_AP_REGION = "Ancient Labyrinth"
-ANCIENT_LABYRINTH_AP_REGION_TELEPORT_LOCATION = "-179,142,-313"
-LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION = "Ancient Labyrinth Weird Rebar Hallway"
-LABYRINTH_CORE_AP_REGION = "Ancient Labyrinth Core"
-THE_SEQUOIA_AP_REGION = "The Sequoia"
-THE_SEQUOIA_AP_REGION_TELEPORT_LOCATION = "-263,58,-534"
-THE_DEPTHS_AP_REGION = "The Depths"
-THE_DEPTHS_AP_REGION_TELEPORT_LOCATION = "759,3,-611"
-CASTLE_SEQUOIA_AP_REGION = "Castle Sequoia"
-CASTLE_SEQUOIA_AP_REGION_TELEPORT_LOCATION = "406,180,-370"
-THE_OLD_WORLD_AP_REGION = "The Old World"
-THE_OLD_WORLD_AP_REGION_TELEPORT_LOCATION = "-233,12,184"
-THE_NEW_WORLD_AP_REGION = "The New World"
-THE_NEW_WORLD_AP_REGION_TELEPORT_LOCATION = "-70,11,-524"
+#If making a new AP Region, remember to add it to display_regions
+#Make sure the teleport location constant variable name matches the AP Region it's in + _TELEPORT_LOCATION perfectly
+#Beginner
+MENU_AP_REGION = "Menu"
+SPAWNING_MEADOWS_AP_REGION = "Spawning Meadows"
+SPAWNING_MEADOWS_AP_REGION_TELEPORT_LOCATION = "0,99,0"
+#Delende start
+DELENDE_PLAINS_AP_REGION = "Delende"
+#Fish Hatchery save point
+DELENDE_PLAINS_AP_REGION_TELEPORT_LOCATION = "173,132,-194"
+ATOP_FISH_HATCHERY_AP_REGION = "Atop Fish Hatchery"
+DELENDE_HIGH_BRIDGES_AP_REGION = "Delende High Bridges"
+GRAN_AP_REGION = "Gran"
+BELOW_GRAN_AP_REGION = "Below Gran"
+DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION = "Delende Mesa Over Spooky Cave"
+HEART_TARN_AP_REGION = "Heart Tarn"
+DELENDE_PEAK_AP_REGION = "Delende Peak"
+#Delende end
+MERCURY_SHRINE_AP_REGION = "Mercury Shrine"
+MERCURY_SHRINE_AP_REGION_TELEPORT_LOCATION = "204,128,7"
+#Soiled Den start
+SOILED_DENLENDE_AP_REGION = "Soiled Den"
+SOILED_DENLENDE_AP_REGION_TELEPORT_LOCATION = "269,118,-139"
+THE_BANGLER_AP_REGION = "Huntie the Bangler's Seaside Cavern"
+THE_BANGLER_AP_REGION_TELEPORT_LOCATION = "318,113,-100"
+#Soiled Den End
+THE_PALE_GROTTO_AP_REGION = "The Pale Grotto"
+THE_PALE_GROTTO_AP_REGION_TELEPORT_LOCATION = "279,122,-327"
+#Seaside Cliffs start
+SEASIDE_CLIFFS_AP_REGION = "Seaside Cliffs"
+SEASIDE_CLIFFS_AP_REGION_TELEPORT_LOCATION = "308,119,-63"
+CLIFF_OVER_SEASIDE_CAMP_AP_REGION = "Cliff Over Seaside Camp"
+#Seaside Cliffs end
+DRAFT_SHAFT_CONDUIT_AP_REGION = "Draft Shaft Conduit"
+DRAFT_SHAFT_CONDUIT_AP_REGION_TELEPORT_LOCATION = "332,95,-33"
+#Yamagawa M.A. start
+YAMAGAWA_MA_AP_REGION = "Yamagawa M.A."
+YAMAGAWA_MA_AP_REGION_TELEPORT_LOCATION = "66,183,-95"
+FENCERS_KEEP_CHEST_AP_REGION = "Fencer's Keep Chest"
+#Yamagawa M.A. end
+#Proving Meadows start
+PROVING_MEADOWS_AP_REGION = "Proving Meadows"
+PROVING_MEADOWS_AP_REGION_TELEPORT_LOCATION = "394,109,-187"
+PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION = "Proving Meadows Skumparadise Connector"
+#Proving Meadows end
+SKUMPARADISE_AP_REGION = "Skumparadise"
+SKUMPARADISE_AP_REGION_TELEPORT_LOCATION = "442,102,-245"
+#Advanced
+#Capital Sequoia start
+CAPITAL_SEQUOIA_AP_REGION = "Capital Sequoia"
+CAPITAL_SEQUOIA_AP_REGION_TELEPORT_LOCATION = "409,169,-286"
+MOAT_SHALLOWS_AP_REGION = "Capital Moat Shallows"
+CAPITAL_MOAT_AP_REGION = "Capital Moat"
+BEATSMITH_DISCO_AP_REGION = "Beatsmith Disco"
+#Capital Sequoia end
+#Jojo Sewers start
+JOJO_SEWERS_AP_REGION = "Jojo Sewers Main (lol)"
+JOJO_SEWERS_AP_REGION_TELEPORT_LOCATION = "426,155,-300"
+SEWERS_SECRET_PASSWORD_AP_REGION = "Sewers Secret Password"
+SEWERS_TO_BOOMER_SOCIETY_AP_REGION = "Sewers to Boomer Society"
+#Jojo Sewers end
+BOOMER_SOCIETY_AP_REGION = "Boomer Society"
+BOOMER_SOCIETY_AP_REGION_TELEPORT_LOCATION = "335,195,-324"
+#Rolling Quintar Fields start
+ROLLING_QUINTAR_FIELDS_AP_REGION = "Rolling Quintar Fields"
+ROLLING_QUINTAR_FIELDS_AP_REGION_TELEPORT_LOCATION = "467,169,-287"
+ROLLING_TREETOP_HIGHWAY_AP_REGION = "Rolling Treetop Highway"
+SANCTUM_ENTRANCE_AP_REGION = "Quintar Sanctum Entrance"
+HUNTERS_TOWER_AP_REGION = "Hunter's Tower"
+#Rolling Quintar Fields end
+QUINTAR_NEST_AP_REGION = "Quintar Nest"
+QUINTAR_NEST_AP_REGION_TELEPORT_LOCATION = "505,156,-295"
+#Quintar Sanctum start
+QUINTAR_SANCTUM_AP_REGION = "Quintar Sanctum"
+QUINTAR_SANCTUM_AP_REGION_TELEPORT_LOCATION = "560,188,-478"
+SANCTUM_EXIT_CLIFFTOP_AP_REGION = "Quintar Sanctum Exit Clifftop"
+#Quintar Sanctum end
+#Capital Jail start
+CAPITAL_JAIL_AP_REGION = "Capital Jail"
+CAPITAL_JAIL_AP_REGION_TELEPORT_LOCATION = "405,95,-301"
+JAIL_SOUTH_WING_AP_REGION = "Capital Jail South Wing"
+JAIL_SOUTH_WING_RUBBLE_AP_REGION = "Capital Jail South Wing Rubble"
+#Capital Jail end
+#Capital Pipeline start
+PIPELINE_NORTH_AP_REGION = "Capital Pipeline North"
+PIPELINE_SOUTH_AP_REGION = "Capital Pipeline South"
+#East Pipeline home point
+PIPELINE_SOUTH_AP_REGION_TELEPORT_LOCATION = "653,66,66"
+PIPELINE_JIDAMBA_CONNECTOR_AP_REGION = "Capital Pipeline Jidamba Connector"
+#Capital Pipeline End
+COBBLESTONE_CRAG_AP_REGION = "Cobblestone Crag"
+COBBLESTONE_CRAG_AP_REGION_TELEPORT_LOCATION = "491,147,-189"
+OKIMOTO_NS_AP_REGION = "Okimoto N.S."
+OKIMOTO_NS_AP_REGION_TELEPORT_LOCATION = "628,160,-178"
+GREENSHIRE_REPRISE_AP_REGION = "Greenshire Reprise"
+GREENSHIRE_REPRISE_AP_REGION_TELEPORT_LOCATION = "286,160,-253"
+#Salmon Pass start
+SALMON_PASS_EAST_AP_REGION = "Salmon Pass East"
+SALMON_PASS_EAST_AP_REGION_TELEPORT_LOCATION = "222,161,-257"
+SALMON_PASS_WEST_AP_REGION = "Salmon Pass West"
+#Salmon Pass end
+#Salmon River start
+SALMON_RIVER_AP_REGION = "Salmon River"
+#Salmon Shack home point
+SALMON_RIVER_AP_REGION_TELEPORT_LOCATION = "121,174,-363"
+POSEIDON_SHRINE_PROPER_AP_REGION = "Poseidon Shrine"
+POSEIDON_SHRINE_ROOF_AP_REGION = "Poseidon Shrine Roof"
+RIVER_CATS_EGO_AP_REGION = "River Cat's Ego"
+#if you add a Mushroom Mountain Teleport location the rules around locations and regions need to be updated; e.g. the checks that require only fish no longer make sense
+MUSHROOM_MOUNTAIN_AP_REGION = "Mushroom Mountain"
+SOUTH_SALMON_RIVER_AP_REGION = "South Salmon River"
+#Salmon River end
+SHOUDU_WATERFRONT_AP_REGION = "Shoudu Waterfront"
+SHOUDU_WATERFRONT_AP_REGION_TELEPORT_LOCATION = "722,98,-168"
+#Poko Poko Desert start
+POKO_POKO_DESERT_AP_REGION = "Poko Poko Desert"
+#Mars Shrine entrance
+POKO_POKO_DESERT_AP_REGION_TELEPORT_LOCATION = "-189,111,-94"
+POKO_POKO_EAST_PLATEAU_AP_REGION = "Poko Poko Eastern Plateau"
+POKO_POKO_BEACH_WEST_PASS_AP_REGION = "Poko Poko Sara Sara Beach West Mountain Pass"
+TOWER_OF_ZOT_AP_REGION = "Tower of Zot"
+POKO_POKO_LAKE_DELENDE_PASS_AP_REGION = "Poko Poko Lake Delende Mountain Pass"
+POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION = "Poko Poko Spawning Meadows Mountain Pass"
+#Poko Poko Desert end
+SARA_SARA_BAZAAR_AP_REGION = "Sara Sara Bazaar"
+SARA_SARA_BAZAAR_AP_REGION_TELEPORT_LOCATION = "-137,101,54"
+SARA_SARA_BEACH_EAST_AP_REGION = "Sara Sara Beach East"
+SARA_SARA_BEACH_EAST_AP_REGION_TELEPORT_LOCATION = "-78,97,61"
+SARA_SARA_BEACH_WEST_AP_REGION = "Sara Sara Beach West"
+SARA_SARA_BEACH_WEST_AP_REGION_TELEPORT_LOCATION = "-265,97,-37"
+ANCIENT_RESERVOIR_AP_REGION = "Ancient Reservoir"
+ANCIENT_RESERVOIR_AP_REGION_TELEPORT_LOCATION = "-42,102,-136"
+IBEK_CAVE_AP_REGION = "Ibek Cave"
+#Salmon Bay start
+SALMON_BAY_AP_REGION = "Salmon Bay"
+SALMON_BAY_AP_REGION_TELEPORT_LOCATION = "-37,97,-265"
+SALMON_RIVER_MOUTH_AP_REGION = "Salmon River Mouth"
+#Salmon Bay end
+#Expert
+#The Open Sea start
+THE_OPEN_SEA_AP_REGION = "The Open Sea"
+THE_OPEN_SEA_AP_REGION_TELEPORT_LOCATION = "929,91,253"
+JIDAMBA_ATOLLS_AP_REGION = "Jidamba Atolls"
+BAZAAR_COAST_AP_REGION = "Sara Sara Bazaar Coast"
+#The Open Sea end
+SHOUDU_PROVINCE_AP_REGION = "Shoudu Province"
+SHOUDU_PROVINCE_AP_REGION_TELEPORT_LOCATION = "756,101,-166"
+THE_UNDERCITY_AP_REGION = "The Undercity"
+THE_UNDERCITY_AP_REGION_TELEPORT_LOCATION = "732,97,-197"
+GANYMEDE_SHRINE_AP_REGION = "Ganymede Shrine"
+GANYMEDE_SHRINE_AP_REGION_TELEPORT_LOCATION = "689,158,-240"
+BEAURIOR_VOLCANO_AP_REGION = "Beaurior Volcano"
+BEAURIOR_VOLCANO_AP_REGION_TELEPORT_LOCATION = "96,100,119"
+BEAURIOR_ROCK_AP_REGION = "Beaurior Rock"
+BEAURIOR_ROCK_AP_REGION_TELEPORT_LOCATION = "95,100,102"
+#Lake Delende start
+LAKE_DELENDE_AP_REGION = "Lake Delende"
+LAKE_DELENDE_AP_REGION_TELEPORT_LOCATION = "105,128,-201"
+ATOP_DAM_AP_REGION = "Atop Salmon River Dam"
+#Lake Delende end
+#Quintar Reserve start
+QUINTAR_RESERVE_AP_REGION = "Quintar Reserve"
+QUINTAR_RESERVE_AP_REGION_TELEPORT_LOCATION = "683,204,-437"
+RESERVE_TREETOPS_AP_REGION = "Quintar Reserve Treetops"
+#Quintar Reserve end
+#Dione Shrine start
+DIONE_SHRINE_AP_REGION = "Dione Shrine"
+DIONE_ROOF_AP_REGION = "Dione Shrine Roof"
+DIONE_ROOF_AP_REGION_TELEPORT_LOCATION = "680,221,-447"
+#Dione Shrine end
+QUINTAR_MAUSOLEUM_AP_REGION = "Quintar Mausoleum"
+QUINTAR_MAUSOLEUM_AP_REGION_TELEPORT_LOCATION = "700,181,-327"
+EASTERN_CHASM_AP_REGION = "Eastern Chasm"
+EASTERN_CHASM_AP_REGION_TELEPORT_LOCATION = "721,239,-466"
+#Tall, Tall Heights start
+BOOMER_OVERLOOK_AP_REGION = "Boomer Overlook"
+TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION = "Tall, Tall Ramparts Crag"
+GREENSHIRE_OVERLOOK_AP_REGION = "Greenshire Overlook"
+TALL_TALL_SAVE_POINT_AP_REGION = "Tall, Tall Heights Save Point"
+LOWER_ICE_LAKES_AP_REGION = "Lower Ice Lakes"
+#Ice Fisher's Hut
+LOWER_ICE_LAKES_AP_REGION_TELEPORT_LOCATION = "195,192,-435"
+UPPER_ICE_LAKES_AP_REGION = "Upper Ice Lakes"
+LANDS_END_COTTAGE_RIDGE_AP_REGION = "Land's End Cottage Ridge"
+TALL_TALL_DIAMONDSMITH_AP_REGION = "Tall, Tall Diamondsmith"
+SLIP_GLIDE_RIDE_EXIT_AP_REGION = "Slip Glide Ride Exit"
+SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION = "Slip to Cave or Souvenirs"
+SOUVENIR_SHOP_AP_REGION = "Souvenir Shop"
+SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION = "Sequoia Athenaeum Entrance"
+SEQUOIA_ATHENAEUM_BALCONY_AP_REGION = "Sequoia Athenaeum Balcony"
+TALL_TALL_TALL_CHEST_AP_REGION = "Tall, Tall Tall Chest"
+ICY_SPIKES_MADNESS_AP_REGION = "Icy Spikes Madness"
+PAMOA_TREE_AP_REGION = "Pamoa Tree"
+#Tall, Tall Heights end
+#Northern Cave Start
+UPPER_NORTHERN_CAVE_AP_REGION = "Upper Northern Cave"
+#Uppermost cave entrance
+UPPER_NORTHERN_CAVE_AP_REGION_TELEPORT_LOCATION = "274,205,-563"
+MIDDLE_NORTHERN_CAVE_AP_REGION = "Middle Northern Cave"
+ICE_CELL_AP_REGION = "Ice Cell"
+LOWER_NORTHERN_CAVE_AP_REGION = "Lower Northern Cave"
+#Northern Cave End
+#Land's End start
+LANDS_END_AP_REGION = "Land's End"
+#On the Land's End side of the broken bridge from Tall, Tall Heights
+LANDS_END_AP_REGION_TELEPORT_LOCATION = "124,192,-488"
+LANDS_END_NORTHERN_PEAK_AP_REGION = "Land's End Northern Peak"
+OWL_TREE_AP_REGION = "Owl Tree"
+#Land's End end
+#Slip Glide Ride start
+SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION = "Slip Glide Ride Entrance"
+SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION_TELEPORT_LOCATION = "119,182,-520"
+SLIP_GLIDE_RIDE_AP_REGION = "Slip Glide Ride"
+#Slip Glide Ride end
+SEQUOIA_ATHENAEUM_AP_REGION = "Sequoia Athenaeum"
+SEQUOIA_ATHENAEUM_AP_REGION_TELEPORT_LOCATION = "415,196,-546"
+#Northern Stretch start
+NORTHERN_STRETCH_RACE_START_AP_REGION = "Northern Stretch Race Start"
+NORTHERN_STRETCH_RACE_START_AP_REGION_TELEPORT_LOCATION = "211,222,-567"
+NORTHERN_STRETCH_RACE_FINISH_AP_REGION = "Northern Stretch Race Finish"
+SUMMONERS_TOWER_AP_REGION = "Summoner's Tower"
+#Northern Stretch end
+#Castle Ramparts start
+RAMPART_ATOP_PORTCULLIS_AP_REGION = "Rampart Atop Portcullis"
+LONE_CHEST_RAMPART_AP_REGION = "Lone Chest Rampart"
+RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION = "Ramparts Tall, Tall Traverse"
+PEAK_RAMPARTS_AP_REGION = "Peak Ramparts"
+#West Ramparts save point
+PEAK_RAMPARTS_AP_REGION_TELEPORT_LOCATION = "360,234,-392"
+#Castle Ramparts end
+THE_CHALICE_OF_TAR_AP_REGION = "The Chalice of Tar"
+THE_CHALICE_OF_TAR_AP_REGION_TELEPORT_LOCATION = "572,221,-426"
+FLYERS_CRAG_AP_REGION = "Flyer's Crag"
+FLYERS_CRAG_AP_REGION_TELEPORT_LOCATION = "657,217,-170"
+JIDAMBA_FOREST_FLOOR_AP_REGION = "Jidamba Forest Floor"
+JIDAMBA_DIAMONDSMITH_AP_REGION = "Jidamba Diamondsmith"
+JIDAMBA_SOUTHWEST_BEACH_AP_REGION = "Jidamba Southwest Beach"
+EUROPA_SHRINE_AP_REGION = "Europa Shrine"
+EUROPA_SHRINE_AP_REGION_TELEPORT_LOCATION = "762,100,43"
+JIDAMBA_CAVE_AP_REGION = "Jidamba Cave"
+JIDAMBA_SOUTH_CLIFF_AP_REGION = "Jidamba South Cliff"
+JIDAMBA_EACLANEYA_COURTYARD_AP_REGION = "Jidamba Eaclaneya Courtyard"
+JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION = "Jidamba Southwest Beach Cliff"
+JIDAMBA_SUMMIT_AP_REGION = "Jidamba Summit"
+JIDAMBA_CANOPY_AP_REGION = "Jidamba Canopy"
+JIDAMBA_WATERWAYS_AP_REGION = "Jidamba Waterways"
+TANGLE_EACLANEYA_CONNECTOR_AP_REGION = "Jidamba Tangle Eaclaneya Connector"
+JIDAMBA_EACLANEYA_AP_REGION = "Jidamba Eaclaneya"
+JIDAMBA_EACLANEYA_AP_REGION_TELEPORT_LOCATION = "739,103,124"
+THE_DEEP_SEA_AP_REGION = "The Deep Sea"
+THE_DEEP_SEA_AP_REGION_TELEPORT_LOCATION = "-323,67,-618"
+NEPTUNE_SHRINE_AP_REGION = "Neptune Shrine"
+NEPTUNE_SHRINE_AP_REGION_TELEPORT_LOCATION = "-270,58,221"
+JADE_CAVERN_AP_REGION = "Jade Cavern"
+JADE_CAVERN_AP_REGION_TELEPORT_LOCATION = "239,95,-113"
+JADE_WATERWAYS_AP_REGION = "Jade Waterways"
+JADE_WATERWAYS_AP_REGION_TELEPORT_LOCATION = "239,95,-113"
+CONTINENTAL_TRAM_AP_REGION = "Continental Tram"
+CONTINENTAL_TRAM_AP_REGION_TELEPORT_LOCATION = "-129,68,64"
+#End Game
+ANCIENT_LABYRINTH_AP_REGION = "Ancient Labyrinth"
+ANCIENT_LABYRINTH_AP_REGION_TELEPORT_LOCATION = "-179,142,-313"
+LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION = "Ancient Labyrinth Weird Rebar Hallway"
+LABYRINTH_CORE_AP_REGION = "Ancient Labyrinth Core"
+THE_SEQUOIA_AP_REGION = "The Sequoia"
+THE_SEQUOIA_AP_REGION_TELEPORT_LOCATION = "-263,58,-534"
+THE_DEPTHS_AP_REGION = "The Depths"
+THE_DEPTHS_AP_REGION_TELEPORT_LOCATION = "759,3,-611"
+CASTLE_SEQUOIA_AP_REGION = "Castle Sequoia"
+CASTLE_SEQUOIA_AP_REGION_TELEPORT_LOCATION = "406,180,-370"
+THE_OLD_WORLD_AP_REGION = "The Old World"
+THE_OLD_WORLD_AP_REGION_TELEPORT_LOCATION = "-233,12,184"
+THE_NEW_WORLD_AP_REGION = "The New World"
+THE_NEW_WORLD_AP_REGION_TELEPORT_LOCATION = "-70,11,-524"
MODDED_ZONE_AP_REGION = "Modded Zone"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/biomes.py b/worlds/crystal_project/constants/biomes.py
index 2209f84263c6..a91b8d6f3266 100644
--- a/worlds/crystal_project/constants/biomes.py
+++ b/worlds/crystal_project/constants/biomes.py
@@ -1,193 +1,193 @@
-from .ap_regions import *
-from .display_regions import *
-
-SPAWNING_MEADOWS_IDS = {1}
-DELENDE_IDS = {2, 10, 21, 155, 184}
-SOILED_DEN_IDS = {49}
-PALE_GROTTO_IDS = {107, 123}
-SEASIDE_CLIFF_IDS = {3, 106, 128, 160, 164}
-DRAFT_SHAFT_CONDUITS_IDS = {6, 125, 126}
-MERCURY_SHRINE_IDS = {9, 159}
-YAMAGAWA_MA_IDS = {5, 127, 168}
-PROVING_MEADOWS_IDS = {7, 161}
-SKUMPARADISE_IDS = {11, 12, 182, 183}
-CAPITAL_IDS = {14, 37, 41, 48, 68, 102}
-JOJO_SEWERS_IDS = {16, 47}
-BOOMER_SOCIETY_IDS = {23}
-ROLLING_QUINTAR_FIELDS_IDS = {15, 25, 81, 108}
-QUINTAR_NEST_IDS = {18}
-QUINTAR_SANCTUM_IDS = {39, 40}
-CAPITAL_JAIL_IDS = {17, 193}
-CAPITAL_PIPELINE_IDS = {24, 99}
-COBBLESTONE_CRAG_IDS = {32}
-OKIMOTO_IDS = {20, 148, 192}
-GREENSHIRE_IDS = {13, 158}
-SALMON_PASS_IDS = {46}
-SALMON_RIVER_IDS = {28, 31, 78, 110}
-POKO_POKO_IDS = {26, 51, 124, 167, 170, 187, 195}
-SARA_SARA_IDS = {27, 122}
-SARA_SARA_BEACH_IDS = {33}
-ANCIENT_RESERVOIR_IDS = {30, 66, 85, 87}
-IBEK_CAVE_IDS = {86}
-SALMON_BAY_IDS = {60, 67}
-OPEN_SEA_IDS = {34, 129, 185}
-WATERFRONT_IDS = {115}
-SHOUDU_IDS = {38, 71, 83, 109}
-UNDERCITY_IDS = {43, 181}
-GANYMEDE_IDS = {50}
-BEAURIOR_VOLCANO_IDS = {44, 151}
-BEAURIOR_ROCK_IDS = {19, 70, 142, 144, 145, 146, 147}
-LAKE_DELENDE_IDS = {53}
-QUINTAR_RESERVE_IDS = {52}
-DIONE_SHRINE_IDS = {76, 105, 112, 130, 171, 173}
-QUINTAR_MAUSOLEUM_IDS = {143, 172}
-EASTERN_CHASM_IDS = {132, 134, 149, 152, 166}
-TALL_TALL_HEIGHTS_IDS = {8, 95, 117, 118, 153, 169, 179}
-NORTHERN_CAVE_IDS = {77}
-LANDS_END_IDS = {62, 63, 64, 65, 88, 116, 157}
-SLIP_GLIDE_RIDE_IDS = {36}
-SEQUOIA_ATHENAEUM_IDS = {90, 91, 174, 175, 176}
-NORTHERN_STRETCH_IDS = {131, 156, 163}
-CASTLE_RAMPARTS_IDS = {57}
-CHALICE_OF_TAR_IDS = {121}
-FLYERS_CRAG_IDS = {61}
-JIDAMBA_IDS = {29, 54, 72, 84, 165}
-EACLANEYA_IDS = {55, 89, 113, 114, 162}
-DEEP_SEA_IDS = {35, 82, 96, 98, 100, 103, 104, 120, 133, 186}
-NEPTUNE_SHRINE_IDS = {135}
-JADE_CAVERN_IDS = {154}
-CONTINENTAL_TRAM_IDS = {79, 80}
-ANCIENT_LABYRINTH_IDS = {56, 136, 137, 138, 139, 140, 141, 150}
-SEQUOIA_IDS = {22, 45, 92, 101, 188}
-DEPTHS_IDS = {42, 97}
-CASTLE_SEQUOIA_IDS = {58, 69, 73, 74, 75, 93, 94, 177}
-NEW_WORLD_IDS = {59, 111, 180}
-OLD_WORLD_IDS = {178}
-#TODO Underpass, overpass
-
-def get_display_region_by_id(region_id: int) -> str:
- if region_id in SPAWNING_MEADOWS_IDS:
- return SPAWNING_MEADOWS_DISPLAY_NAME
- if region_id in DELENDE_IDS:
- return DELENDE_DISPLAY_NAME
- if region_id in SOILED_DEN_IDS:
- return SOILED_DEN_DISPLAY_NAME
- if region_id in PALE_GROTTO_IDS:
- return THE_PALE_GROTTO_DISPLAY_NAME
- if region_id in SEASIDE_CLIFF_IDS:
- return SEASIDE_CLIFFS_DISPLAY_NAME
- if region_id in DRAFT_SHAFT_CONDUITS_IDS:
- return DRAFT_SHAFT_CONDUIT_DISPLAY_NAME
- if region_id in MERCURY_SHRINE_IDS:
- return MERCURY_SHRINE_DISPLAY_NAME
- if region_id in PROVING_MEADOWS_IDS:
- return PROVING_MEADOWS_DISPLAY_NAME
- if region_id in YAMAGAWA_MA_IDS:
- return YAMAGAWA_MA_DISPLAY_NAME
- if region_id in SKUMPARADISE_IDS:
- return SKUMPARADISE_DISPLAY_NAME
- if region_id in CAPITAL_IDS:
- return CAPITAL_SEQUOIA_DISPLAY_NAME
- if region_id in JOJO_SEWERS_IDS:
- return JOJO_SEWERS_DISPLAY_NAME
- if region_id in BOOMER_SOCIETY_IDS:
- return BOOMER_SOCIETY_DISPLAY_NAME
- if region_id in ROLLING_QUINTAR_FIELDS_IDS:
- return ROLLING_QUINTAR_FIELDS_DISPLAY_NAME
- if region_id in QUINTAR_NEST_IDS:
- return QUINTAR_NEST_DISPLAY_NAME
- if region_id in QUINTAR_SANCTUM_IDS:
- return QUINTAR_SANCTUM_DISPLAY_NAME
- if region_id in CAPITAL_JAIL_IDS:
- return CAPITAL_JAIL_DISPLAY_NAME
- if region_id in CAPITAL_PIPELINE_IDS:
- return CAPITAL_PIPELINE_DISPLAY_NAME
- if region_id in COBBLESTONE_CRAG_IDS:
- return COBBLESTONE_CRAG_DISPLAY_NAME
- if region_id in OKIMOTO_IDS:
- return OKIMOTO_NS_DISPLAY_NAME
- if region_id in GREENSHIRE_IDS:
- return GREENSHIRE_REPRISE_DISPLAY_NAME
- if region_id in SALMON_PASS_IDS:
- return SALMON_PASS_DISPLAY_NAME
- if region_id in SALMON_RIVER_IDS:
- return SALMON_RIVER_DISPLAY_NAME
- if region_id in POKO_POKO_IDS:
- return POKO_POKO_DESERT_DISPLAY_NAME
- if region_id in SARA_SARA_IDS:
- return SARA_SARA_BAZAAR_DISPLAY_NAME
- if region_id in SARA_SARA_BEACH_IDS:
- #No idea why there are two of these now, but we have no way to know so just pick one
- return SARA_SARA_BEACH_EAST_DISPLAY_NAME
- if region_id in ANCIENT_RESERVOIR_IDS:
- return ANCIENT_RESERVOIR_DISPLAY_NAME
- if region_id in SALMON_BAY_IDS:
- return SALMON_BAY_DISPLAY_NAME
- if region_id in OPEN_SEA_IDS:
- return THE_OPEN_SEA_DISPLAY_NAME
- if region_id in WATERFRONT_IDS:
- return SHOUDU_WATERFRONT_DISPLAY_NAME
- if region_id in SHOUDU_IDS:
- return SHOUDU_PROVINCE_DISPLAY_NAME
- if region_id in UNDERCITY_IDS:
- return THE_UNDERCITY_DISPLAY_NAME
- if region_id in GANYMEDE_IDS:
- return GANYMEDE_SHRINE_DISPLAY_NAME
- if region_id in BEAURIOR_VOLCANO_IDS:
- return BEAURIOR_VOLCANO_DISPLAY_NAME
- if region_id in BEAURIOR_ROCK_IDS:
- return BEAURIOR_ROCK_DISPLAY_NAME
- if region_id in LAKE_DELENDE_IDS:
- return LAKE_DELENDE_DISPLAY_NAME
- if region_id in QUINTAR_RESERVE_IDS:
- return QUINTAR_RESERVE_DISPLAY_NAME
- if region_id in DIONE_SHRINE_IDS:
- return DIONE_SHRINE_DISPLAY_NAME
- if region_id in QUINTAR_MAUSOLEUM_IDS:
- return QUINTAR_MAUSOLEUM_DISPLAY_NAME
- if region_id in EASTERN_CHASM_IDS:
- return EASTERN_CHASM_DISPLAY_NAME
- if region_id in TALL_TALL_HEIGHTS_IDS:
- return TALL_TALL_HEIGHTS_DISPLAY_NAME
- if region_id in NORTHERN_CAVE_IDS:
- return NORTHERN_CAVE_DISPLAY_NAME
- if region_id in LANDS_END_IDS:
- return LANDS_END_DISPLAY_NAME
- if region_id in SLIP_GLIDE_RIDE_IDS:
- return SLIP_GLIDE_RIDE_DISPLAY_NAME
- if region_id in SEQUOIA_ATHENAEUM_IDS:
- return SEQUOIA_ATHENAEUM_DISPLAY_NAME
- if region_id in NORTHERN_STRETCH_IDS:
- return NORTHERN_STRETCH_DISPLAY_NAME
- if region_id in CASTLE_RAMPARTS_IDS:
- return CASTLE_RAMPARTS_DISPLAY_NAME
- if region_id in CHALICE_OF_TAR_IDS:
- return THE_CHALICE_OF_TAR_DISPLAY_NAME
- if region_id in FLYERS_CRAG_IDS:
- return FLYERS_CRAG_DISPLAY_NAME
- if region_id in JIDAMBA_IDS:
- return JIDAMBA_TANGLE_DISPLAY_NAME
- if region_id in EACLANEYA_IDS:
- return JIDAMBA_EACLANEYA_DISPLAY_NAME
- if region_id in DEEP_SEA_IDS:
- return THE_DEEP_SEA_DISPLAY_NAME
- if region_id in NEPTUNE_SHRINE_IDS:
- return NEPTUNE_SHRINE_DISPLAY_NAME
- if region_id in JADE_CAVERN_IDS:
- return JADE_CAVERN_DISPLAY_NAME
- if region_id in CONTINENTAL_TRAM_IDS:
- return CONTINENTAL_TRAM_DISPLAY_NAME
- if region_id in ANCIENT_LABYRINTH_IDS:
- return ANCIENT_LABYRINTH_DISPLAY_NAME
- if region_id in SEQUOIA_IDS:
- return THE_SEQUOIA_DISPLAY_NAME
- if region_id in DEPTHS_IDS:
- return THE_DEPTHS_DISPLAY_NAME
- if region_id in CASTLE_SEQUOIA_IDS:
- return CASTLE_SEQUOIA_DISPLAY_NAME
- if region_id in NEW_WORLD_IDS:
- return THE_NEW_WORLD_DISPLAY_NAME
- if region_id in OLD_WORLD_IDS:
- return THE_OLD_WORLD_DISPLAY_NAME
-
+from .ap_regions import *
+from .display_regions import *
+
+SPAWNING_MEADOWS_IDS = {1}
+DELENDE_IDS = {2, 10, 21, 155, 184}
+SOILED_DEN_IDS = {49}
+PALE_GROTTO_IDS = {107, 123}
+SEASIDE_CLIFF_IDS = {3, 106, 128, 160, 164}
+DRAFT_SHAFT_CONDUITS_IDS = {6, 125, 126}
+MERCURY_SHRINE_IDS = {9, 159}
+YAMAGAWA_MA_IDS = {5, 127, 168}
+PROVING_MEADOWS_IDS = {7, 161}
+SKUMPARADISE_IDS = {11, 12, 182, 183}
+CAPITAL_IDS = {14, 37, 41, 48, 68, 102}
+JOJO_SEWERS_IDS = {16, 47}
+BOOMER_SOCIETY_IDS = {23}
+ROLLING_QUINTAR_FIELDS_IDS = {15, 25, 81, 108}
+QUINTAR_NEST_IDS = {18}
+QUINTAR_SANCTUM_IDS = {39, 40}
+CAPITAL_JAIL_IDS = {17, 193}
+CAPITAL_PIPELINE_IDS = {24, 99}
+COBBLESTONE_CRAG_IDS = {32}
+OKIMOTO_IDS = {20, 148, 192}
+GREENSHIRE_IDS = {13, 158}
+SALMON_PASS_IDS = {46}
+SALMON_RIVER_IDS = {28, 31, 78, 110}
+POKO_POKO_IDS = {26, 51, 124, 167, 170, 187, 195}
+SARA_SARA_IDS = {27, 122}
+SARA_SARA_BEACH_IDS = {33}
+ANCIENT_RESERVOIR_IDS = {30, 66, 85, 87}
+IBEK_CAVE_IDS = {86}
+SALMON_BAY_IDS = {60, 67}
+OPEN_SEA_IDS = {34, 129, 185}
+WATERFRONT_IDS = {115}
+SHOUDU_IDS = {38, 71, 83, 109}
+UNDERCITY_IDS = {43, 181}
+GANYMEDE_IDS = {50}
+BEAURIOR_VOLCANO_IDS = {44, 151}
+BEAURIOR_ROCK_IDS = {19, 70, 142, 144, 145, 146, 147}
+LAKE_DELENDE_IDS = {53}
+QUINTAR_RESERVE_IDS = {52}
+DIONE_SHRINE_IDS = {76, 105, 112, 130, 171, 173}
+QUINTAR_MAUSOLEUM_IDS = {143, 172}
+EASTERN_CHASM_IDS = {132, 134, 149, 152, 166}
+TALL_TALL_HEIGHTS_IDS = {8, 95, 117, 118, 153, 169, 179}
+NORTHERN_CAVE_IDS = {77}
+LANDS_END_IDS = {62, 63, 64, 65, 88, 116, 157}
+SLIP_GLIDE_RIDE_IDS = {36}
+SEQUOIA_ATHENAEUM_IDS = {90, 91, 174, 175, 176}
+NORTHERN_STRETCH_IDS = {131, 156, 163}
+CASTLE_RAMPARTS_IDS = {57}
+CHALICE_OF_TAR_IDS = {121}
+FLYERS_CRAG_IDS = {61}
+JIDAMBA_IDS = {29, 54, 72, 84, 165}
+EACLANEYA_IDS = {55, 89, 113, 114, 162}
+DEEP_SEA_IDS = {35, 82, 96, 98, 100, 103, 104, 120, 133, 186}
+NEPTUNE_SHRINE_IDS = {135}
+JADE_CAVERN_IDS = {154}
+CONTINENTAL_TRAM_IDS = {79, 80}
+ANCIENT_LABYRINTH_IDS = {56, 136, 137, 138, 139, 140, 141, 150}
+SEQUOIA_IDS = {22, 45, 92, 101, 188}
+DEPTHS_IDS = {42, 97}
+CASTLE_SEQUOIA_IDS = {58, 69, 73, 74, 75, 93, 94, 177}
+NEW_WORLD_IDS = {59, 111, 180}
+OLD_WORLD_IDS = {178}
+#TODO Underpass, overpass
+
+def get_display_region_by_id(region_id: int) -> str:
+ if region_id in SPAWNING_MEADOWS_IDS:
+ return SPAWNING_MEADOWS_DISPLAY_NAME
+ if region_id in DELENDE_IDS:
+ return DELENDE_DISPLAY_NAME
+ if region_id in SOILED_DEN_IDS:
+ return SOILED_DEN_DISPLAY_NAME
+ if region_id in PALE_GROTTO_IDS:
+ return THE_PALE_GROTTO_DISPLAY_NAME
+ if region_id in SEASIDE_CLIFF_IDS:
+ return SEASIDE_CLIFFS_DISPLAY_NAME
+ if region_id in DRAFT_SHAFT_CONDUITS_IDS:
+ return DRAFT_SHAFT_CONDUIT_DISPLAY_NAME
+ if region_id in MERCURY_SHRINE_IDS:
+ return MERCURY_SHRINE_DISPLAY_NAME
+ if region_id in PROVING_MEADOWS_IDS:
+ return PROVING_MEADOWS_DISPLAY_NAME
+ if region_id in YAMAGAWA_MA_IDS:
+ return YAMAGAWA_MA_DISPLAY_NAME
+ if region_id in SKUMPARADISE_IDS:
+ return SKUMPARADISE_DISPLAY_NAME
+ if region_id in CAPITAL_IDS:
+ return CAPITAL_SEQUOIA_DISPLAY_NAME
+ if region_id in JOJO_SEWERS_IDS:
+ return JOJO_SEWERS_DISPLAY_NAME
+ if region_id in BOOMER_SOCIETY_IDS:
+ return BOOMER_SOCIETY_DISPLAY_NAME
+ if region_id in ROLLING_QUINTAR_FIELDS_IDS:
+ return ROLLING_QUINTAR_FIELDS_DISPLAY_NAME
+ if region_id in QUINTAR_NEST_IDS:
+ return QUINTAR_NEST_DISPLAY_NAME
+ if region_id in QUINTAR_SANCTUM_IDS:
+ return QUINTAR_SANCTUM_DISPLAY_NAME
+ if region_id in CAPITAL_JAIL_IDS:
+ return CAPITAL_JAIL_DISPLAY_NAME
+ if region_id in CAPITAL_PIPELINE_IDS:
+ return CAPITAL_PIPELINE_DISPLAY_NAME
+ if region_id in COBBLESTONE_CRAG_IDS:
+ return COBBLESTONE_CRAG_DISPLAY_NAME
+ if region_id in OKIMOTO_IDS:
+ return OKIMOTO_NS_DISPLAY_NAME
+ if region_id in GREENSHIRE_IDS:
+ return GREENSHIRE_REPRISE_DISPLAY_NAME
+ if region_id in SALMON_PASS_IDS:
+ return SALMON_PASS_DISPLAY_NAME
+ if region_id in SALMON_RIVER_IDS:
+ return SALMON_RIVER_DISPLAY_NAME
+ if region_id in POKO_POKO_IDS:
+ return POKO_POKO_DESERT_DISPLAY_NAME
+ if region_id in SARA_SARA_IDS:
+ return SARA_SARA_BAZAAR_DISPLAY_NAME
+ if region_id in SARA_SARA_BEACH_IDS:
+ #No idea why there are two of these now, but we have no way to know so just pick one
+ return SARA_SARA_BEACH_EAST_DISPLAY_NAME
+ if region_id in ANCIENT_RESERVOIR_IDS:
+ return ANCIENT_RESERVOIR_DISPLAY_NAME
+ if region_id in SALMON_BAY_IDS:
+ return SALMON_BAY_DISPLAY_NAME
+ if region_id in OPEN_SEA_IDS:
+ return THE_OPEN_SEA_DISPLAY_NAME
+ if region_id in WATERFRONT_IDS:
+ return SHOUDU_WATERFRONT_DISPLAY_NAME
+ if region_id in SHOUDU_IDS:
+ return SHOUDU_PROVINCE_DISPLAY_NAME
+ if region_id in UNDERCITY_IDS:
+ return THE_UNDERCITY_DISPLAY_NAME
+ if region_id in GANYMEDE_IDS:
+ return GANYMEDE_SHRINE_DISPLAY_NAME
+ if region_id in BEAURIOR_VOLCANO_IDS:
+ return BEAURIOR_VOLCANO_DISPLAY_NAME
+ if region_id in BEAURIOR_ROCK_IDS:
+ return BEAURIOR_ROCK_DISPLAY_NAME
+ if region_id in LAKE_DELENDE_IDS:
+ return LAKE_DELENDE_DISPLAY_NAME
+ if region_id in QUINTAR_RESERVE_IDS:
+ return QUINTAR_RESERVE_DISPLAY_NAME
+ if region_id in DIONE_SHRINE_IDS:
+ return DIONE_SHRINE_DISPLAY_NAME
+ if region_id in QUINTAR_MAUSOLEUM_IDS:
+ return QUINTAR_MAUSOLEUM_DISPLAY_NAME
+ if region_id in EASTERN_CHASM_IDS:
+ return EASTERN_CHASM_DISPLAY_NAME
+ if region_id in TALL_TALL_HEIGHTS_IDS:
+ return TALL_TALL_HEIGHTS_DISPLAY_NAME
+ if region_id in NORTHERN_CAVE_IDS:
+ return NORTHERN_CAVE_DISPLAY_NAME
+ if region_id in LANDS_END_IDS:
+ return LANDS_END_DISPLAY_NAME
+ if region_id in SLIP_GLIDE_RIDE_IDS:
+ return SLIP_GLIDE_RIDE_DISPLAY_NAME
+ if region_id in SEQUOIA_ATHENAEUM_IDS:
+ return SEQUOIA_ATHENAEUM_DISPLAY_NAME
+ if region_id in NORTHERN_STRETCH_IDS:
+ return NORTHERN_STRETCH_DISPLAY_NAME
+ if region_id in CASTLE_RAMPARTS_IDS:
+ return CASTLE_RAMPARTS_DISPLAY_NAME
+ if region_id in CHALICE_OF_TAR_IDS:
+ return THE_CHALICE_OF_TAR_DISPLAY_NAME
+ if region_id in FLYERS_CRAG_IDS:
+ return FLYERS_CRAG_DISPLAY_NAME
+ if region_id in JIDAMBA_IDS:
+ return JIDAMBA_TANGLE_DISPLAY_NAME
+ if region_id in EACLANEYA_IDS:
+ return JIDAMBA_EACLANEYA_DISPLAY_NAME
+ if region_id in DEEP_SEA_IDS:
+ return THE_DEEP_SEA_DISPLAY_NAME
+ if region_id in NEPTUNE_SHRINE_IDS:
+ return NEPTUNE_SHRINE_DISPLAY_NAME
+ if region_id in JADE_CAVERN_IDS:
+ return JADE_CAVERN_DISPLAY_NAME
+ if region_id in CONTINENTAL_TRAM_IDS:
+ return CONTINENTAL_TRAM_DISPLAY_NAME
+ if region_id in ANCIENT_LABYRINTH_IDS:
+ return ANCIENT_LABYRINTH_DISPLAY_NAME
+ if region_id in SEQUOIA_IDS:
+ return THE_SEQUOIA_DISPLAY_NAME
+ if region_id in DEPTHS_IDS:
+ return THE_DEPTHS_DISPLAY_NAME
+ if region_id in CASTLE_SEQUOIA_IDS:
+ return CASTLE_SEQUOIA_DISPLAY_NAME
+ if region_id in NEW_WORLD_IDS:
+ return THE_NEW_WORLD_DISPLAY_NAME
+ if region_id in OLD_WORLD_IDS:
+ return THE_OLD_WORLD_DISPLAY_NAME
+
return MODDED_ZONE_DISPLAY_NAME
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/crystal_locations.py b/worlds/crystal_project/constants/crystal_locations.py
index 4daca2ba1e6b..74cbbec22ab8 100644
--- a/worlds/crystal_project/constants/crystal_locations.py
+++ b/worlds/crystal_project/constants/crystal_locations.py
@@ -1,18 +1,18 @@
-FENCER_JOB_CRYSTAL_LOCATION = " Crystal - Fencer"
-SHAMAN_JOB_CRYSTAL_LOCATION = " Crystal - Shaman"
-SCHOLAR_JOB_CRYSTAL_LOCATION = " Crystal - Jump into fireplace cave for Scholar"
-AEGIS_JOB_CRYSTAL_LOCATION = " Crystal - Aegis"
-BEATSMITH_JOB_CRYSTAL_LOCATION = " Crystal - Beatsmith"
-HUNTER_JOB_CRYSTAL_LOCATION = " Crystal - Hunter"
-CHEMIST_JOB_CRYSTAL_LOCATION = " Crystal - Chemist (of course this is in the shroom zone)"
-REAPER_JOB_CRYSTAL_LOCATION = " Crystal - Reaper, above hell pool"
-NINJA_JOB_CRYSTAL_LOCATION = " Crystal - Ninja"
-NOMAD_JOB_CRYSTAL_LOCATION = " Crystal - Appease the QuizFish Nomad"
-DERVISH_JOB_CRYSTAL_LOCATION = " Crystal - Dervish"
-SAMURAI_JOB_CRYSTAL_LOCATION = " Crystal - Samurai for 3 Sky Arena wins"
-ASSASSIN_JOB_CRYSTAL_LOCATION = " Crystal - Assassin"
-VALKYRIE_JOB_CRYSTAL_LOCATION = " Crystal - Valkyrie"
-SUMMONER_JOB_CRYSTAL_LOCATION = " Crystal - Summoner"
-BEASTMASTER_JOB_CRYSTAL_LOCATION = " Crystal - Beastmaster (say high to the Ramparts Demon!)"
-MIMIC_JOB_CRYSTAL_LOCATION = " Crystal - Biiiiiig glide to the Mimic"
+FENCER_JOB_CRYSTAL_LOCATION = " Crystal - Fencer"
+SHAMAN_JOB_CRYSTAL_LOCATION = " Crystal - Shaman"
+SCHOLAR_JOB_CRYSTAL_LOCATION = " Crystal - Jump into fireplace cave for Scholar"
+AEGIS_JOB_CRYSTAL_LOCATION = " Crystal - Aegis"
+BEATSMITH_JOB_CRYSTAL_LOCATION = " Crystal - Beatsmith"
+HUNTER_JOB_CRYSTAL_LOCATION = " Crystal - Hunter"
+CHEMIST_JOB_CRYSTAL_LOCATION = " Crystal - Chemist (of course this is in the shroom zone)"
+REAPER_JOB_CRYSTAL_LOCATION = " Crystal - Reaper, above hell pool"
+NINJA_JOB_CRYSTAL_LOCATION = " Crystal - Ninja"
+NOMAD_JOB_CRYSTAL_LOCATION = " Crystal - Appease the QuizFish Nomad"
+DERVISH_JOB_CRYSTAL_LOCATION = " Crystal - Dervish"
+SAMURAI_JOB_CRYSTAL_LOCATION = " Crystal - Samurai for 3 Sky Arena wins"
+ASSASSIN_JOB_CRYSTAL_LOCATION = " Crystal - Assassin"
+VALKYRIE_JOB_CRYSTAL_LOCATION = " Crystal - Valkyrie"
+SUMMONER_JOB_CRYSTAL_LOCATION = " Crystal - Summoner"
+BEASTMASTER_JOB_CRYSTAL_LOCATION = " Crystal - Beastmaster (say high to the Ramparts Demon!)"
+MIMIC_JOB_CRYSTAL_LOCATION = " Crystal - Biiiiiig glide to the Mimic"
WEAVER_JOB_CRYSTAL_LOCATION = " Crystal - Weaver"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/display_regions.py b/worlds/crystal_project/constants/display_regions.py
index ae695b29cb81..576544dc7bf4 100644
--- a/worlds/crystal_project/constants/display_regions.py
+++ b/worlds/crystal_project/constants/display_regions.py
@@ -1,141 +1,141 @@
-from .ap_regions import *
-
-#The first subregion (AP Region) in a display region will be the starter region if a player puts that display region's pass in their starting inventory; ensure that it has teleport coordinates for it in the ap_regions file
-#If making a new subregion, check all other project files for instances where the region you're replacing is mentioned, but especially regions, locations, and home_points
-MENU_DISPLAY_NAME = "Menu"
-MENU_DISPLAY_SUBREGIONS = [MENU_AP_REGION]
-#Beginner
-SPAWNING_MEADOWS_DISPLAY_NAME = "Spawning Meadows"
-SPAWNING_MEADOWS_DISPLAY_SUBREGIONS = [SPAWNING_MEADOWS_AP_REGION]
-DELENDE_DISPLAY_NAME = "Delende"
-DELENDE_DISPLAY_SUBREGIONS = [DELENDE_PLAINS_AP_REGION, ATOP_FISH_HATCHERY_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, GRAN_AP_REGION, BELOW_GRAN_AP_REGION, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, HEART_TARN_AP_REGION, DELENDE_PEAK_AP_REGION]
-MERCURY_SHRINE_DISPLAY_NAME = "Mercury Shrine"
-MERCURY_SHRINE_DISPLAY_SUBREGIONS = [MERCURY_SHRINE_AP_REGION]
-SOILED_DEN_DISPLAY_NAME = "Soiled Den"
-SOILED_DEN_DISPLAY_SUBREGIONS = [SOILED_DENLENDE_AP_REGION, THE_BANGLER_AP_REGION]
-THE_PALE_GROTTO_DISPLAY_NAME = "The Pale Grotto"
-THE_PALE_GROTTO_DISPLAY_SUBREGIONS = [THE_PALE_GROTTO_AP_REGION]
-SEASIDE_CLIFFS_DISPLAY_NAME = "Seaside Cliffs"
-SEASIDE_CLIFFS_DISPLAY_SUBREGIONS = [SEASIDE_CLIFFS_AP_REGION, CLIFF_OVER_SEASIDE_CAMP_AP_REGION]
-DRAFT_SHAFT_CONDUIT_DISPLAY_NAME = "Draft Shaft Conduit"
-DRAFT_SHAFT_CONDUIT_DISPLAY_SUBREGIONS = [DRAFT_SHAFT_CONDUIT_AP_REGION]
-YAMAGAWA_MA_DISPLAY_NAME = "Yamagawa M.A."
-YAMAGAWA_MA_DISPLAY_SUBREGIONS = [YAMAGAWA_MA_AP_REGION, FENCERS_KEEP_CHEST_AP_REGION]
-PROVING_MEADOWS_DISPLAY_NAME = "Proving Meadows"
-PROVING_MEADOWS_DISPLAY_SUBREGIONS = [PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION]
-SKUMPARADISE_DISPLAY_NAME = "Skumparadise"
-SKUMPARADISE_DISPLAY_SUBREGIONS = [SKUMPARADISE_AP_REGION]
-#Advanced
-CAPITAL_SEQUOIA_DISPLAY_NAME = "Capital Sequoia"
-CAPITAL_SEQUOIA_DISPLAY_SUBREGIONS = [CAPITAL_SEQUOIA_AP_REGION, MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BEATSMITH_DISCO_AP_REGION]
-JOJO_SEWERS_DISPLAY_NAME = "Jojo Sewers"
-JOJO_SEWERS_DISPLAY_SUBREGIONS = [JOJO_SEWERS_AP_REGION, SEWERS_SECRET_PASSWORD_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION]
-BOOMER_SOCIETY_DISPLAY_NAME = "Boomer Society"
-BOOMER_SOCIETY_DISPLAY_SUBREGIONS = [BOOMER_SOCIETY_AP_REGION]
-ROLLING_QUINTAR_FIELDS_DISPLAY_NAME = "Rolling Quintar Fields"
-ROLLING_QUINTAR_FIELDS_DISPLAY_SUBREGIONS = [ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_TREETOP_HIGHWAY_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, HUNTERS_TOWER_AP_REGION]
-QUINTAR_NEST_DISPLAY_NAME = "Quintar Nest"
-QUINTAR_NEST_DISPLAY_SUBREGIONS = [QUINTAR_NEST_AP_REGION]
-QUINTAR_SANCTUM_DISPLAY_NAME = "Quintar Sanctum"
-QUINTAR_SANCTUM_DISPLAY_SUBREGIONS = [QUINTAR_SANCTUM_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION]
-CAPITAL_JAIL_DISPLAY_NAME = "Capital Jail"
-CAPITAL_JAIL_DISPLAY_SUBREGIONS = [CAPITAL_JAIL_AP_REGION, JAIL_SOUTH_WING_AP_REGION, JAIL_SOUTH_WING_RUBBLE_AP_REGION]
-CAPITAL_PIPELINE_DISPLAY_NAME = "Capital Pipeline"
-#picked south pipeline as the starter region
-CAPITAL_PIPELINE_DISPLAY_SUBREGIONS = [PIPELINE_SOUTH_AP_REGION, PIPELINE_NORTH_AP_REGION, PIPELINE_JIDAMBA_CONNECTOR_AP_REGION]
-COBBLESTONE_CRAG_DISPLAY_NAME = "Cobblestone Crag"
-COBBLESTONE_CRAG_DISPLAY_SUBREGIONS = [COBBLESTONE_CRAG_AP_REGION]
-OKIMOTO_NS_DISPLAY_NAME = "Okimoto N.S."
-OKIMOTO_NS_DISPLAY_SUBREGIONS = [OKIMOTO_NS_AP_REGION]
-GREENSHIRE_REPRISE_DISPLAY_NAME = "Greenshire Reprise"
-GREENSHIRE_REPRISE_DISPLAY_SUBREGIONS = [GREENSHIRE_REPRISE_AP_REGION]
-SALMON_PASS_DISPLAY_NAME = "Salmon Pass"
-SALMON_PASS_DISPLAY_SUBREGIONS = [SALMON_PASS_EAST_AP_REGION, SALMON_PASS_WEST_AP_REGION]
-SALMON_RIVER_DISPLAY_NAME = "Salmon River"
-SALMON_RIVER_DISPLAY_SUBREGIONS = [SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, RIVER_CATS_EGO_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION]
-SHOUDU_WATERFRONT_DISPLAY_NAME = "Shoudu Waterfront"
-SHOUDU_WATERFRONT_DISPLAY_SUBREGIONS = [SHOUDU_WATERFRONT_AP_REGION]
-POKO_POKO_DESERT_DISPLAY_NAME = "Poko Poko Desert"
-POKO_POKO_DESERT_DISPLAY_SUBREGIONS = [POKO_POKO_DESERT_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_BEACH_WEST_PASS_AP_REGION, TOWER_OF_ZOT_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION]
-SARA_SARA_BAZAAR_DISPLAY_NAME = "Sara Sara Bazaar"
-SARA_SARA_BAZAAR_DISPLAY_SUBREGIONS = [SARA_SARA_BAZAAR_AP_REGION]
-SARA_SARA_BEACH_EAST_DISPLAY_NAME = "Sara Sara Beach East"
-SARA_SARA_BEACH_EAST_DISPLAY_SUBREGIONS = [SARA_SARA_BEACH_EAST_AP_REGION]
-SARA_SARA_BEACH_WEST_DISPLAY_NAME = "Sara Sara Beach West"
-SARA_SARA_BEACH_WEST_DISPLAY_SUBREGIONS = [SARA_SARA_BEACH_WEST_AP_REGION]
-ANCIENT_RESERVOIR_DISPLAY_NAME = "Ancient Reservoir"
-ANCIENT_RESERVOIR_DISPLAY_SUBREGIONS = [ANCIENT_RESERVOIR_AP_REGION, IBEK_CAVE_AP_REGION]
-SALMON_BAY_DISPLAY_NAME = "Salmon Bay"
-SALMON_BAY_DISPLAY_SUBREGIONS = [SALMON_BAY_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION]
-#Expert
-THE_OPEN_SEA_DISPLAY_NAME = "The Open Sea"
-THE_OPEN_SEA_DISPLAY_SUBREGIONS = [THE_OPEN_SEA_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, BAZAAR_COAST_AP_REGION]
-SHOUDU_PROVINCE_DISPLAY_NAME = "Shoudu Province"
-SHOUDU_PROVINCE_DISPLAY_SUBREGIONS = [SHOUDU_PROVINCE_AP_REGION]
-THE_UNDERCITY_DISPLAY_NAME = "The Undercity"
-THE_UNDERCITY_DISPLAY_SUBREGIONS = [THE_UNDERCITY_AP_REGION]
-GANYMEDE_SHRINE_DISPLAY_NAME = "Ganymede Shrine"
-GANYMEDE_SHRINE_DISPLAY_SUBREGIONS = [GANYMEDE_SHRINE_AP_REGION]
-BEAURIOR_VOLCANO_DISPLAY_NAME = "Beaurior Volcano"
-BEAURIOR_VOLCANO_DISPLAY_SUBREGIONS = [BEAURIOR_VOLCANO_AP_REGION]
-BEAURIOR_ROCK_DISPLAY_NAME = "Beaurior Rock"
-BEAURIOR_ROCK_DISPLAY_SUBREGIONS = [BEAURIOR_ROCK_AP_REGION]
-LAKE_DELENDE_DISPLAY_NAME = "Lake Delende"
-LAKE_DELENDE_DISPLAY_SUBREGIONS = [LAKE_DELENDE_AP_REGION, ATOP_DAM_AP_REGION]
-QUINTAR_RESERVE_DISPLAY_NAME = "Quintar Reserve"
-QUINTAR_RESERVE_DISPLAY_SUBREGIONS = [QUINTAR_RESERVE_AP_REGION, RESERVE_TREETOPS_AP_REGION]
-DIONE_SHRINE_DISPLAY_NAME = "Dione Shrine"
-DIONE_SHRINE_DISPLAY_SUBREGIONS = [DIONE_ROOF_AP_REGION, DIONE_SHRINE_AP_REGION]
-QUINTAR_MAUSOLEUM_DISPLAY_NAME = "Quintar Mausoleum"
-QUINTAR_MAUSOLEUM_DISPLAY_SUBREGIONS = [QUINTAR_MAUSOLEUM_AP_REGION]
-EASTERN_CHASM_DISPLAY_NAME = "Eastern Chasm"
-EASTERN_CHASM_DISPLAY_SUBREGIONS = [EASTERN_CHASM_AP_REGION]
-TALL_TALL_HEIGHTS_DISPLAY_NAME = "Tall, Tall Heights"
-#Lower Ice Lakes is the default start
-TALL_TALL_HEIGHTS_DISPLAY_SUBREGIONS = [LOWER_ICE_LAKES_AP_REGION, BOOMER_OVERLOOK_AP_REGION, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, GREENSHIRE_OVERLOOK_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, UPPER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, SLIP_GLIDE_RIDE_EXIT_AP_REGION, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, SOUVENIR_SHOP_AP_REGION, SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, ICY_SPIKES_MADNESS_AP_REGION, PAMOA_TREE_AP_REGION]
-NORTHERN_CAVE_DISPLAY_NAME = "Northern Cave"
-NORTHERN_CAVE_DISPLAY_SUBREGIONS = [UPPER_NORTHERN_CAVE_AP_REGION, MIDDLE_NORTHERN_CAVE_AP_REGION, ICE_CELL_AP_REGION, LOWER_NORTHERN_CAVE_AP_REGION]
-LANDS_END_DISPLAY_NAME = "Land's End"
-LANDS_END_DISPLAY_SUBREGIONS = [LANDS_END_AP_REGION, LANDS_END_NORTHERN_PEAK_AP_REGION, OWL_TREE_AP_REGION]
-SLIP_GLIDE_RIDE_DISPLAY_NAME = "Slip Glide Ride"
-SLIP_GLIDE_RIDE_DISPLAY_SUBREGIONS = [SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, SLIP_GLIDE_RIDE_AP_REGION]
-SEQUOIA_ATHENAEUM_DISPLAY_NAME = "Sequoia Athenaeum"
-SEQUOIA_ATHENAEUM_DISPLAY_SUBREGIONS = [SEQUOIA_ATHENAEUM_AP_REGION]
-NORTHERN_STRETCH_DISPLAY_NAME = "Northern Stretch"
-#Race start is default start
-NORTHERN_STRETCH_DISPLAY_SUBREGIONS = [NORTHERN_STRETCH_RACE_START_AP_REGION, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, SUMMONERS_TOWER_AP_REGION]
-CASTLE_RAMPARTS_DISPLAY_NAME = "Castle Ramparts"
-#West save point is default start
-CASTLE_RAMPARTS_DISPLAY_SUBREGIONS = [PEAK_RAMPARTS_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, LONE_CHEST_RAMPART_AP_REGION, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION]
-THE_CHALICE_OF_TAR_DISPLAY_NAME = "The Chalice of Tar"
-THE_CHALICE_OF_TAR_DISPLAY_SUBREGIONS = [THE_CHALICE_OF_TAR_AP_REGION]
-FLYERS_CRAG_DISPLAY_NAME = "Flyer's Crag"
-FLYERS_CRAG_DISPLAY_SUBREGIONS = [FLYERS_CRAG_AP_REGION]
-JIDAMBA_TANGLE_DISPLAY_NAME = "Jidamba Tangle"
-JIDAMBA_TANGLE_DISPLAY_SUBREGIONS = [EUROPA_SHRINE_AP_REGION, JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_DIAMONDSMITH_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_CANOPY_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION, TANGLE_EACLANEYA_CONNECTOR_AP_REGION]
-JIDAMBA_EACLANEYA_DISPLAY_NAME = "Jidamba Eaclaneya"
-JIDAMBA_EACLANEYA_DISPLAY_SUBREGIONS = [JIDAMBA_EACLANEYA_AP_REGION]
-THE_DEEP_SEA_DISPLAY_NAME = "The Deep Sea"
-THE_DEEP_SEA_DISPLAY_SUBREGIONS = [THE_DEEP_SEA_AP_REGION]
-NEPTUNE_SHRINE_DISPLAY_NAME = "Neptune Shrine"
-NEPTUNE_SHRINE_DISPLAY_SUBREGIONS = [NEPTUNE_SHRINE_AP_REGION]
-JADE_CAVERN_DISPLAY_NAME = "Jade Cavern"
-JADE_CAVERN_DISPLAY_SUBREGIONS = [JADE_CAVERN_AP_REGION, JADE_WATERWAYS_AP_REGION]
-CONTINENTAL_TRAM_DISPLAY_NAME = "Continental Tram"
-CONTINENTAL_TRAM_DISPLAY_SUBREGIONS = [CONTINENTAL_TRAM_AP_REGION]
-#End-Game
-ANCIENT_LABYRINTH_DISPLAY_NAME = "Ancient Labyrinth"
-ANCIENT_LABYRINTH_DISPLAY_SUBREGIONS = [ANCIENT_LABYRINTH_AP_REGION, LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, LABYRINTH_CORE_AP_REGION]
-THE_SEQUOIA_DISPLAY_NAME = "The Sequoia"
-THE_SEQUOIA_DISPLAY_SUBREGIONS = [THE_SEQUOIA_AP_REGION]
-THE_DEPTHS_DISPLAY_NAME = "The Depths"
-THE_DEPTHS_DISPLAY_SUBREGIONS = [THE_DEPTHS_AP_REGION]
-CASTLE_SEQUOIA_DISPLAY_NAME = "Castle Sequoia"
-CASTLE_SEQUOIA_DISPLAY_SUBREGIONS = [CASTLE_SEQUOIA_AP_REGION]
-THE_OLD_WORLD_DISPLAY_NAME = "The Old World"
-THE_OLD_WORLD_DISPLAY_SUBREGIONS = [THE_OLD_WORLD_AP_REGION]
-THE_NEW_WORLD_DISPLAY_NAME = "The New World"
-THE_NEW_WORLD_DISPLAY_SUBREGIONS = [THE_NEW_WORLD_AP_REGION]
-#Mods
-MODDED_ZONE_DISPLAY_NAME = "Modded Zone"
+from .ap_regions import *
+
+#The first subregion (AP Region) in a display region will be the starter region if a player puts that display region's pass in their starting inventory; ensure that it has teleport coordinates for it in the ap_regions file
+#If making a new subregion, check all other project files for instances where the region you're replacing is mentioned, but especially regions, locations, and home_points
+MENU_DISPLAY_NAME = "Menu"
+MENU_DISPLAY_SUBREGIONS = [MENU_AP_REGION]
+#Beginner
+SPAWNING_MEADOWS_DISPLAY_NAME = "Spawning Meadows"
+SPAWNING_MEADOWS_DISPLAY_SUBREGIONS = [SPAWNING_MEADOWS_AP_REGION]
+DELENDE_DISPLAY_NAME = "Delende"
+DELENDE_DISPLAY_SUBREGIONS = [DELENDE_PLAINS_AP_REGION, ATOP_FISH_HATCHERY_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, GRAN_AP_REGION, BELOW_GRAN_AP_REGION, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, HEART_TARN_AP_REGION, DELENDE_PEAK_AP_REGION]
+MERCURY_SHRINE_DISPLAY_NAME = "Mercury Shrine"
+MERCURY_SHRINE_DISPLAY_SUBREGIONS = [MERCURY_SHRINE_AP_REGION]
+SOILED_DEN_DISPLAY_NAME = "Soiled Den"
+SOILED_DEN_DISPLAY_SUBREGIONS = [SOILED_DENLENDE_AP_REGION, THE_BANGLER_AP_REGION]
+THE_PALE_GROTTO_DISPLAY_NAME = "The Pale Grotto"
+THE_PALE_GROTTO_DISPLAY_SUBREGIONS = [THE_PALE_GROTTO_AP_REGION]
+SEASIDE_CLIFFS_DISPLAY_NAME = "Seaside Cliffs"
+SEASIDE_CLIFFS_DISPLAY_SUBREGIONS = [SEASIDE_CLIFFS_AP_REGION, CLIFF_OVER_SEASIDE_CAMP_AP_REGION]
+DRAFT_SHAFT_CONDUIT_DISPLAY_NAME = "Draft Shaft Conduit"
+DRAFT_SHAFT_CONDUIT_DISPLAY_SUBREGIONS = [DRAFT_SHAFT_CONDUIT_AP_REGION]
+YAMAGAWA_MA_DISPLAY_NAME = "Yamagawa M.A."
+YAMAGAWA_MA_DISPLAY_SUBREGIONS = [YAMAGAWA_MA_AP_REGION, FENCERS_KEEP_CHEST_AP_REGION]
+PROVING_MEADOWS_DISPLAY_NAME = "Proving Meadows"
+PROVING_MEADOWS_DISPLAY_SUBREGIONS = [PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION]
+SKUMPARADISE_DISPLAY_NAME = "Skumparadise"
+SKUMPARADISE_DISPLAY_SUBREGIONS = [SKUMPARADISE_AP_REGION]
+#Advanced
+CAPITAL_SEQUOIA_DISPLAY_NAME = "Capital Sequoia"
+CAPITAL_SEQUOIA_DISPLAY_SUBREGIONS = [CAPITAL_SEQUOIA_AP_REGION, MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BEATSMITH_DISCO_AP_REGION]
+JOJO_SEWERS_DISPLAY_NAME = "Jojo Sewers"
+JOJO_SEWERS_DISPLAY_SUBREGIONS = [JOJO_SEWERS_AP_REGION, SEWERS_SECRET_PASSWORD_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION]
+BOOMER_SOCIETY_DISPLAY_NAME = "Boomer Society"
+BOOMER_SOCIETY_DISPLAY_SUBREGIONS = [BOOMER_SOCIETY_AP_REGION]
+ROLLING_QUINTAR_FIELDS_DISPLAY_NAME = "Rolling Quintar Fields"
+ROLLING_QUINTAR_FIELDS_DISPLAY_SUBREGIONS = [ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_TREETOP_HIGHWAY_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, HUNTERS_TOWER_AP_REGION]
+QUINTAR_NEST_DISPLAY_NAME = "Quintar Nest"
+QUINTAR_NEST_DISPLAY_SUBREGIONS = [QUINTAR_NEST_AP_REGION]
+QUINTAR_SANCTUM_DISPLAY_NAME = "Quintar Sanctum"
+QUINTAR_SANCTUM_DISPLAY_SUBREGIONS = [QUINTAR_SANCTUM_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION]
+CAPITAL_JAIL_DISPLAY_NAME = "Capital Jail"
+CAPITAL_JAIL_DISPLAY_SUBREGIONS = [CAPITAL_JAIL_AP_REGION, JAIL_SOUTH_WING_AP_REGION, JAIL_SOUTH_WING_RUBBLE_AP_REGION]
+CAPITAL_PIPELINE_DISPLAY_NAME = "Capital Pipeline"
+#picked south pipeline as the starter region
+CAPITAL_PIPELINE_DISPLAY_SUBREGIONS = [PIPELINE_SOUTH_AP_REGION, PIPELINE_NORTH_AP_REGION, PIPELINE_JIDAMBA_CONNECTOR_AP_REGION]
+COBBLESTONE_CRAG_DISPLAY_NAME = "Cobblestone Crag"
+COBBLESTONE_CRAG_DISPLAY_SUBREGIONS = [COBBLESTONE_CRAG_AP_REGION]
+OKIMOTO_NS_DISPLAY_NAME = "Okimoto N.S."
+OKIMOTO_NS_DISPLAY_SUBREGIONS = [OKIMOTO_NS_AP_REGION]
+GREENSHIRE_REPRISE_DISPLAY_NAME = "Greenshire Reprise"
+GREENSHIRE_REPRISE_DISPLAY_SUBREGIONS = [GREENSHIRE_REPRISE_AP_REGION]
+SALMON_PASS_DISPLAY_NAME = "Salmon Pass"
+SALMON_PASS_DISPLAY_SUBREGIONS = [SALMON_PASS_EAST_AP_REGION, SALMON_PASS_WEST_AP_REGION]
+SALMON_RIVER_DISPLAY_NAME = "Salmon River"
+SALMON_RIVER_DISPLAY_SUBREGIONS = [SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, RIVER_CATS_EGO_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION]
+SHOUDU_WATERFRONT_DISPLAY_NAME = "Shoudu Waterfront"
+SHOUDU_WATERFRONT_DISPLAY_SUBREGIONS = [SHOUDU_WATERFRONT_AP_REGION]
+POKO_POKO_DESERT_DISPLAY_NAME = "Poko Poko Desert"
+POKO_POKO_DESERT_DISPLAY_SUBREGIONS = [POKO_POKO_DESERT_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_BEACH_WEST_PASS_AP_REGION, TOWER_OF_ZOT_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION]
+SARA_SARA_BAZAAR_DISPLAY_NAME = "Sara Sara Bazaar"
+SARA_SARA_BAZAAR_DISPLAY_SUBREGIONS = [SARA_SARA_BAZAAR_AP_REGION]
+SARA_SARA_BEACH_EAST_DISPLAY_NAME = "Sara Sara Beach East"
+SARA_SARA_BEACH_EAST_DISPLAY_SUBREGIONS = [SARA_SARA_BEACH_EAST_AP_REGION]
+SARA_SARA_BEACH_WEST_DISPLAY_NAME = "Sara Sara Beach West"
+SARA_SARA_BEACH_WEST_DISPLAY_SUBREGIONS = [SARA_SARA_BEACH_WEST_AP_REGION]
+ANCIENT_RESERVOIR_DISPLAY_NAME = "Ancient Reservoir"
+ANCIENT_RESERVOIR_DISPLAY_SUBREGIONS = [ANCIENT_RESERVOIR_AP_REGION, IBEK_CAVE_AP_REGION]
+SALMON_BAY_DISPLAY_NAME = "Salmon Bay"
+SALMON_BAY_DISPLAY_SUBREGIONS = [SALMON_BAY_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION]
+#Expert
+THE_OPEN_SEA_DISPLAY_NAME = "The Open Sea"
+THE_OPEN_SEA_DISPLAY_SUBREGIONS = [THE_OPEN_SEA_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, BAZAAR_COAST_AP_REGION]
+SHOUDU_PROVINCE_DISPLAY_NAME = "Shoudu Province"
+SHOUDU_PROVINCE_DISPLAY_SUBREGIONS = [SHOUDU_PROVINCE_AP_REGION]
+THE_UNDERCITY_DISPLAY_NAME = "The Undercity"
+THE_UNDERCITY_DISPLAY_SUBREGIONS = [THE_UNDERCITY_AP_REGION]
+GANYMEDE_SHRINE_DISPLAY_NAME = "Ganymede Shrine"
+GANYMEDE_SHRINE_DISPLAY_SUBREGIONS = [GANYMEDE_SHRINE_AP_REGION]
+BEAURIOR_VOLCANO_DISPLAY_NAME = "Beaurior Volcano"
+BEAURIOR_VOLCANO_DISPLAY_SUBREGIONS = [BEAURIOR_VOLCANO_AP_REGION]
+BEAURIOR_ROCK_DISPLAY_NAME = "Beaurior Rock"
+BEAURIOR_ROCK_DISPLAY_SUBREGIONS = [BEAURIOR_ROCK_AP_REGION]
+LAKE_DELENDE_DISPLAY_NAME = "Lake Delende"
+LAKE_DELENDE_DISPLAY_SUBREGIONS = [LAKE_DELENDE_AP_REGION, ATOP_DAM_AP_REGION]
+QUINTAR_RESERVE_DISPLAY_NAME = "Quintar Reserve"
+QUINTAR_RESERVE_DISPLAY_SUBREGIONS = [QUINTAR_RESERVE_AP_REGION, RESERVE_TREETOPS_AP_REGION]
+DIONE_SHRINE_DISPLAY_NAME = "Dione Shrine"
+DIONE_SHRINE_DISPLAY_SUBREGIONS = [DIONE_ROOF_AP_REGION, DIONE_SHRINE_AP_REGION]
+QUINTAR_MAUSOLEUM_DISPLAY_NAME = "Quintar Mausoleum"
+QUINTAR_MAUSOLEUM_DISPLAY_SUBREGIONS = [QUINTAR_MAUSOLEUM_AP_REGION]
+EASTERN_CHASM_DISPLAY_NAME = "Eastern Chasm"
+EASTERN_CHASM_DISPLAY_SUBREGIONS = [EASTERN_CHASM_AP_REGION]
+TALL_TALL_HEIGHTS_DISPLAY_NAME = "Tall, Tall Heights"
+#Lower Ice Lakes is the default start
+TALL_TALL_HEIGHTS_DISPLAY_SUBREGIONS = [LOWER_ICE_LAKES_AP_REGION, BOOMER_OVERLOOK_AP_REGION, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, GREENSHIRE_OVERLOOK_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, UPPER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, SLIP_GLIDE_RIDE_EXIT_AP_REGION, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, SOUVENIR_SHOP_AP_REGION, SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, ICY_SPIKES_MADNESS_AP_REGION, PAMOA_TREE_AP_REGION]
+NORTHERN_CAVE_DISPLAY_NAME = "Northern Cave"
+NORTHERN_CAVE_DISPLAY_SUBREGIONS = [UPPER_NORTHERN_CAVE_AP_REGION, MIDDLE_NORTHERN_CAVE_AP_REGION, ICE_CELL_AP_REGION, LOWER_NORTHERN_CAVE_AP_REGION]
+LANDS_END_DISPLAY_NAME = "Land's End"
+LANDS_END_DISPLAY_SUBREGIONS = [LANDS_END_AP_REGION, LANDS_END_NORTHERN_PEAK_AP_REGION, OWL_TREE_AP_REGION]
+SLIP_GLIDE_RIDE_DISPLAY_NAME = "Slip Glide Ride"
+SLIP_GLIDE_RIDE_DISPLAY_SUBREGIONS = [SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, SLIP_GLIDE_RIDE_AP_REGION]
+SEQUOIA_ATHENAEUM_DISPLAY_NAME = "Sequoia Athenaeum"
+SEQUOIA_ATHENAEUM_DISPLAY_SUBREGIONS = [SEQUOIA_ATHENAEUM_AP_REGION]
+NORTHERN_STRETCH_DISPLAY_NAME = "Northern Stretch"
+#Race start is default start
+NORTHERN_STRETCH_DISPLAY_SUBREGIONS = [NORTHERN_STRETCH_RACE_START_AP_REGION, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, SUMMONERS_TOWER_AP_REGION]
+CASTLE_RAMPARTS_DISPLAY_NAME = "Castle Ramparts"
+#West save point is default start
+CASTLE_RAMPARTS_DISPLAY_SUBREGIONS = [PEAK_RAMPARTS_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, LONE_CHEST_RAMPART_AP_REGION, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION]
+THE_CHALICE_OF_TAR_DISPLAY_NAME = "The Chalice of Tar"
+THE_CHALICE_OF_TAR_DISPLAY_SUBREGIONS = [THE_CHALICE_OF_TAR_AP_REGION]
+FLYERS_CRAG_DISPLAY_NAME = "Flyer's Crag"
+FLYERS_CRAG_DISPLAY_SUBREGIONS = [FLYERS_CRAG_AP_REGION]
+JIDAMBA_TANGLE_DISPLAY_NAME = "Jidamba Tangle"
+JIDAMBA_TANGLE_DISPLAY_SUBREGIONS = [EUROPA_SHRINE_AP_REGION, JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_DIAMONDSMITH_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_CANOPY_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION, TANGLE_EACLANEYA_CONNECTOR_AP_REGION]
+JIDAMBA_EACLANEYA_DISPLAY_NAME = "Jidamba Eaclaneya"
+JIDAMBA_EACLANEYA_DISPLAY_SUBREGIONS = [JIDAMBA_EACLANEYA_AP_REGION]
+THE_DEEP_SEA_DISPLAY_NAME = "The Deep Sea"
+THE_DEEP_SEA_DISPLAY_SUBREGIONS = [THE_DEEP_SEA_AP_REGION]
+NEPTUNE_SHRINE_DISPLAY_NAME = "Neptune Shrine"
+NEPTUNE_SHRINE_DISPLAY_SUBREGIONS = [NEPTUNE_SHRINE_AP_REGION]
+JADE_CAVERN_DISPLAY_NAME = "Jade Cavern"
+JADE_CAVERN_DISPLAY_SUBREGIONS = [JADE_CAVERN_AP_REGION, JADE_WATERWAYS_AP_REGION]
+CONTINENTAL_TRAM_DISPLAY_NAME = "Continental Tram"
+CONTINENTAL_TRAM_DISPLAY_SUBREGIONS = [CONTINENTAL_TRAM_AP_REGION]
+#End-Game
+ANCIENT_LABYRINTH_DISPLAY_NAME = "Ancient Labyrinth"
+ANCIENT_LABYRINTH_DISPLAY_SUBREGIONS = [ANCIENT_LABYRINTH_AP_REGION, LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, LABYRINTH_CORE_AP_REGION]
+THE_SEQUOIA_DISPLAY_NAME = "The Sequoia"
+THE_SEQUOIA_DISPLAY_SUBREGIONS = [THE_SEQUOIA_AP_REGION]
+THE_DEPTHS_DISPLAY_NAME = "The Depths"
+THE_DEPTHS_DISPLAY_SUBREGIONS = [THE_DEPTHS_AP_REGION]
+CASTLE_SEQUOIA_DISPLAY_NAME = "Castle Sequoia"
+CASTLE_SEQUOIA_DISPLAY_SUBREGIONS = [CASTLE_SEQUOIA_AP_REGION]
+THE_OLD_WORLD_DISPLAY_NAME = "The Old World"
+THE_OLD_WORLD_DISPLAY_SUBREGIONS = [THE_OLD_WORLD_AP_REGION]
+THE_NEW_WORLD_DISPLAY_NAME = "The New World"
+THE_NEW_WORLD_DISPLAY_SUBREGIONS = [THE_NEW_WORLD_AP_REGION]
+#Mods
+MODDED_ZONE_DISPLAY_NAME = "Modded Zone"
MODDED_ZONE_DISPLAY_SUBREGIONS = [MODDED_ZONE_AP_REGION]
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/item_groups.py b/worlds/crystal_project/constants/item_groups.py
index 2b21bd3934db..785fd91a3c30 100644
--- a/worlds/crystal_project/constants/item_groups.py
+++ b/worlds/crystal_project/constants/item_groups.py
@@ -1,15 +1,15 @@
-EQUIPMENT = "Equipment"
-ITEM = "Item"
-JOB = "Job"
-KEY = "Key"
-MOUNT = "Mount"
-MAP = "Map"
-ORE = "Ore"
-SCHOLAR_ABILITY = "Scholar"
-SUMMON = "Summon"
-TELEPORT_STONE = "Stone"
-TELEPORT_SHARD = "Shard"
-PASS = "Pass"
-MOD = "Mod"
-TRAP = "Trap"
+EQUIPMENT = "Equipment"
+ITEM = "Item"
+JOB = "Job"
+KEY = "Key"
+MOUNT = "Mount"
+MAP = "Map"
+ORE = "Ore"
+SCHOLAR_ABILITY = "Scholar"
+SUMMON = "Summon"
+TELEPORT_STONE = "Stone"
+TELEPORT_SHARD = "Shard"
+PASS = "Pass"
+MOD = "Mod"
+TRAP = "Trap"
TACKLE = "Tackle"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/jobs.py b/worlds/crystal_project/constants/jobs.py
index c6d9ddcadbdc..8a38e5e70182 100644
--- a/worlds/crystal_project/constants/jobs.py
+++ b/worlds/crystal_project/constants/jobs.py
@@ -1,24 +1,24 @@
-WARRIOR_JOB = "Job - Warrior"
-MONK_JOB = "Job - Monk"
-ROGUE_JOB = "Job - Rogue"
-CLERIC_JOB = "Job - Cleric"
-WIZARD_JOB = "Job - Wizard"
-WARLOCK_JOB = "Job - Warlock"
-FENCER_JOB = "Job - Fencer"
-SHAMAN_JOB = "Job - Shaman"
-SCHOLAR_JOB = "Job - Scholar"
-AEGIS_JOB = "Job - Aegis"
-HUNTER_JOB = "Job - Hunter"
-CHEMIST_JOB = "Job - Chemist"
-REAPER_JOB = "Job - Reaper"
-NINJA_JOB = "Job - Ninja"
-NOMAD_JOB = "Job - Nomad"
-DERVISH_JOB = "Job - Dervish"
-BEATSMITH_JOB = "Job - Beatsmith"
-SAMURAI_JOB = "Job - Samurai"
-ASSASSIN_JOB = "Job - Assassin"
-VALKYRIE_JOB = "Job - Valkyrie"
-SUMMONER_JOB = "Job - Summoner"
-BEASTMASTER_JOB = "Job - Beastmaster"
-WEAVER_JOB = "Job - Weaver"
+WARRIOR_JOB = "Job - Warrior"
+MONK_JOB = "Job - Monk"
+ROGUE_JOB = "Job - Rogue"
+CLERIC_JOB = "Job - Cleric"
+WIZARD_JOB = "Job - Wizard"
+WARLOCK_JOB = "Job - Warlock"
+FENCER_JOB = "Job - Fencer"
+SHAMAN_JOB = "Job - Shaman"
+SCHOLAR_JOB = "Job - Scholar"
+AEGIS_JOB = "Job - Aegis"
+HUNTER_JOB = "Job - Hunter"
+CHEMIST_JOB = "Job - Chemist"
+REAPER_JOB = "Job - Reaper"
+NINJA_JOB = "Job - Ninja"
+NOMAD_JOB = "Job - Nomad"
+DERVISH_JOB = "Job - Dervish"
+BEATSMITH_JOB = "Job - Beatsmith"
+SAMURAI_JOB = "Job - Samurai"
+ASSASSIN_JOB = "Job - Assassin"
+VALKYRIE_JOB = "Job - Valkyrie"
+SUMMONER_JOB = "Job - Summoner"
+BEASTMASTER_JOB = "Job - Beastmaster"
+WEAVER_JOB = "Job - Weaver"
MIMIC_JOB = "Job - Mimic"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/key_items.py b/worlds/crystal_project/constants/key_items.py
index 540935aef69f..c3e2f4c5b9e8 100644
--- a/worlds/crystal_project/constants/key_items.py
+++ b/worlds/crystal_project/constants/key_items.py
@@ -1,32 +1,32 @@
-#Passes
-FERRY_PASS = "Item - Ferry Pass"
-PROGRESSIVE_LUXURY_PASS = "Item - Progressive Luxury Pass"
-#Key Items
-BLACK_SQUIRREL = "Item - Black Squirrel"
-DOG_BONE = "Item - Dog Bone"
-CLAMSHELL = "Item - Clamshell"
-DIGESTED_HEAD = "Item - Digested Head"
-LOST_PENGUIN = "Item - Lost Penguin"
-ELEVATOR_PART = "Item - Elevator Part"
-UNDERSEA_CRAB = "Item - Undersea Crab"
-WEST_LOOKOUT_TOKEN = "Item - West Lookout Token"
-CENTRAL_LOOKOUT_TOKEN = "Item - Central Lookout Token"
-NORTH_LOOKOUT_TOKEN = "Item - North Lookout Token"
-BABEL_QUINTAR = "Item - Babel Quintar"
-CRAG_DEMON_HORN = "Item - Crag Demon Horn"
-VERMILLION_BOOK = "Item - Vermillion Book"
-VIRIDIAN_BOOK = "Item - Viridian Book"
-CERULEAN_BOOK = "Item - Cerulean Book"
-ANCIENT_TABLET_A = "Item - Ancient Tablet A"
-ANCIENT_TABLET_B = "Item - Ancient Tablet B"
-ANCIENT_TABLET_C = "Item - Ancient Tablet C"
-TREASURE_FINDER = "Item - Treasure Finder"
-PROGRESSIVE_LEVEL = "Item - Progressive Level"
-SPECIAL_SHOUDU_STEW = "Item - Special Shoudu Stew"
-SPECIAL_MILK = "Item - Special Milk"
-SPECIAL_FRESH_SALMON = "Item - Special Fresh Salmon"
-SPECIAL_ROTTEN_SALMON = "Item - Special Rotten Salmon"
-DEITY_EYE = "Item - Deity Eye"
-STEM_WARD = "Item - STEM WARD"
-PROOF_OF_MERIT = "Item - Proof of Merit"
+#Passes
+FERRY_PASS = "Item - Ferry Pass"
+PROGRESSIVE_LUXURY_PASS = "Item - Progressive Luxury Pass"
+#Key Items
+BLACK_SQUIRREL = "Item - Black Squirrel"
+DOG_BONE = "Item - Dog Bone"
+CLAMSHELL = "Item - Clamshell"
+DIGESTED_HEAD = "Item - Digested Head"
+LOST_PENGUIN = "Item - Lost Penguin"
+ELEVATOR_PART = "Item - Elevator Part"
+UNDERSEA_CRAB = "Item - Undersea Crab"
+WEST_LOOKOUT_TOKEN = "Item - West Lookout Token"
+CENTRAL_LOOKOUT_TOKEN = "Item - Central Lookout Token"
+NORTH_LOOKOUT_TOKEN = "Item - North Lookout Token"
+BABEL_QUINTAR = "Item - Babel Quintar"
+CRAG_DEMON_HORN = "Item - Crag Demon Horn"
+VERMILLION_BOOK = "Item - Vermillion Book"
+VIRIDIAN_BOOK = "Item - Viridian Book"
+CERULEAN_BOOK = "Item - Cerulean Book"
+ANCIENT_TABLET_A = "Item - Ancient Tablet A"
+ANCIENT_TABLET_B = "Item - Ancient Tablet B"
+ANCIENT_TABLET_C = "Item - Ancient Tablet C"
+TREASURE_FINDER = "Item - Treasure Finder"
+PROGRESSIVE_LEVEL = "Item - Progressive Level"
+SPECIAL_SHOUDU_STEW = "Item - Special Shoudu Stew"
+SPECIAL_MILK = "Item - Special Milk"
+SPECIAL_FRESH_SALMON = "Item - Special Fresh Salmon"
+SPECIAL_ROTTEN_SALMON = "Item - Special Rotten Salmon"
+DEITY_EYE = "Item - Deity Eye"
+STEM_WARD = "Item - STEM WARD"
+PROOF_OF_MERIT = "Item - Proof of Merit"
HOMEPOINT_UNLOCK = "Item - Homepoint Unlock"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/keys.py b/worlds/crystal_project/constants/keys.py
index 484e10a86a56..f45bfb2f9907 100644
--- a/worlds/crystal_project/constants/keys.py
+++ b/worlds/crystal_project/constants/keys.py
@@ -1,27 +1,27 @@
-GARDENERS_KEY = "Item - Gardeners Key"
-COURTYARD_KEY = "Item - Courtyard Key"
-LUXURY_KEY = "Item - Luxury Key"
-CELL_KEY = "Item - Cell Key"
-SOUTH_WING_KEY = "Item - South Wing Key"
-EAST_WING_KEY = "Item - East Wing Key"
-WEST_WING_KEY = "Item - West Wing Key"
-DARK_WING_KEY = "Item - Dark Wing Key"
-ROOM_ONE_KEY = "Item - Room 1 Key"
-PYRAMID_KEY = "Item - Pyramid Key"
-TRAM_KEY = "Item - Tram Key"
-SMALL_KEY = "Item - Small Key"
-BEAURIOR_BOSS_KEY = "Item - Boss Key"
-ICE_CELL_KEY = "Item - Ice Cell Key"
-RED_DOOR_KEY = "Item - Red Door Key"
-ICE_PUZZLE_KEY = "Item - Ice Puzzle Key"
-FOLIAGE_KEY = "Item - Foliage Key"
-CAVE_KEY = "Item - Cave Key"
-CANOPY_KEY = "Item - Canopy Key"
-RAMPART_KEY = "Item - Rampart Key"
-FORGOTTEN_KEY = "Item - Forgotten Key"
-SKELETON_KEY = "Item - Skeleton Key"
-PRISON_KEY_RING = "Item - Prison Key Ring"
-BEAURIOR_KEY_RING = "Item - Beaurior Key Ring"
-SLIP_GLIDE_RIDE_KEY_RING = "Item - Slip Glide Ride Key Ring"
-ICE_PUZZLE_KEY_RING = "Item - Ice Puzzle Key Ring"
+GARDENERS_KEY = "Item - Gardeners Key"
+COURTYARD_KEY = "Item - Courtyard Key"
+LUXURY_KEY = "Item - Luxury Key"
+CELL_KEY = "Item - Cell Key"
+SOUTH_WING_KEY = "Item - South Wing Key"
+EAST_WING_KEY = "Item - East Wing Key"
+WEST_WING_KEY = "Item - West Wing Key"
+DARK_WING_KEY = "Item - Dark Wing Key"
+ROOM_ONE_KEY = "Item - Room 1 Key"
+PYRAMID_KEY = "Item - Pyramid Key"
+TRAM_KEY = "Item - Tram Key"
+SMALL_KEY = "Item - Small Key"
+BEAURIOR_BOSS_KEY = "Item - Boss Key"
+ICE_CELL_KEY = "Item - Ice Cell Key"
+RED_DOOR_KEY = "Item - Red Door Key"
+ICE_PUZZLE_KEY = "Item - Ice Puzzle Key"
+FOLIAGE_KEY = "Item - Foliage Key"
+CAVE_KEY = "Item - Cave Key"
+CANOPY_KEY = "Item - Canopy Key"
+RAMPART_KEY = "Item - Rampart Key"
+FORGOTTEN_KEY = "Item - Forgotten Key"
+SKELETON_KEY = "Item - Skeleton Key"
+PRISON_KEY_RING = "Item - Prison Key Ring"
+BEAURIOR_KEY_RING = "Item - Beaurior Key Ring"
+SLIP_GLIDE_RIDE_KEY_RING = "Item - Slip Glide Ride Key Ring"
+ICE_PUZZLE_KEY_RING = "Item - Ice Puzzle Key Ring"
JIDAMBA_KEY_RING = "Item - Jidamba Key Ring"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/level_requirements.py b/worlds/crystal_project/constants/level_requirements.py
index ee12ce49d316..67d0a41bd743 100644
--- a/worlds/crystal_project/constants/level_requirements.py
+++ b/worlds/crystal_project/constants/level_requirements.py
@@ -1,70 +1,70 @@
-#Regions mostly without combat
-QUINTAR_RESERVE_ENEMY_LEVEL = 54
-CAPITAL_SEQUOIA_ENEMY_LEVEL = 18
-
-#Lost Son
-POKO_POKO_ENEMY_LEVEL = 30
-
-#Summons
-SHAKU_FIGHT_LEVEL = 54
-NILTSI_FIGHT_LEVEL = 54
-GUABA_FIGHT_LEVEL = 58
-PAH_FIGHT_LEVEL = 58
-TIRA_FIGHT_LEVEL = 57
-IOSKE_FIGHT_LEVEL = 57
-PAMOA_FIGHT_LEVEL = 53
-JUSES_FIGHT_LEVEL = 58
-COYOTE_FIGHT_LEVEL = 58
-
-#Bosses
-TROLL_FIGHT_LEVEL = 50
-GRAN_FIGHT_LEVEL = 30
-BONE_THIEF_FIGHT_LEVEL = 12
-GUARDIAN_FIGHT_LEVEL = 12
-CANAL_BEAST_FIGHT_LEVEL = 12
-SEPULCHRA_FIGHT_LEVEL = 18
-KNIGHT_FIGHT_LEVEL = 10
-PARASITE_FIGHT_LEVEL = 19
-DUMMY_BOSS_FIGHT_LEVEL = 60
-ENAMI_FIGHT_LEVEL = 58
-BLOOD_SLOP_FIGHT_LEVEL = 26
-FANCY_QUINTAR_FIGHT_LEVEL = 26
-WARDEN_FIGHT_LEVEL = 27
-CRAG_DEMON_FIGHT_LEVEL = 50
-KUROMANTO_FIGHT_LEVEL = 29
-POSSESSOR_FIGHT_LEVEL = 35
-ARACHLEA_FIGHT_LEVEL = 58
-BLADE_MASTER_FIGHT_LEVEL = 40
-SHADOW_MASTER_FIGHT_LEVEL = 40
-DUEL_MASTER_FIGHT_LEVEL = 40
-ANCIENT_SWORD_FIGHT_LEVEL = 39
-IGUANADON_AND_DIN_FIGHT_LEVEL = 40
-UNDERGROWTH_FIGHT_LEVEL = 60
-HERMETIC_FIGHT_LEVEL = 55
-OWLBEAR_FIGHT_LEVEL = 47
-RED_GUARDIAN_FIGHT_LEVEL = 49
-RAMPART_DEMON_FIGHT_LEVEL = 54
-CONSCRIPT_FIGHT_LEVEL = 60
-ANUBIS_FIGHT_LEVEL = 62
-SPIRIT_CAGE_FIGHT_LEVEL = 63
-DEVOURER_FIGHT_LEVEL = 65
-OLD_ONE_FIGHT_LEVEL = 65
-ENFORCER_FIGHT_LEVEL = 65
-PEACEKEEPER_FIGHT_LEVEL = 63
-GABRIEL_FIGHT_LEVEL = 65
-PARASITE_X_FIGHT_LEVEL = 56
-NEO_WARDEN_FIGHT_LEVEL = 57
-AKAMANTO_FIGHT_LEVEL = 58
-PERICULUM_FIGHT_LEVEL = 70
-
-#Sky Arena
-FIRST_SKY_ARENA_FIGHT_LEVEL = 30
-SECOND_SKY_ARENA_FIGHT_LEVEL = 32
-THIRD_SKY_ARENA_FIGHT_LEVEL = 35
-FOURTH_SKY_ARENA_FIGHT_LEVEL = 40
-FIFTH_SKY_ARENA_FIGHT_LEVEL = 44
-SIXTH_SKY_ARENA_FIGHT_LEVEL = 48
-SEVENTH_SKY_ARENA_FIGHT_LEVEL = 52
-EIGHTH_SKY_ARENA_FIGHT_LEVEL = 54
-NINTH_SKY_ARENA_FIGHT_LEVEL = 56
+#Regions mostly without combat
+QUINTAR_RESERVE_ENEMY_LEVEL = 54
+CAPITAL_SEQUOIA_ENEMY_LEVEL = 18
+
+#Lost Son
+POKO_POKO_ENEMY_LEVEL = 30
+
+#Summons
+SHAKU_FIGHT_LEVEL = 54
+NILTSI_FIGHT_LEVEL = 54
+GUABA_FIGHT_LEVEL = 58
+PAH_FIGHT_LEVEL = 58
+TIRA_FIGHT_LEVEL = 57
+IOSKE_FIGHT_LEVEL = 57
+PAMOA_FIGHT_LEVEL = 53
+JUSES_FIGHT_LEVEL = 58
+COYOTE_FIGHT_LEVEL = 58
+
+#Bosses
+TROLL_FIGHT_LEVEL = 50
+GRAN_FIGHT_LEVEL = 30
+BONE_THIEF_FIGHT_LEVEL = 12
+GUARDIAN_FIGHT_LEVEL = 12
+CANAL_BEAST_FIGHT_LEVEL = 12
+SEPULCHRA_FIGHT_LEVEL = 18
+KNIGHT_FIGHT_LEVEL = 10
+PARASITE_FIGHT_LEVEL = 19
+DUMMY_BOSS_FIGHT_LEVEL = 60
+ENAMI_FIGHT_LEVEL = 58
+BLOOD_SLOP_FIGHT_LEVEL = 26
+FANCY_QUINTAR_FIGHT_LEVEL = 26
+WARDEN_FIGHT_LEVEL = 27
+CRAG_DEMON_FIGHT_LEVEL = 50
+KUROMANTO_FIGHT_LEVEL = 29
+POSSESSOR_FIGHT_LEVEL = 35
+ARACHLEA_FIGHT_LEVEL = 58
+BLADE_MASTER_FIGHT_LEVEL = 40
+SHADOW_MASTER_FIGHT_LEVEL = 40
+DUEL_MASTER_FIGHT_LEVEL = 40
+ANCIENT_SWORD_FIGHT_LEVEL = 39
+IGUANADON_AND_DIN_FIGHT_LEVEL = 40
+UNDERGROWTH_FIGHT_LEVEL = 60
+HERMETIC_FIGHT_LEVEL = 55
+OWLBEAR_FIGHT_LEVEL = 47
+RED_GUARDIAN_FIGHT_LEVEL = 49
+RAMPART_DEMON_FIGHT_LEVEL = 54
+CONSCRIPT_FIGHT_LEVEL = 60
+ANUBIS_FIGHT_LEVEL = 62
+SPIRIT_CAGE_FIGHT_LEVEL = 63
+DEVOURER_FIGHT_LEVEL = 65
+OLD_ONE_FIGHT_LEVEL = 65
+ENFORCER_FIGHT_LEVEL = 65
+PEACEKEEPER_FIGHT_LEVEL = 63
+GABRIEL_FIGHT_LEVEL = 65
+PARASITE_X_FIGHT_LEVEL = 56
+NEO_WARDEN_FIGHT_LEVEL = 57
+AKAMANTO_FIGHT_LEVEL = 58
+PERICULUM_FIGHT_LEVEL = 70
+
+#Sky Arena
+FIRST_SKY_ARENA_FIGHT_LEVEL = 30
+SECOND_SKY_ARENA_FIGHT_LEVEL = 32
+THIRD_SKY_ARENA_FIGHT_LEVEL = 35
+FOURTH_SKY_ARENA_FIGHT_LEVEL = 40
+FIFTH_SKY_ARENA_FIGHT_LEVEL = 44
+SIXTH_SKY_ARENA_FIGHT_LEVEL = 48
+SEVENTH_SKY_ARENA_FIGHT_LEVEL = 52
+EIGHTH_SKY_ARENA_FIGHT_LEVEL = 54
+NINTH_SKY_ARENA_FIGHT_LEVEL = 56
#TENTH_SKY_ARENA_FIGHT_LEVEL = ARACHLEA_FIGHT_LEVEL
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/maps.py b/worlds/crystal_project/constants/maps.py
index 447300946b9c..d55a736a82c4 100644
--- a/worlds/crystal_project/constants/maps.py
+++ b/worlds/crystal_project/constants/maps.py
@@ -1,68 +1,68 @@
-SPAWNING_MEADOWS_MAP = "Item - Spawning Meadows Map"
-DELENDE_MAP = "Item - Delende Map"
-THE_PALE_GROTTO_MAP = "Item - The Pale Grotto Map"
-SEASIDE_CLIFFS_MAP = "Item - Seaside Cliffs Map"
-DRAFT_SHAFT_CONDUIT_MAP = "Item - Draft Shaft Conduit Map"
-PROVING_MEADOWS_MAP = "Item - Proving Meadows Map"
-SOILED_DEN_MAP = "Item - Soiled Den Map"
-YAMAGAWA_MA_MAP = "Item - Yamagawa M.A. Map"
-SKUMPARADISE_MAP = "Item - Skumparadise Map"
-CAPITAL_COURTYARD_MAP = "Item - Capital Courtyard Map"
-CAPITAL_SEQUOIA_MAP = "Item - Capital Sequoia Map"
-JOJO_SEWERS_MAP = "Item - Jojo Sewers Map"
-GREENSHIRE_REPRISE_MAP = "Item - Greenshire Reprise Map"
-MERCURY_SHRINE_MAP = "Item - Mercury Shrine Map"
-BOOMER_SOCIETY_MAP = "Item - Boomer Society Map"
-ROLLING_QUINTAR_FIELDS_MAP = "Item - Rolling Quintar Fields Map"
-QUINTAR_NEST_MAP = "Item - Quintar Nest Map"
-CAPITAL_JAIL_MAP = "Item - Capital Jail Map"
-COBBLESTONE_CRAG_MAP = "Item - Cobblestone Crag Map"
-OKIMOTO_NS_MAP = "Item - Okimoto N.S. Map"
-SALMON_PASS_MAP = "Item - Salmon Pass Map"
-SALMON_RIVER_MAP = "Item - Salmon River Map"
-POSEIDON_SHRINE_MAP = "Item - Poseidon Shrine Map"
-POKO_POKO_DESERT_MAP = "Item - Poko Poko Desert Map"
-SARA_SARA_BAZAAR_MAP = "Item - Sara Sara Bazaar Map"
-SARA_SARA_BEACH_MAP = "Item - Sara Sara Beach Map"
-ANCIENT_RESERVOIR_MAP = "Item - Ancient Reservoir Map"
-SHOUDU_PROVINCE_MAP = "Item - Shoudu Province Map"
-THE_UNDERCITY_MAP = "Item - The Undercity Map"
-BEAURIOR_VOLCANO_MAP = "Item - Beaurior Volcano Map"
-BEAURIOR_ROCK_MAP = "Item - Beaurior Rock Map"
-THE_SEQUOIA_MAP = "Item - The Sequoia Map"
-TALL_TALL_HEIGHTS_MAP = "Item - Tall, Tall Heights Map"
-SLIP_GLIDE_RIDE_MAP = "Item - Slip Glide Ride Map"
-GANYMEDE_SHRINE_MAP = "Item - Ganymede Shrine Map"
-QUINTAR_RESERVE_MAP = "Item - Quintar Reserve Map"
-QUINTAR_SANCTUM_MAP = "Item - Quintar Sanctum Map"
-LAKE_DELENDE_MAP = "Item - Lake Delende Map"
-JIDAMBA_TANGLE_MAP = "Item - Jidamba Tangle Map"
-JIDAMBA_EACLANEYA_MAP = "Item - Jidamba Eaclaneya Map"
-THE_DEEP_SEA_MAP = "Item - The Deep Sea Map"
-THE_NEW_WORLD_MAP = "Item - The New World Map"
-CONTINENTAL_TRAM_MAP = "Item - Continental Tram Map"
-CASTLE_RAMPARTS_MAP = "Item - Castle Ramparts Map"
-SALMON_BAY_MAP = "Item - Salmon Bay Map"
-LANDS_END_MAP = "Item - Land's End Map"
-CAPITAL_PIPELINE_MAP = "Item - Capital Pipeline Map"
-NORTHERN_CAVE_MAP = "Item - Northern Cave Map"
-THE_DEPTHS_MAP = "Item - The Depths Map"
-THE_OPEN_SEA_MAP = "Item - The Open Sea Map"
-DIONE_SHRINE_MAP = "Item - Dione Shrine Map"
-NEPTUNE_SHRINE_MAP = "Item - Neptune Shrine Map"
-CASTLE_SEQUOIA_MAP = "Item - Castle Sequoia Map"
-ANCIENT_LABYRINTH_MAP = "Item - Ancient Labyrinth Map"
-QUINTAR_MAUSOLEUM_MAP = "Item - Quintar Mausoleum Map"
-BASEMENT_MAP = "Item - Basement Map"
-TRIAL_CAVES_MAP = "Item - Trial Caves Map"
-OVERPASS_MAP = "Item - Overpass Map"
-UNDERPASS_MAP = "Item - Underpass Map"
-RIVER_CATS_EGO_MAP = "Item - River Cat's Ego Map"
-NORTHERN_STRETCH_MAP = "Item - Northern Stretch Map"
-EASTERN_CHASM_MAP = "Item - Eastern Chasm Map"
-SEQUOIA_ATHENAEUM_MAP = "Item - Sequoia Athenaeum Map"
-THE_CHALICE_OF_TAR_MAP = "Item - The Chalice of Tar Map"
-FLYERS_CRAG_MAP = "Item - Flyer's Crag Map"
-FLYERS_LOOKOUT_MAP = "Item - Flyer's Lookout Map"
-JADE_CAVERN_MAP = "Item - Jade Cavern Map"
+SPAWNING_MEADOWS_MAP = "Item - Spawning Meadows Map"
+DELENDE_MAP = "Item - Delende Map"
+THE_PALE_GROTTO_MAP = "Item - The Pale Grotto Map"
+SEASIDE_CLIFFS_MAP = "Item - Seaside Cliffs Map"
+DRAFT_SHAFT_CONDUIT_MAP = "Item - Draft Shaft Conduit Map"
+PROVING_MEADOWS_MAP = "Item - Proving Meadows Map"
+SOILED_DEN_MAP = "Item - Soiled Den Map"
+YAMAGAWA_MA_MAP = "Item - Yamagawa M.A. Map"
+SKUMPARADISE_MAP = "Item - Skumparadise Map"
+CAPITAL_COURTYARD_MAP = "Item - Capital Courtyard Map"
+CAPITAL_SEQUOIA_MAP = "Item - Capital Sequoia Map"
+JOJO_SEWERS_MAP = "Item - Jojo Sewers Map"
+GREENSHIRE_REPRISE_MAP = "Item - Greenshire Reprise Map"
+MERCURY_SHRINE_MAP = "Item - Mercury Shrine Map"
+BOOMER_SOCIETY_MAP = "Item - Boomer Society Map"
+ROLLING_QUINTAR_FIELDS_MAP = "Item - Rolling Quintar Fields Map"
+QUINTAR_NEST_MAP = "Item - Quintar Nest Map"
+CAPITAL_JAIL_MAP = "Item - Capital Jail Map"
+COBBLESTONE_CRAG_MAP = "Item - Cobblestone Crag Map"
+OKIMOTO_NS_MAP = "Item - Okimoto N.S. Map"
+SALMON_PASS_MAP = "Item - Salmon Pass Map"
+SALMON_RIVER_MAP = "Item - Salmon River Map"
+POSEIDON_SHRINE_MAP = "Item - Poseidon Shrine Map"
+POKO_POKO_DESERT_MAP = "Item - Poko Poko Desert Map"
+SARA_SARA_BAZAAR_MAP = "Item - Sara Sara Bazaar Map"
+SARA_SARA_BEACH_MAP = "Item - Sara Sara Beach Map"
+ANCIENT_RESERVOIR_MAP = "Item - Ancient Reservoir Map"
+SHOUDU_PROVINCE_MAP = "Item - Shoudu Province Map"
+THE_UNDERCITY_MAP = "Item - The Undercity Map"
+BEAURIOR_VOLCANO_MAP = "Item - Beaurior Volcano Map"
+BEAURIOR_ROCK_MAP = "Item - Beaurior Rock Map"
+THE_SEQUOIA_MAP = "Item - The Sequoia Map"
+TALL_TALL_HEIGHTS_MAP = "Item - Tall, Tall Heights Map"
+SLIP_GLIDE_RIDE_MAP = "Item - Slip Glide Ride Map"
+GANYMEDE_SHRINE_MAP = "Item - Ganymede Shrine Map"
+QUINTAR_RESERVE_MAP = "Item - Quintar Reserve Map"
+QUINTAR_SANCTUM_MAP = "Item - Quintar Sanctum Map"
+LAKE_DELENDE_MAP = "Item - Lake Delende Map"
+JIDAMBA_TANGLE_MAP = "Item - Jidamba Tangle Map"
+JIDAMBA_EACLANEYA_MAP = "Item - Jidamba Eaclaneya Map"
+THE_DEEP_SEA_MAP = "Item - The Deep Sea Map"
+THE_NEW_WORLD_MAP = "Item - The New World Map"
+CONTINENTAL_TRAM_MAP = "Item - Continental Tram Map"
+CASTLE_RAMPARTS_MAP = "Item - Castle Ramparts Map"
+SALMON_BAY_MAP = "Item - Salmon Bay Map"
+LANDS_END_MAP = "Item - Land's End Map"
+CAPITAL_PIPELINE_MAP = "Item - Capital Pipeline Map"
+NORTHERN_CAVE_MAP = "Item - Northern Cave Map"
+THE_DEPTHS_MAP = "Item - The Depths Map"
+THE_OPEN_SEA_MAP = "Item - The Open Sea Map"
+DIONE_SHRINE_MAP = "Item - Dione Shrine Map"
+NEPTUNE_SHRINE_MAP = "Item - Neptune Shrine Map"
+CASTLE_SEQUOIA_MAP = "Item - Castle Sequoia Map"
+ANCIENT_LABYRINTH_MAP = "Item - Ancient Labyrinth Map"
+QUINTAR_MAUSOLEUM_MAP = "Item - Quintar Mausoleum Map"
+BASEMENT_MAP = "Item - Basement Map"
+TRIAL_CAVES_MAP = "Item - Trial Caves Map"
+OVERPASS_MAP = "Item - Overpass Map"
+UNDERPASS_MAP = "Item - Underpass Map"
+RIVER_CATS_EGO_MAP = "Item - River Cat's Ego Map"
+NORTHERN_STRETCH_MAP = "Item - Northern Stretch Map"
+EASTERN_CHASM_MAP = "Item - Eastern Chasm Map"
+SEQUOIA_ATHENAEUM_MAP = "Item - Sequoia Athenaeum Map"
+THE_CHALICE_OF_TAR_MAP = "Item - The Chalice of Tar Map"
+FLYERS_CRAG_MAP = "Item - Flyer's Crag Map"
+FLYERS_LOOKOUT_MAP = "Item - Flyer's Lookout Map"
+JADE_CAVERN_MAP = "Item - Jade Cavern Map"
THE_OLD_WORLD_MAP = "Item - The Old World Map"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/mounts.py b/worlds/crystal_project/constants/mounts.py
index d95ca83de064..901b5123e168 100644
--- a/worlds/crystal_project/constants/mounts.py
+++ b/worlds/crystal_project/constants/mounts.py
@@ -1,5 +1,5 @@
-PROGRESSIVE_QUINTAR_WOODWIND = "Item - Progressive Quintar Flute"
-IBEK_BELL = "Item - Ibek Bell"
-OWL_DRUM = "Item - Owl Drum"
-PROGRESSIVE_SALMON_VIOLA = "Item - Progressive Salmon Violin"
+PROGRESSIVE_QUINTAR_WOODWIND = "Item - Progressive Quintar Flute"
+IBEK_BELL = "Item - Ibek Bell"
+OWL_DRUM = "Item - Owl Drum"
+PROGRESSIVE_SALMON_VIOLA = "Item - Progressive Salmon Violin"
PROGRESSIVE_MOUNT = "Item - Progressive Mount Instrument"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/region_passes.py b/worlds/crystal_project/constants/region_passes.py
index d25b362dcc0e..23de4adc6b84 100644
--- a/worlds/crystal_project/constants/region_passes.py
+++ b/worlds/crystal_project/constants/region_passes.py
@@ -1,65 +1,65 @@
-SPAWNING_MEADOWS_PASS = "Item - Spawning Meadows Pass"
-DELENDE_PASS = "Item - Delende Pass"
-MERCURY_SHRINE_PASS = "Item - Mercury Shrine Pass"
-SOILED_DEN_PASS = "Item - Soiled Den Pass"
-THE_PALE_GROTTO_PASS = "Item - The Pale Grotto Pass"
-SEASIDE_CLIFFS_PASS = "Item - Seaside Cliffs Pass"
-DRAFT_SHAFT_CONDUIT_PASS = "Item - Draft Shaft Conduit Pass"
-YAMAGAWA_MA_PASS = "Item - Yamagawa M.A. Pass"
-PROVING_MEADOWS_PASS = "Item - Proving Meadows Pass"
-SKUMPARADISE_PASS = "Item - Skumparadise Pass"
-#Advanced
-CAPITAL_SEQUOIA_PASS = "Item - Capital Sequoia Pass"
-JOJO_SEWERS_PASS = "Item - Jojo Sewers Pass"
-BOOMER_SOCIETY_PASS = "Item - Boomer Society Pass"
-ROLLING_QUINTAR_FIELDS_PASS = "Item - Rolling Quintar Fields Pass"
-QUINTAR_NEST_PASS = "Item - Quintar Nest Pass"
-QUINTAR_SANCTUM_PASS = "Item - Quintar Sanctum Pass"
-CAPITAL_JAIL_PASS = "Item - Capital Jail Pass"
-CAPITAL_PIPELINE_PASS = "Item - Capital Pipeline Pass"
-COBBLESTONE_CRAG_PASS = "Item - Cobblestone Crag Pass"
-OKIMOTO_NS_PASS = "Item - Okimoto N.S. Pass"
-GREENSHIRE_REPRISE_PASS = "Item - Greenshire Reprise Pass"
-SALMON_PASS_PASS = "Item - Salmon Pass Pass"
-SALMON_RIVER_PASS = "Item - Salmon River Pass"
-SHOUDU_WATERFRONT_PASS = "Item - Shoudu Waterfront Pass"
-POKO_POKO_DESERT_PASS = "Item - Poko Poko Desert Pass"
-SARA_SARA_BAZAAR_PASS = "Item - Sara Sara Bazaar Pass"
-SARA_SARA_BEACH_EAST_PASS = "Item - Sara Sara Beach East Pass"
-SARA_SARA_BEACH_WEST_PASS = "Item - Sara Sara Beach West Pass"
-ANCIENT_RESERVOIR_PASS = "Item - Ancient Reservoir Pass"
-SALMON_BAY_PASS = "Item - Salmon Bay Pass"
-#Expert
-THE_OPEN_SEA_PASS = "Item - The Open Sea Pass"
-SHOUDU_PROVINCE_PASS = "Item - Shoudu Province Pass"
-THE_UNDERCITY_PASS = "Item - The Undercity Pass"
-GANYMEDE_SHRINE_PASS = "Item - Ganymede Shrine Pass"
-BEAURIOR_VOLCANO_PASS = "Item - Beaurior Volcano Pass"
-BEAURIOR_ROCK_PASS = "Item - Beaurior Rock Pass"
-LAKE_DELENDE_PASS = "Item - Lake Delende Pass"
-QUINTAR_RESERVE_PASS = "Item - Quintar Reserve Pass"
-DIONE_SHRINE_PASS = "Item - Dione Shrine Pass"
-QUINTAR_MAUSOLEUM_PASS = "Item - Quintar Mausoleum Pass"
-EASTERN_CHASM_PASS = "Item - Eastern Chasm Pass"
-TALL_TALL_HEIGHTS_PASS = "Item - Tall, Tall Heights Pass"
-NORTHERN_CAVE_PASS = "Item - Northern Cave Pass"
-LANDS_END_PASS = "Item - Land's End Pass"
-SLIP_GLIDE_RIDE_PASS = "Item - Slip Glide Ride Pass"
-SEQUOIA_ATHENAEUM_PASS = "Item - Sequoia Athenaeum Pass"
-NORTHERN_STRETCH_PASS = "Item - Northern Stretch Pass"
-CASTLE_RAMPARTS_PASS = "Item - Castle Ramparts Pass"
-THE_CHALICE_OF_TAR_PASS = "Item - The Chalice of Tar Pass"
-FLYERS_CRAG_PASS = "Item - Flyer's Crag Pass"
-JIDAMBA_TANGLE_PASS = "Item - Jidamba Tangle Pass"
-JIDAMBA_EACLANEYA_PASS = "Item - Jidamba Eaclaneya Pass"
-THE_DEEP_SEA_PASS = "Item - The Deep Sea Pass"
-NEPTUNE_SHRINE_PASS = "Item - Neptune Shrine Pass"
-JADE_CAVERN_PASS = "Item - Jade Cavern Pass"
-CONTINENTAL_TRAM_PASS = "Item - Continental Tram Pass"
-#End Game
-ANCIENT_LABYRINTH_PASS = "Item - Ancient Labyrinth Pass"
-THE_SEQUOIA_PASS = "Item - The Sequoia Pass"
-THE_DEPTHS_PASS = "Item - The Depths Pass"
-CASTLE_SEQUOIA_PASS = "Item - Castle Sequoia Pass"
-THE_OLD_WORLD_PASS = "Item - The Old World Pass"
+SPAWNING_MEADOWS_PASS = "Item - Spawning Meadows Pass"
+DELENDE_PASS = "Item - Delende Pass"
+MERCURY_SHRINE_PASS = "Item - Mercury Shrine Pass"
+SOILED_DEN_PASS = "Item - Soiled Den Pass"
+THE_PALE_GROTTO_PASS = "Item - The Pale Grotto Pass"
+SEASIDE_CLIFFS_PASS = "Item - Seaside Cliffs Pass"
+DRAFT_SHAFT_CONDUIT_PASS = "Item - Draft Shaft Conduit Pass"
+YAMAGAWA_MA_PASS = "Item - Yamagawa M.A. Pass"
+PROVING_MEADOWS_PASS = "Item - Proving Meadows Pass"
+SKUMPARADISE_PASS = "Item - Skumparadise Pass"
+#Advanced
+CAPITAL_SEQUOIA_PASS = "Item - Capital Sequoia Pass"
+JOJO_SEWERS_PASS = "Item - Jojo Sewers Pass"
+BOOMER_SOCIETY_PASS = "Item - Boomer Society Pass"
+ROLLING_QUINTAR_FIELDS_PASS = "Item - Rolling Quintar Fields Pass"
+QUINTAR_NEST_PASS = "Item - Quintar Nest Pass"
+QUINTAR_SANCTUM_PASS = "Item - Quintar Sanctum Pass"
+CAPITAL_JAIL_PASS = "Item - Capital Jail Pass"
+CAPITAL_PIPELINE_PASS = "Item - Capital Pipeline Pass"
+COBBLESTONE_CRAG_PASS = "Item - Cobblestone Crag Pass"
+OKIMOTO_NS_PASS = "Item - Okimoto N.S. Pass"
+GREENSHIRE_REPRISE_PASS = "Item - Greenshire Reprise Pass"
+SALMON_PASS_PASS = "Item - Salmon Pass Pass"
+SALMON_RIVER_PASS = "Item - Salmon River Pass"
+SHOUDU_WATERFRONT_PASS = "Item - Shoudu Waterfront Pass"
+POKO_POKO_DESERT_PASS = "Item - Poko Poko Desert Pass"
+SARA_SARA_BAZAAR_PASS = "Item - Sara Sara Bazaar Pass"
+SARA_SARA_BEACH_EAST_PASS = "Item - Sara Sara Beach East Pass"
+SARA_SARA_BEACH_WEST_PASS = "Item - Sara Sara Beach West Pass"
+ANCIENT_RESERVOIR_PASS = "Item - Ancient Reservoir Pass"
+SALMON_BAY_PASS = "Item - Salmon Bay Pass"
+#Expert
+THE_OPEN_SEA_PASS = "Item - The Open Sea Pass"
+SHOUDU_PROVINCE_PASS = "Item - Shoudu Province Pass"
+THE_UNDERCITY_PASS = "Item - The Undercity Pass"
+GANYMEDE_SHRINE_PASS = "Item - Ganymede Shrine Pass"
+BEAURIOR_VOLCANO_PASS = "Item - Beaurior Volcano Pass"
+BEAURIOR_ROCK_PASS = "Item - Beaurior Rock Pass"
+LAKE_DELENDE_PASS = "Item - Lake Delende Pass"
+QUINTAR_RESERVE_PASS = "Item - Quintar Reserve Pass"
+DIONE_SHRINE_PASS = "Item - Dione Shrine Pass"
+QUINTAR_MAUSOLEUM_PASS = "Item - Quintar Mausoleum Pass"
+EASTERN_CHASM_PASS = "Item - Eastern Chasm Pass"
+TALL_TALL_HEIGHTS_PASS = "Item - Tall, Tall Heights Pass"
+NORTHERN_CAVE_PASS = "Item - Northern Cave Pass"
+LANDS_END_PASS = "Item - Land's End Pass"
+SLIP_GLIDE_RIDE_PASS = "Item - Slip Glide Ride Pass"
+SEQUOIA_ATHENAEUM_PASS = "Item - Sequoia Athenaeum Pass"
+NORTHERN_STRETCH_PASS = "Item - Northern Stretch Pass"
+CASTLE_RAMPARTS_PASS = "Item - Castle Ramparts Pass"
+THE_CHALICE_OF_TAR_PASS = "Item - The Chalice of Tar Pass"
+FLYERS_CRAG_PASS = "Item - Flyer's Crag Pass"
+JIDAMBA_TANGLE_PASS = "Item - Jidamba Tangle Pass"
+JIDAMBA_EACLANEYA_PASS = "Item - Jidamba Eaclaneya Pass"
+THE_DEEP_SEA_PASS = "Item - The Deep Sea Pass"
+NEPTUNE_SHRINE_PASS = "Item - Neptune Shrine Pass"
+JADE_CAVERN_PASS = "Item - Jade Cavern Pass"
+CONTINENTAL_TRAM_PASS = "Item - Continental Tram Pass"
+#End Game
+ANCIENT_LABYRINTH_PASS = "Item - Ancient Labyrinth Pass"
+THE_SEQUOIA_PASS = "Item - The Sequoia Pass"
+THE_DEPTHS_PASS = "Item - The Depths Pass"
+CASTLE_SEQUOIA_PASS = "Item - Castle Sequoia Pass"
+THE_OLD_WORLD_PASS = "Item - The Old World Pass"
THE_NEW_WORLD_PASS = "Item - The New World Pass"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/scholar_abilities.py b/worlds/crystal_project/constants/scholar_abilities.py
index 01752caea35b..5c7b8473d6b2 100644
--- a/worlds/crystal_project/constants/scholar_abilities.py
+++ b/worlds/crystal_project/constants/scholar_abilities.py
@@ -1,20 +1,20 @@
-ROOST = "Scholar - Roost"
-LUCKY_DICE = "Scholar - Lucky Dice"
-SUN_BATH = "Scholar - Sun Bath"
-SLEEP_AURA = "Scholar - Sleep Aura"
-REGENERATE = "Scholar - Regenerate"
-REVERSE_POLARITY = "Scholar - Reverse Polarity"
-BARRIER = "Scholar - Barrier"
-MP_SICKLE = "Scholar - MP Sickle"
-ADRENALINE = "Scholar - Adrenaline"
-FIRE_BREATH = "Scholar - Fire Breath"
-EXPLODE = "Scholar - Explode"
-WHIRLWIND = "Scholar - Whirlwind"
-ATMOSHEAR = "Scholar - Atmoshear"
-BUILD_LIFE = "Scholar - Build Life"
-AERO = "Scholar - Aero"
-INSULT = "Scholar - Insult"
-INFUSION = "Scholar - Infusion"
-OVERlOAD = "Scholar - Overload"
-REFLECTION = "Scholar - Reflection"
+ROOST = "Scholar - Roost"
+LUCKY_DICE = "Scholar - Lucky Dice"
+SUN_BATH = "Scholar - Sun Bath"
+SLEEP_AURA = "Scholar - Sleep Aura"
+REGENERATE = "Scholar - Regenerate"
+REVERSE_POLARITY = "Scholar - Reverse Polarity"
+BARRIER = "Scholar - Barrier"
+MP_SICKLE = "Scholar - MP Sickle"
+ADRENALINE = "Scholar - Adrenaline"
+FIRE_BREATH = "Scholar - Fire Breath"
+EXPLODE = "Scholar - Explode"
+WHIRLWIND = "Scholar - Whirlwind"
+ATMOSHEAR = "Scholar - Atmoshear"
+BUILD_LIFE = "Scholar - Build Life"
+AERO = "Scholar - Aero"
+INSULT = "Scholar - Insult"
+INFUSION = "Scholar - Infusion"
+OVERlOAD = "Scholar - Overload"
+REFLECTION = "Scholar - Reflection"
LIFEGIVER = "Scholar - Lifegiver"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/summons.py b/worlds/crystal_project/constants/summons.py
index 13a1ba622d81..fdb910041855 100644
--- a/worlds/crystal_project/constants/summons.py
+++ b/worlds/crystal_project/constants/summons.py
@@ -1,10 +1,10 @@
-SHAKU_SUMMON = "Summon - Shaku"
-PAMOA_SUMMON = "Summon - Pamoa"
-GUABA_SUMMON = "Summon - Guaba"
-NILTSI_SUMMON = "Summon - Niltsi"
-IOSKE_SUMMON = "Summon - Ioske"
-COYOTE_SUMMON = "Summon - Coyote"
-#PINGA_SUMMON = "Summon - Pinga" (starter summon)
-TIRA_SUMMON = "Summon - Tira"
-JUSES_SUMMON = "Summon - Juses"
+SHAKU_SUMMON = "Summon - Shaku"
+PAMOA_SUMMON = "Summon - Pamoa"
+GUABA_SUMMON = "Summon - Guaba"
+NILTSI_SUMMON = "Summon - Niltsi"
+IOSKE_SUMMON = "Summon - Ioske"
+COYOTE_SUMMON = "Summon - Coyote"
+#PINGA_SUMMON = "Summon - Pinga" (starter summon)
+TIRA_SUMMON = "Summon - Tira"
+JUSES_SUMMON = "Summon - Juses"
PAH_SUMMON = "Summon - Pah"
\ No newline at end of file
diff --git a/worlds/crystal_project/constants/teleport_stones.py b/worlds/crystal_project/constants/teleport_stones.py
index 8524d28d4018..b9477a7f6364 100644
--- a/worlds/crystal_project/constants/teleport_stones.py
+++ b/worlds/crystal_project/constants/teleport_stones.py
@@ -1,14 +1,14 @@
-HOME_POINT_STONE = "Item - Home Point Stone"
-ARCHIPELAGO_STONE = "Item - Archipelago Stone"
-GAEA_STONE = "Item - Gaea Stone"
-MERCURY_STONE = "Item - Mercury Stone"
-POSEIDON_STONE = "Item - Poseidon Stone"
-MARS_STONE = "Item - Mars Stone"
-GANYMEDE_STONE = "Item - Ganymede Stone"
-TRITON_STONE = "Item - Triton Stone"
-CALLISTO_STONE = "Item - Callisto Stone"
-EUROPA_STONE = "Item - Europa Stone"
-DIONE_STONE = "Item - Dione Stone"
-NEPTUNE_STONE = "Item - Neptune Stone"
-NEW_WORLD_STONE = "Item - New World Stone"
+HOME_POINT_STONE = "Item - Home Point Stone"
+ARCHIPELAGO_STONE = "Item - Archipelago Stone"
+GAEA_STONE = "Item - Gaea Stone"
+MERCURY_STONE = "Item - Mercury Stone"
+POSEIDON_STONE = "Item - Poseidon Stone"
+MARS_STONE = "Item - Mars Stone"
+GANYMEDE_STONE = "Item - Ganymede Stone"
+TRITON_STONE = "Item - Triton Stone"
+CALLISTO_STONE = "Item - Callisto Stone"
+EUROPA_STONE = "Item - Europa Stone"
+DIONE_STONE = "Item - Dione Stone"
+NEPTUNE_STONE = "Item - Neptune Stone"
+NEW_WORLD_STONE = "Item - New World Stone"
OLD_WORLD_STONE = "Item - Old World Stone"
\ No newline at end of file
diff --git a/worlds/crystal_project/docs/en_Crystal Project.md b/worlds/crystal_project/docs/en_Crystal Project.md
index 90ea986efc5d..d47797c50601 100644
--- a/worlds/crystal_project/docs/en_Crystal Project.md
+++ b/worlds/crystal_project/docs/en_Crystal Project.md
@@ -1,34 +1,34 @@
-# Crystal Project (PC)
-
-## Where is the options page?
-
-The [player options page for this game](../player-options) contains most of the options you need to
-configure and export a config file. Some options can only be made in YAML, but an explanation can be found in the
-[template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml).
-
-## What does randomization do to this game?
-
-Rogue Legacy Randomizer takes all the classes, skills, runes, and blueprints and spreads them out into chests, the manor
-upgrade screen, bosses, and some special individual locations. The goal is to become powerful enough to defeat the four
-zone bosses and then defeat The Fountain.
-
-## What items and locations get shuffled?
-All the skill upgrades, class upgrades, runes packs, and equipment packs are shuffled in the manor upgrade screen, diary
-checks, chests and fairy chests, and boss rewards. Skill upgrades are also grouped in packs of 5 to make the finding of
-stats less of a chore. Runes and Equipment are also grouped together.
-
-Some additional locations that can contain items are the Jukebox, the Portraits, and the mini-game rewards.
-
-## Which items can be in another player's world?
-
-Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
-certain items to your own world.
-## When the player receives an item, what happens?
-
-When the player receives an item, your character will hold the item above their head and display it to the world. It's
-good for business!
-
-## What do I do if I encounter a bug with the game?
-
-Please reach out to Phar#4444 on Discord or you can drop a bug report on the
-[GitHub page for Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=report-an-issue---.md&title=%5BIssue%5D).
+# Crystal Project (PC)
+
+## Where is the options page?
+
+The [player options page for this game](../player-options) contains most of the options you need to
+configure and export a config file. Some options can only be made in YAML, but an explanation can be found in the
+[template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml).
+
+## What does randomization do to this game?
+
+Rogue Legacy Randomizer takes all the classes, skills, runes, and blueprints and spreads them out into chests, the manor
+upgrade screen, bosses, and some special individual locations. The goal is to become powerful enough to defeat the four
+zone bosses and then defeat The Fountain.
+
+## What items and locations get shuffled?
+All the skill upgrades, class upgrades, runes packs, and equipment packs are shuffled in the manor upgrade screen, diary
+checks, chests and fairy chests, and boss rewards. Skill upgrades are also grouped in packs of 5 to make the finding of
+stats less of a chore. Runes and Equipment are also grouped together.
+
+Some additional locations that can contain items are the Jukebox, the Portraits, and the mini-game rewards.
+
+## Which items can be in another player's world?
+
+Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
+certain items to your own world.
+## When the player receives an item, what happens?
+
+When the player receives an item, your character will hold the item above their head and display it to the world. It's
+good for business!
+
+## What do I do if I encounter a bug with the game?
+
+Please reach out to Phar#4444 on Discord or you can drop a bug report on the
+[GitHub page for Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=report-an-issue---.md&title=%5BIssue%5D).
diff --git a/worlds/crystal_project/docs/setup_en.md b/worlds/crystal_project/docs/setup_en.md
index d929bbf7ba28..13d18e27cc21 100644
--- a/worlds/crystal_project/docs/setup_en.md
+++ b/worlds/crystal_project/docs/setup_en.md
@@ -1,88 +1,88 @@
-# Crystal Project AP World Setup Guide
-
-## What You Need
-
-- Crystal Project Installer from the
- [Crystal Project AP World Releases Page](https://github.com/Emerassi/CrystalProjectAPWorld/releases)
-
-- .Net 8.0 Desktop Runtime x64 (not the SDK, ASP.NET Core or generic Runtime!) is required to run the Crystal Project Archipelago Mod Installer:
-https://dotnet.microsoft.com/en-us/download/dotnet/8.0
-
-## Recommended Installation Instructions
-
-In your Steam library, right-click Crystal Project in the list and select "Properties...". Go to the Betas section, open the Beta Participation dropdown, and select the archipelago branch (version 1.6.5).
-
-Install .Net 8.0 x64 if you don't already have it.
-
-Go to [Crystal Project AP World Releases Page](https://github.com/Emerassi/CrystalProjectAPWorld/releases).
-Download the installer file, extract it, and run the executable. When prompted, choose your Crystal Project installation location.
-
-## Switching Between Different Versions of Archipelago
-
-Your save files for the unmodified game will not be visible inside the Archipelago version of the game and vice versa.
- The unmodified game and the AP version of the game store saves in different folders to prevent you from loading incompatible saves and getting errors.
-
-If you want to switch back to unmodded Crystal Project:
- 1. In your Steam library, right-click Crystal Project in the list and select "Properties...".
- 1. In the Betas menu, select the branch that matches the save file you want to switch to (likely the release branch, a.k.a. None in the Beta Participation dropdown).
-
-When you want to switch back to the Archipelago version of Crystal Project:
- 1. Change back to the archipelago branch in Steam's Beta Participation menu.
- 1. Run the installer for the version you want.
-
-If you want to switch to a different version of Archipelago Crystal Project:
- 1. In your Steam library, right-click Crystal Project in the list and select "Properties...".
- 1. Go to the Installed Files section and select "Verify integrity of game files".
- 1. Run the installer for the version of Crystal Project Archipelago you want to switch to.
-
-## Configuring your YAML file
-
-### What is a YAML file and why do I need one?
-
-A YAML file lets you configure options for your randomizer game.
-See [Archipelago Multiworld Setup Guide](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game) for a more in-depth explanation!
-
-### Where do I get a YAML file?
-
-Two YAML files are included on the [Crystal Project AP World Releases Page](https://github.com/Emerassi/CrystalProjectAPWorld/releases): a default YAML, and an Explorer preset that maximizes platforming and minimizes combat.
-
-## Is there a tracker for this game?
-
-### Yes!
-
-But not a separate one. We have modified the base game to provide in-game tracking, including a full locations list, goal items tracker, world map icons, and minimap icons (with custom art to indicate accessibility and importance!) You can even bind the world map and locations list to hotkeys.
-
-## Connect to the MultiServer
-
-1. Start a new game in Crystal Project. An Archipelago connection screen will appear.
-1. Fill out the hostname and port (archipelago.gg: #####), slot name, and password (if applicable). You can use your keyboard to type, or you can hit the Paste button on controller.
-1. Hit the Connect button. You should now be connected!
-
-Continue through the new game setup as normal - though it is not recommended to enable any of the base game's randomizer settings. (Some of them may still work, but some of them will break things lol).
-
-### Can I use other Crystal Project mods?
-Using other Crystal Project mods along with Archipelago is not yet fully supported. Apply them with caution! Mods newer than the Archipelago version (Editor version 30) will be incompatible.
-
-The Use Mods YAML option adds items and locations from other Crystal Project mods to the item and location pools at generation.
-WARNING: This option is very in beta right now! Enabling it is not recommended for: multiworlds that do not allow releasing items or with Regionsanity enabled (some mods add items to regions but don't place them anywhere near that region).
-
-Multiworld host instructions for the Use Mods option:
-1. In order to select the mods you'd like to include in randomization, make a folder named "crystal_project_mods" inside your root Archipelago directory.
-1. Go to your Steam installation folder for Crystal Project (/steamapps/workshop/content/1637730) and find the individual folders for the mods you'd like to include.
-1. Inside each mod's folder is a mod json. Copy that json to the crystal_project_mods folder you made inside the Archipelago directory.
-1. If you have a specific order you want to apply the mods, rename the jsons such that they are in alphabetical order in the order you want them to be applied. E.g. name the first mod a_modname, the second b_modname, etc.
-
-NOTE: When the Use Mods option is on, all Crystal Project players in the multiworld with this option enabled MUST use the same mods.
-
-The in-game tracking will use special icons for modded locations that will not display their accessibility (as we can only guess at how accessible they are based on coordinates, and would prefer the tracking to be as accurate as possible).
-
-When disabled, only base game locations and items will be randomized. You can still use other mods - at your own risk, adventurer - they just won't add checks.
-
-The game will warn you if you open a game with mods that don't match the mods used to generate the multiworld.
-
-## Play the game
-
-After you've successfully connected once, your save file will automatically reconnect to the multiworld the next time you open the game.
-(Remember to refresh the multiworld room page if no one has connected in a while.) If the room number has changed, open the menu and select Archipelago from the sidebar to update your connection information and reconnect.
-
-Set forth on adventure! Remember to touch any save points you come across so you can teleport to them from the world map.
+# Crystal Project AP World Setup Guide
+
+## What You Need
+
+- Crystal Project Installer from the
+ [Crystal Project AP World Releases Page](https://github.com/Emerassi/CrystalProjectAPWorld/releases)
+
+- .Net 8.0 Desktop Runtime x64 (not the SDK, ASP.NET Core or generic Runtime!) is required to run the Crystal Project Archipelago Mod Installer:
+https://dotnet.microsoft.com/en-us/download/dotnet/8.0
+
+## Recommended Installation Instructions
+
+In your Steam library, right-click Crystal Project in the list and select "Properties...". Go to the Betas section, open the Beta Participation dropdown, and select the archipelago branch (version 1.6.5).
+
+Install .Net 8.0 x64 if you don't already have it.
+
+Go to [Crystal Project AP World Releases Page](https://github.com/Emerassi/CrystalProjectAPWorld/releases).
+Download the installer file, extract it, and run the executable. When prompted, choose your Crystal Project installation location.
+
+## Switching Between Different Versions of Archipelago
+
+Your save files for the unmodified game will not be visible inside the Archipelago version of the game and vice versa.
+ The unmodified game and the AP version of the game store saves in different folders to prevent you from loading incompatible saves and getting errors.
+
+If you want to switch back to unmodded Crystal Project:
+ 1. In your Steam library, right-click Crystal Project in the list and select "Properties...".
+ 1. In the Betas menu, select the branch that matches the save file you want to switch to (likely the release branch, a.k.a. None in the Beta Participation dropdown).
+
+When you want to switch back to the Archipelago version of Crystal Project:
+ 1. Change back to the archipelago branch in Steam's Beta Participation menu.
+ 1. Run the installer for the version you want.
+
+If you want to switch to a different version of Archipelago Crystal Project:
+ 1. In your Steam library, right-click Crystal Project in the list and select "Properties...".
+ 1. Go to the Installed Files section and select "Verify integrity of game files".
+ 1. Run the installer for the version of Crystal Project Archipelago you want to switch to.
+
+## Configuring your YAML file
+
+### What is a YAML file and why do I need one?
+
+A YAML file lets you configure options for your randomizer game.
+See [Archipelago Multiworld Setup Guide](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game) for a more in-depth explanation!
+
+### Where do I get a YAML file?
+
+Two YAML files are included on the [Crystal Project AP World Releases Page](https://github.com/Emerassi/CrystalProjectAPWorld/releases): a default YAML, and an Explorer preset that maximizes platforming and minimizes combat.
+
+## Is there a tracker for this game?
+
+### Yes!
+
+But not a separate one. We have modified the base game to provide in-game tracking, including a full locations list, goal items tracker, world map icons, and minimap icons (with custom art to indicate accessibility and importance!) You can even bind the world map and locations list to hotkeys.
+
+## Connect to the MultiServer
+
+1. Start a new game in Crystal Project. An Archipelago connection screen will appear.
+1. Fill out the hostname and port (archipelago.gg: #####), slot name, and password (if applicable). You can use your keyboard to type, or you can hit the Paste button on controller.
+1. Hit the Connect button. You should now be connected!
+
+Continue through the new game setup as normal - though it is not recommended to enable any of the base game's randomizer settings. (Some of them may still work, but some of them will break things lol).
+
+### Can I use other Crystal Project mods?
+Using other Crystal Project mods along with Archipelago is not yet fully supported. Apply them with caution! Mods newer than the Archipelago version (Editor version 30) will be incompatible.
+
+The Use Mods YAML option adds items and locations from other Crystal Project mods to the item and location pools at generation.
+WARNING: This option is very in beta right now! Enabling it is not recommended for: multiworlds that do not allow releasing items or with Regionsanity enabled (some mods add items to regions but don't place them anywhere near that region).
+
+Multiworld host instructions for the Use Mods option:
+1. In order to select the mods you'd like to include in randomization, make a folder named "crystal_project_mods" inside your root Archipelago directory.
+1. Go to your Steam installation folder for Crystal Project (/steamapps/workshop/content/1637730) and find the individual folders for the mods you'd like to include.
+1. Inside each mod's folder is a mod json. Copy that json to the crystal_project_mods folder you made inside the Archipelago directory.
+1. If you have a specific order you want to apply the mods, rename the jsons such that they are in alphabetical order in the order you want them to be applied. E.g. name the first mod a_modname, the second b_modname, etc.
+
+NOTE: When the Use Mods option is on, all Crystal Project players in the multiworld with this option enabled MUST use the same mods.
+
+The in-game tracking will use special icons for modded locations that will not display their accessibility (as we can only guess at how accessible they are based on coordinates, and would prefer the tracking to be as accurate as possible).
+
+When disabled, only base game locations and items will be randomized. You can still use other mods - at your own risk, adventurer - they just won't add checks.
+
+The game will warn you if you open a game with mods that don't match the mods used to generate the multiworld.
+
+## Play the game
+
+After you've successfully connected once, your save file will automatically reconnect to the multiworld the next time you open the game.
+(Remember to refresh the multiworld room page if no one has connected in a while.) If the room number has changed, open the menu and select Archipelago from the sidebar to update your connection information and reconnect.
+
+Set forth on adventure! Remember to touch any save points you come across so you can teleport to them from the world map.
diff --git a/worlds/crystal_project/home_points.py b/worlds/crystal_project/home_points.py
index c29c700a5caa..550053c83114 100644
--- a/worlds/crystal_project/home_points.py
+++ b/worlds/crystal_project/home_points.py
@@ -1,140 +1,140 @@
-from typing import List, Optional
-
-from .options import CrystalProjectOptions
-from .rules import CrystalProjectLogic
-from .locations import LocationData
-from .constants.ap_regions import *
-from .constants.display_regions import *
-from .constants.keys import *
-from .constants.teleport_stones import *
-
-def get_home_points(player: Optional[int], options: Optional[CrystalProjectOptions]) -> List[LocationData]:
- logic = CrystalProjectLogic(player, options)
- home_point_table: List[LocationData] = [
- LocationData(SPAWNING_MEADOWS_AP_REGION, "AP Spawn Point", 5003),
- LocationData(SPAWNING_MEADOWS_AP_REGION, "Old Nan's Watering Hole", 59),
-
- LocationData(DELENDE_PLAINS_AP_REGION, "The Pale Grotto Entrance", 44),
- LocationData(DELENDE_PLAINS_AP_REGION, "Soiled Den", 66),
- LocationData(DELENDE_PLAINS_AP_REGION, "Fish Hatchery", 127),
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Cabin On The Cliff", 94),
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Delende Falls", 186),
- LocationData(DELENDE_PEAK_AP_REGION, "Delende Peak", 160),
-
- LocationData(MERCURY_SHRINE_AP_REGION, "Mercury Shrine", 152),
-
- LocationData(THE_PALE_GROTTO_AP_REGION, "The Pale Grotto Ruins", 148),
-
- LocationData(SEASIDE_CLIFFS_AP_REGION, "Seaside Cliffs Camp", 72),
-
- LocationData(YAMAGAWA_MA_AP_REGION, "Yamagawa M.A. Summit", 165),
-
- LocationData(PROVING_MEADOWS_AP_REGION, "Proving Meadows Camp", 119),
-
- LocationData(SKUMPARADISE_AP_REGION, "Skumparadise Entrance", 637),
- LocationData(SKUMPARADISE_AP_REGION, "Skumparadise Depths", 331),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Skumparadise Exit", 231),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Gaea Shrine", 112),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "East Market District", 374),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Bulletin Square", 890),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Know-It-All Ducks' House", 559),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "West Market District", 2026),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Training Grounds", 3057),
-
- LocationData(CAPITAL_JAIL_AP_REGION, "Capital Jail Entrance", 643),
- LocationData(CAPITAL_JAIL_AP_REGION, "Capital Jail Dark Wing", 915, lambda state: logic.has_key(state, DARK_WING_KEY)),
-
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, "Quintar Enthusiast's House", 440),
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, "Rent-A-Quintar", 462),
- LocationData(SANCTUM_ENTRANCE_AP_REGION, "Quintar Sanctum", 917),
-
- LocationData(QUINTAR_SANCTUM_AP_REGION, "Quintar Nameko", 968),
-
- LocationData(BOOMER_SOCIETY_AP_REGION, "Boomer Society", 170),
-
- LocationData(OKIMOTO_NS_AP_REGION, "Okimoto N.S. Base", 335),
- LocationData(OKIMOTO_NS_AP_REGION, "Ninja Yashiki", 366),
-
- LocationData(SALMON_PASS_EAST_AP_REGION, "Salmon Pass Entrance", 367),
-
- LocationData(SALMON_RIVER_AP_REGION, "Salmon Shack", 1076),
-
- LocationData(CASTLE_SEQUOIA_AP_REGION, "Castle Sequoia Foyer", 514),
-
- LocationData(TOWER_OF_ZOT_AP_REGION, "Labyrinth Encampment", 2712),
-
- LocationData(SARA_SARA_BAZAAR_AP_REGION, "Sara Sara Bazaar Port", 941),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, "Poko Poko West Gate", 3783),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, "Poko Poko East Gate", 3784),
-
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, "Ibek's Cave", 2005, lambda state: logic.has_vertical_movement(state)),
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, "Beach Bird's Nest", 2709, lambda state: logic.has_vertical_movement(state)),
-
- LocationData(BEAURIOR_VOLCANO_AP_REGION, "Beaurior Rock", 1792),
- #TODO: put volcano peak in separate ap region instead of included in Beaurior Rock (also items nearby, not just homepoint stone)
- LocationData(BEAURIOR_ROCK_AP_REGION, "Beaurior Volcano Peak", 3037, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)),
-
- LocationData(BEAURIOR_ROCK_AP_REGION, "Boss Room", 822, lambda state: logic.has_key(state, SMALL_KEY, 4)),
-
- LocationData(ANCIENT_RESERVOIR_AP_REGION, "Ancient Reservoir Entrance", 1124),
- LocationData(ANCIENT_RESERVOIR_AP_REGION, "Main Reservoir Chamber", 1660),
-
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Fields", 576, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Market", 577, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Port", 672),
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Shanty Inn", 1523),
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Sky Arena", 1524, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Prize Counter", 2731, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Elevator", 3523, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
-
- LocationData(GANYMEDE_SHRINE_AP_REGION, "Ganymede Shrine", 1573),
-
- LocationData(THE_UNDERCITY_AP_REGION, "The Undercity", 1266, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
-
- LocationData(PIPELINE_NORTH_AP_REGION, "Capital Pipeline", 1127),
- LocationData(PIPELINE_SOUTH_AP_REGION, "East Capital Pipeline", 1420),
-
- LocationData(SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, "Sequoia Athenaeum", 2361),
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Ice Pass", 2413),
- LocationData(SOUVENIR_SHOP_AP_REGION, "Tall, Tall Souvenir Shop", 1260),
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Land's End Cottage", 2564),
- LocationData(SLIP_GLIDE_RIDE_EXIT_AP_REGION, "Slip Glide Ride Exit", 2743),
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Ice Fisher's Hut", 3014),
- LocationData(UPPER_ICE_LAKES_AP_REGION, "Triton Shrine", 3018),
- LocationData(TALL_TALL_SAVE_POINT_AP_REGION, "Tall, Tall Heights", 3047),
-
- LocationData(PEAK_RAMPARTS_AP_REGION, "East Ramparts", 1375, lambda state: logic.has_glide(state)),
- LocationData(PEAK_RAMPARTS_AP_REGION, "West Ramparts", 1376, lambda state: logic.has_glide(state)),
-
- LocationData(SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, "Slip Glide Ride Entrance", 1550),
-
- LocationData(LANDS_END_AP_REGION, "Summit Shrine", 1559),
-
- LocationData(QUINTAR_RESERVE_AP_REGION, "Dione Shrine", 1595),
-
- LocationData(EUROPA_SHRINE_AP_REGION, "Europa Shrine", 1626),
-
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, "Eaclaneya Entrance", 1402),
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, "Salmon Room", 2474),
-
- LocationData(LABYRINTH_CORE_AP_REGION, "Ancient Labyrinth Core", 1739),
-
- LocationData(DIONE_SHRINE_AP_REGION, "Flyer's Lookout", 2141),
-
- LocationData(THE_SEQUOIA_AP_REGION, "Top Of The Sequoia", 2452),
-
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, "The Chalice of Tar", 3055),
-
- LocationData(THE_OPEN_SEA_AP_REGION, "Sailor's Raft", 3775),
-
- LocationData(THE_NEW_WORLD_AP_REGION, "Astley's Shrine", 3776),
- LocationData(THE_NEW_WORLD_AP_REGION, "Astley's Keep", 3777),
- LocationData(THE_NEW_WORLD_AP_REGION, "Discipline Hollow", 3797),
-
- LocationData(CONTINENTAL_TRAM_AP_REGION, "Platform A", 3780),
-
- LocationData(NEPTUNE_SHRINE_AP_REGION, "Neptune Shrine", 3781),
- ]
-
+from typing import List, Optional
+
+from .options import CrystalProjectOptions
+from .rules import CrystalProjectLogic
+from .locations import LocationData
+from .constants.ap_regions import *
+from .constants.display_regions import *
+from .constants.keys import *
+from .constants.teleport_stones import *
+
+def get_home_points(player: Optional[int], options: Optional[CrystalProjectOptions]) -> List[LocationData]:
+ logic = CrystalProjectLogic(player, options)
+ home_point_table: List[LocationData] = [
+ LocationData(SPAWNING_MEADOWS_AP_REGION, "AP Spawn Point", 5003),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, "Old Nan's Watering Hole", 59),
+
+ LocationData(DELENDE_PLAINS_AP_REGION, "The Pale Grotto Entrance", 44),
+ LocationData(DELENDE_PLAINS_AP_REGION, "Soiled Den", 66),
+ LocationData(DELENDE_PLAINS_AP_REGION, "Fish Hatchery", 127),
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Cabin On The Cliff", 94),
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Delende Falls", 186),
+ LocationData(DELENDE_PEAK_AP_REGION, "Delende Peak", 160),
+
+ LocationData(MERCURY_SHRINE_AP_REGION, "Mercury Shrine", 152),
+
+ LocationData(THE_PALE_GROTTO_AP_REGION, "The Pale Grotto Ruins", 148),
+
+ LocationData(SEASIDE_CLIFFS_AP_REGION, "Seaside Cliffs Camp", 72),
+
+ LocationData(YAMAGAWA_MA_AP_REGION, "Yamagawa M.A. Summit", 165),
+
+ LocationData(PROVING_MEADOWS_AP_REGION, "Proving Meadows Camp", 119),
+
+ LocationData(SKUMPARADISE_AP_REGION, "Skumparadise Entrance", 637),
+ LocationData(SKUMPARADISE_AP_REGION, "Skumparadise Depths", 331),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Skumparadise Exit", 231),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Gaea Shrine", 112),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "East Market District", 374),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Bulletin Square", 890),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Know-It-All Ducks' House", 559),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "West Market District", 2026),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Training Grounds", 3057),
+
+ LocationData(CAPITAL_JAIL_AP_REGION, "Capital Jail Entrance", 643),
+ LocationData(CAPITAL_JAIL_AP_REGION, "Capital Jail Dark Wing", 915, lambda state: logic.has_key(state, DARK_WING_KEY)),
+
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, "Quintar Enthusiast's House", 440),
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, "Rent-A-Quintar", 462),
+ LocationData(SANCTUM_ENTRANCE_AP_REGION, "Quintar Sanctum", 917),
+
+ LocationData(QUINTAR_SANCTUM_AP_REGION, "Quintar Nameko", 968),
+
+ LocationData(BOOMER_SOCIETY_AP_REGION, "Boomer Society", 170),
+
+ LocationData(OKIMOTO_NS_AP_REGION, "Okimoto N.S. Base", 335),
+ LocationData(OKIMOTO_NS_AP_REGION, "Ninja Yashiki", 366),
+
+ LocationData(SALMON_PASS_EAST_AP_REGION, "Salmon Pass Entrance", 367),
+
+ LocationData(SALMON_RIVER_AP_REGION, "Salmon Shack", 1076),
+
+ LocationData(CASTLE_SEQUOIA_AP_REGION, "Castle Sequoia Foyer", 514),
+
+ LocationData(TOWER_OF_ZOT_AP_REGION, "Labyrinth Encampment", 2712),
+
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, "Sara Sara Bazaar Port", 941),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, "Poko Poko West Gate", 3783),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, "Poko Poko East Gate", 3784),
+
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, "Ibek's Cave", 2005, lambda state: logic.has_vertical_movement(state)),
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, "Beach Bird's Nest", 2709, lambda state: logic.has_vertical_movement(state)),
+
+ LocationData(BEAURIOR_VOLCANO_AP_REGION, "Beaurior Rock", 1792),
+ #TODO: put volcano peak in separate ap region instead of included in Beaurior Rock (also items nearby, not just homepoint stone)
+ LocationData(BEAURIOR_ROCK_AP_REGION, "Beaurior Volcano Peak", 3037, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)),
+
+ LocationData(BEAURIOR_ROCK_AP_REGION, "Boss Room", 822, lambda state: logic.has_key(state, SMALL_KEY, 4)),
+
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, "Ancient Reservoir Entrance", 1124),
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, "Main Reservoir Chamber", 1660),
+
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Fields", 576, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Market", 577, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Port", 672),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Shanty Inn", 1523),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Sky Arena", 1524, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Prize Counter", 2731, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, "Shoudu Elevator", 3523, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
+
+ LocationData(GANYMEDE_SHRINE_AP_REGION, "Ganymede Shrine", 1573),
+
+ LocationData(THE_UNDERCITY_AP_REGION, "The Undercity", 1266, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)),
+
+ LocationData(PIPELINE_NORTH_AP_REGION, "Capital Pipeline", 1127),
+ LocationData(PIPELINE_SOUTH_AP_REGION, "East Capital Pipeline", 1420),
+
+ LocationData(SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, "Sequoia Athenaeum", 2361),
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Ice Pass", 2413),
+ LocationData(SOUVENIR_SHOP_AP_REGION, "Tall, Tall Souvenir Shop", 1260),
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Land's End Cottage", 2564),
+ LocationData(SLIP_GLIDE_RIDE_EXIT_AP_REGION, "Slip Glide Ride Exit", 2743),
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Ice Fisher's Hut", 3014),
+ LocationData(UPPER_ICE_LAKES_AP_REGION, "Triton Shrine", 3018),
+ LocationData(TALL_TALL_SAVE_POINT_AP_REGION, "Tall, Tall Heights", 3047),
+
+ LocationData(PEAK_RAMPARTS_AP_REGION, "East Ramparts", 1375, lambda state: logic.has_glide(state)),
+ LocationData(PEAK_RAMPARTS_AP_REGION, "West Ramparts", 1376, lambda state: logic.has_glide(state)),
+
+ LocationData(SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, "Slip Glide Ride Entrance", 1550),
+
+ LocationData(LANDS_END_AP_REGION, "Summit Shrine", 1559),
+
+ LocationData(QUINTAR_RESERVE_AP_REGION, "Dione Shrine", 1595),
+
+ LocationData(EUROPA_SHRINE_AP_REGION, "Europa Shrine", 1626),
+
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, "Eaclaneya Entrance", 1402),
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, "Salmon Room", 2474),
+
+ LocationData(LABYRINTH_CORE_AP_REGION, "Ancient Labyrinth Core", 1739),
+
+ LocationData(DIONE_SHRINE_AP_REGION, "Flyer's Lookout", 2141),
+
+ LocationData(THE_SEQUOIA_AP_REGION, "Top Of The Sequoia", 2452),
+
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, "The Chalice of Tar", 3055),
+
+ LocationData(THE_OPEN_SEA_AP_REGION, "Sailor's Raft", 3775),
+
+ LocationData(THE_NEW_WORLD_AP_REGION, "Astley's Shrine", 3776),
+ LocationData(THE_NEW_WORLD_AP_REGION, "Astley's Keep", 3777),
+ LocationData(THE_NEW_WORLD_AP_REGION, "Discipline Hollow", 3797),
+
+ LocationData(CONTINENTAL_TRAM_AP_REGION, "Platform A", 3780),
+
+ LocationData(NEPTUNE_SHRINE_AP_REGION, "Neptune Shrine", 3781),
+ ]
+
return home_point_table
\ No newline at end of file
diff --git a/worlds/crystal_project/items.py b/worlds/crystal_project/items.py
index d07675a839c2..e69c9154341b 100644
--- a/worlds/crystal_project/items.py
+++ b/worlds/crystal_project/items.py
@@ -1,1919 +1,1919 @@
-import logging
-from typing import Dict, Set, Tuple, NamedTuple, Optional, List, TYPE_CHECKING
-from BaseClasses import ItemClassification
-from .constants.item_groups import *
-from .constants.jobs import *
-from .constants.keys import *
-from .constants.key_items import *
-from .constants.maps import *
-from .constants.mounts import *
-from .constants.scholar_abilities import *
-from .constants.summons import *
-from .constants.teleport_stones import *
-from .constants.region_passes import *
-from .constants.display_regions import *
-from .constants.crystal_locations import *
-
-if TYPE_CHECKING:
- from . import CrystalProjectWorld
-
-class ItemData(NamedTuple):
- category: str
- code: int
- classification: ItemClassification
- #For maguffins, we use Progression + Deprioritize + SkipBalancing
- #For progression items that you need a lot to unlock not much, and are therefore unsatisfying, we use Progression + Deprioritize
- #For really neat progression items that are a BIG DEAL, we use Progression + Useful, just because that comes out a gold color instead of purple when colored text is used.
- # deprioritize means it won't be picked for a priority location, because it would feel unsatisfying to get i.e. clam from a priority location
- # skip balancing means that if you used progression balancing to try to force one player's items earlier than another, it will fill those with i.e. non-clams
- #Amount found in each region type; added together for each set you're including
- beginnerAmount: Optional[int] = 1
- advancedAmount: Optional[int] = 0
- expertAmount: Optional[int] = 0
- endGameAmount: Optional[int] = 0
- # Amount found in each region type's shops; added together for each set you're including
- beginnerShops: Optional[int] = 0
- advancedShops: Optional[int] = 0
- expertShops: Optional[int] = 0
- endGameShops: Optional[int] = 0
-
-class Job(NamedTuple):
- name: str
- id: int
-
-#Archipelago does not like it if an item has a code of 0, Crystal project starts its database
-#Ids at 0, so we add an offset to the id of each item and remove that offset in the client
-job_index_offset = 1
-item_index_offset = 101
-equipment_index_offset = 1001
-summon_index_offset = 10001
-scholar_index_offset = 100001
-trap_index_offset = 1000001
-
-item_table: Dict[str, ItemData] = {
- #Jobs
- WARRIOR_JOB: ItemData(JOB, 0 + job_index_offset, ItemClassification.progression),
- MONK_JOB: ItemData(JOB, 5 + job_index_offset, ItemClassification.progression),
- ROGUE_JOB: ItemData(JOB, 2 + job_index_offset, ItemClassification.progression),
- CLERIC_JOB: ItemData(JOB, 4 + job_index_offset, ItemClassification.progression),
- WIZARD_JOB: ItemData(JOB, 3 + job_index_offset, ItemClassification.progression),
- WARLOCK_JOB: ItemData(JOB, 14 + job_index_offset, ItemClassification.progression),
- FENCER_JOB: ItemData(JOB, 1 + job_index_offset, ItemClassification.progression),
- SHAMAN_JOB: ItemData(JOB, 8 + job_index_offset, ItemClassification.progression),
- SCHOLAR_JOB: ItemData(JOB, 13 + job_index_offset, ItemClassification.progression), #requirement for Grans subbasement
- AEGIS_JOB: ItemData(JOB, 10 + job_index_offset, ItemClassification.progression),
- HUNTER_JOB: ItemData(JOB, 7 + job_index_offset, ItemClassification.progression),
- CHEMIST_JOB: ItemData(JOB, 17 + job_index_offset, ItemClassification.progression),
- REAPER_JOB: ItemData(JOB, 6 + job_index_offset, ItemClassification.progression),
- NINJA_JOB: ItemData(JOB, 18 + job_index_offset, ItemClassification.progression),
- NOMAD_JOB: ItemData(JOB, 12 + job_index_offset, ItemClassification.progression),
- DERVISH_JOB: ItemData(JOB, 11 + job_index_offset, ItemClassification.progression),
- BEATSMITH_JOB: ItemData(JOB, 9 + job_index_offset, ItemClassification.progression),
- SAMURAI_JOB: ItemData(JOB, 20 + job_index_offset, ItemClassification.progression),
- ASSASSIN_JOB: ItemData(JOB, 19 + job_index_offset, ItemClassification.progression),
- VALKYRIE_JOB: ItemData(JOB, 15 + job_index_offset, ItemClassification.progression),
- SUMMONER_JOB: ItemData(JOB, 21 + job_index_offset, ItemClassification.progression), #Required for summon fights; only job checked by NPCs
- BEASTMASTER_JOB: ItemData(JOB, 23 + job_index_offset, ItemClassification.progression),
- WEAVER_JOB: ItemData(JOB, 16 + job_index_offset, ItemClassification.progression),
- MIMIC_JOB: ItemData(JOB, 22 + job_index_offset, ItemClassification.progression),
-
- #Consumables
- "Item - Tonic": ItemData(ITEM, 18 + item_index_offset, ItemClassification.filler, 0),
- "Item - Potion": ItemData(ITEM, 0 + item_index_offset, ItemClassification.filler, 0),
- "Item - Z-Potion": ItemData(ITEM, 102 + item_index_offset, ItemClassification.filler, 0),
- "Item - Tincture": ItemData(ITEM, 47 + item_index_offset, ItemClassification.filler, 0),
- "Item - Ether": ItemData(ITEM, 1 + item_index_offset, ItemClassification.filler, 0),
- "Item - Zether": ItemData(ITEM, 142 + item_index_offset, ItemClassification.filler, 0),
- "Item - Fenix Juice": ItemData(ITEM, 2 + item_index_offset, ItemClassification.filler, 0),
- "Item - Fenix Syrup": ItemData(ITEM, 145 + item_index_offset, ItemClassification.filler, 0),
- "Item - Nans Stew": ItemData(ITEM, 9 + item_index_offset, ItemClassification.filler, 0),
- "Item - Nans Cocoa": ItemData(ITEM, 8 + item_index_offset, ItemClassification.filler, 0),
- "Item - Nans Secret Recipe": ItemData(ITEM, 54 + item_index_offset, ItemClassification.filler, 0),
- "Item - Nuts": ItemData(ITEM, 14 + item_index_offset, ItemClassification.filler, 0),
- "Item - Milk": ItemData(ITEM, 20 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Thirsty Lad, Poko Poko Desert, Advanced Regions
- "Item - Shoudu Stew": ItemData(ITEM, 132 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Foreign Foodie, Sara Sara Bazaar, Advanced Regions
- "Item - Sweet Pop Candy": ItemData(ITEM, 34 + item_index_offset, ItemClassification.filler, 0),
- "Item - Sour Pop Candy": ItemData(ITEM, 35 + item_index_offset, ItemClassification.filler, 0),
- "Item - Bitter Pop Candy": ItemData(ITEM, 171 + item_index_offset, ItemClassification.filler, 0),
- "Item - Rotten Salmon": ItemData(ITEM, 11 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Fish Merchant, Sara Sara Bazaar, Advanced Regions
- "Item - Decent Cod": ItemData(ITEM, 38 + item_index_offset, ItemClassification.filler, 0),
- "Item - Fresh Salmon": ItemData(ITEM, 10 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Fish Merchant, Sara Sara Bazaar, Advanced Regions
- "Item - Scroll": ItemData(ITEM, 263 + item_index_offset, ItemClassification.filler, 0),
-
- #Bag upgrades
- "Item - Tonic Pouch": ItemData(ITEM, 133 + item_index_offset, ItemClassification.useful, 7, 9, 1, 0), #17
- "Item - Potion Pouch": ItemData(ITEM, 134 + item_index_offset, ItemClassification.useful, 0, 5, 8, 0), #13
- "Item - Z-Potion Pouch": ItemData(ITEM, 143 + item_index_offset, ItemClassification.useful, 1, 0, 2, 1), #5
- "Item - Tincture Pouch": ItemData(ITEM, 135 + item_index_offset, ItemClassification.useful, 4, 8, 2, 0), #14
- "Item - Ether Pouch": ItemData(ITEM, 136 + item_index_offset, ItemClassification.useful, 0, 6, 5, 0), #11
- "Item - Zether Pouch": ItemData(ITEM, 144 + item_index_offset, ItemClassification.useful, 0, 1, 1, 3), #5
- "Item - Fenix Juice Pouch": ItemData(ITEM, 137 + item_index_offset, ItemClassification.useful, 1, 1, 0, 0), #2
- "Item - Fenix Syrup Pouch": ItemData(ITEM, 146 + item_index_offset, ItemClassification.useful, 0, 2, 0, 0), #2
- "Item - Nuts Sack": ItemData(ITEM, 184 + item_index_offset, ItemClassification.useful, 0, 1), #Capital Sequoia, Advanced Zones
- "Item - Milk Bag": ItemData(ITEM, 138 + item_index_offset, ItemClassification.useful, 0, 1), #Poko Poko Desert, Advanced Zones
- "Item - Decent Cod Bag": ItemData(ITEM, 185 + item_index_offset, ItemClassification.useful, 0, 0, 1), #Shoudu Province, Expert Zones
-
- #Fishing
- "Item - Flimsy Rod": ItemData(TACKLE, 55 + item_index_offset, ItemClassification.progression, 1),
- "Item - Tough Rod": ItemData(TACKLE, 150 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- "Item - Super Rod": ItemData(TACKLE, 151 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- "Item - Plug Lure": ItemData(TACKLE, 91 + item_index_offset, ItemClassification.progression, 1),
- "Item - Fly Lure": ItemData(TACKLE, 149 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- "Item - Jigging Lure": ItemData(TACKLE, 97 + item_index_offset, ItemClassification.progression, 0, 0, 1),
-
- #Ore
- "Item - Silver Ore": ItemData(ORE, 3 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Capital Blacksmith, Capital Sequoia, Advanced Regions
- "Item - Silver Ingot": ItemData(ORE, 67 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Capital Blacksmith, Capital Sequoia, Advanced Regions
- "Item - Silver Dust": ItemData(ORE, 68 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Capital Blacksmith, Capital Sequoia, Advanced Regions
- "Item - Gold Ore": ItemData(ORE, 4 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Armorer in Sara Sara Bazaar and Weaponsmith in Shoudu Province, Advanced Regions
- "Item - Gold Ingot": ItemData(ORE, 69 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Armorer in Sara Sara Bazaar and Weaponsmith in Shoudu Province, Advanced Regions
- "Item - Gold Dust": ItemData(ORE, 70 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Armorer in Sara Sara Bazaar and Weaponsmith in Shoudu Province, Advanced Regions
- "Item - Diamond Ore": ItemData(ORE, 5 + item_index_offset, ItemClassification.useful, 0, 0, 18), #Used by Armorer in Tall Tall Heights and Weaponsmith in Jidamba Tangle, Expert Regions
- "Item - Diamond Ingot": ItemData(ORE, 71 + item_index_offset, ItemClassification.useful, 0, 0, 18), #Used by Armorer in Tall Tall Heights and Weaponsmith in Jidamba Tangle, Expert Regions
- "Item - Diamond Dust": ItemData(ORE, 72 + item_index_offset, ItemClassification.useful, 0, 0, 18), #Used by Armorer in Tall Tall Heights and Weaponsmith in Jidamba Tangle, Expert Regions
-
- #Keys
- GARDENERS_KEY: ItemData(KEY, 31 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Sequoia, Advanced Regions
- COURTYARD_KEY: ItemData(KEY, 33 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Sequoia (Courtyard), Advanced Regions
- LUXURY_KEY: ItemData(KEY, 36 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Sequoia, Advanced Regions
- CELL_KEY: ItemData(KEY, 40 + item_index_offset, ItemClassification.progression, 0, 7), #Turn-in: Capital Jail, Advanced Regions
- SOUTH_WING_KEY: ItemData(KEY, 41 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
- EAST_WING_KEY: ItemData(KEY, 42 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
- WEST_WING_KEY: ItemData(KEY, 43 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
- DARK_WING_KEY: ItemData(KEY, 44 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
- ROOM_ONE_KEY: ItemData(KEY, 32 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
- PYRAMID_KEY: ItemData(KEY, 60 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Poko Poko Desert (unlocks Ancient Reservoir), Advanced Regions
- TRAM_KEY: ItemData(KEY, 95 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Continental Tram (unlocks connection between Continental Tram and Sara Sara Bazaar), Expert Regions
- SMALL_KEY: ItemData(KEY, 29 + item_index_offset, ItemClassification.progression, 0, 0, 4), #Turn-in: Beaurior Rock, Expert Regions
- BEAURIOR_BOSS_KEY: ItemData(KEY, 30 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Beaurior Rock, Expert Regions
- ICE_CELL_KEY: ItemData(KEY, 156 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Northern Cave, Expert Regions
- RED_DOOR_KEY: ItemData(KEY, 169 + item_index_offset, ItemClassification.progression, 0, 0, 3), #Turn-in: Slip Glide Ride, Expert Regions
- ICE_PUZZLE_KEY: ItemData(KEY, 160 + item_index_offset, ItemClassification.progression, 0, 0, 6), #Turn-in: Sequoia Athenaeum, Expert Regions
- FOLIAGE_KEY: ItemData(KEY, 141 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Jidamba Tangle (unlocks Jidamba Eaclaneya), Expert Regions
- CAVE_KEY: ItemData(KEY, 118 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Jidamba Tangle (unlocks Jidamba Eaclaneya), Expert Regions
- CANOPY_KEY: ItemData(KEY, 116 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Jidamba Tangle (unlocks Jidamba Eaclaneya), Expert Regions
- RAMPART_KEY: ItemData(KEY, 175 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Castle Ramparts, Expert Regions
- FORGOTTEN_KEY: ItemData(KEY, 192 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: The Deep Sea, Expert Regions
- SKELETON_KEY: ItemData(KEY, 147 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 0, 1), #Everyone's best friend
- PRISON_KEY_RING: ItemData(KEY, 650 + item_index_offset, ItemClassification.progression, 0, 1),
- BEAURIOR_KEY_RING: ItemData(KEY, 651 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- ICE_PUZZLE_KEY_RING: ItemData(KEY, 652 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- SLIP_GLIDE_RIDE_KEY_RING: ItemData(KEY, 653 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- JIDAMBA_KEY_RING: ItemData(KEY, 654 + item_index_offset, ItemClassification.progression, 0, 0, 1),
-
- #Passes
- "Item - Quintar Pass": ItemData(ITEM, 7 + item_index_offset, ItemClassification.filler, 0), #We don't use this so it's filler to prevent it from being jsonified (now part of Progressive Quintar Flute)
- PROGRESSIVE_LUXURY_PASS: ItemData(ITEM, 93 + item_index_offset, ItemClassification.progression, 0, 2), #Luxury Pass ID 93; Luxury Pass V2 148; Turn-in: Capital Sequoia, Advanced Regions
- "Item - Luxury Pass V2": ItemData(ITEM, 148 + item_index_offset, ItemClassification.filler, 0), #We don't use this so it's filler to prevent it from being jsonified (now part of Progressive Luxury Pass)
- FERRY_PASS: ItemData(ITEM, 37 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Sara Sara Bazaar (unlocks connection to Shoudu Province), Expert Regions
-
- #Key Items
- BLACK_SQUIRREL: ItemData(ITEM, 21 + item_index_offset, ItemClassification.progression, 4), #Turn-in: Spawning Meadows, Beginner Regions
- DOG_BONE: ItemData(ITEM, 6 + item_index_offset, ItemClassification.progression, 3), #Turn-in: Delende, Beginner Regions
- # Number of clamshells is set dynamically based on your Clamshells in pool variable
- CLAMSHELL: ItemData(ITEM, 16 + item_index_offset, ItemClassification.progression | ItemClassification.deprioritized | ItemClassification.skip_balancing, 0), #Turn-in: Seaside Cliffs, Beginner Regions
- DIGESTED_HEAD: ItemData(ITEM, 17 + item_index_offset, ItemClassification.progression, 0, 3), #Turn-in: Capital Sequoia, Advanced Regions
- LOST_PENGUIN: ItemData(ITEM, 24 + item_index_offset, ItemClassification.progression, 0, 12), #Turn-in: Capital Sequoia, Advanced Regions
- ELEVATOR_PART: ItemData(ITEM, 224 + item_index_offset, ItemClassification.progression | ItemClassification.deprioritized, 0, 0, 10), #Turn-in: Shoudu Province, Expert Regions
- UNDERSEA_CRAB: ItemData(ITEM, 212 + item_index_offset, ItemClassification.progression | ItemClassification.deprioritized, 0, 0, 15), #Turn-in: The Deep Sea, Expert Regions
- WEST_LOOKOUT_TOKEN: ItemData(ITEM, 81 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
- CENTRAL_LOOKOUT_TOKEN: ItemData(ITEM, 88 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
- NORTH_LOOKOUT_TOKEN: ItemData(ITEM, 131 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
- BABEL_QUINTAR: ItemData(ITEM, 167 + item_index_offset, ItemClassification.progression), #Quintar shop!
- "Item - Quintar Shedding": ItemData(ITEM, 168 + item_index_offset, ItemClassification.filler, 0), #12
- CRAG_DEMON_HORN: ItemData(ITEM, 197 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Jojo Sewers, Advanced Regions
- VERMILLION_BOOK: ItemData(ITEM, 172 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Tall Tall Heights (unlocks Sequoia Athenaeum), Expert Regions
- VIRIDIAN_BOOK: ItemData(ITEM, 173 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Tall Tall Heights (Sequoia Athenaeum), Expert Regions
- CERULEAN_BOOK: ItemData(ITEM, 174 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Tall Tall Heights (Sequoia Athenaeum), Expert Regions
- ANCIENT_TABLET_A: ItemData(ITEM, 161 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- ANCIENT_TABLET_B: ItemData(ITEM, 162 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- ANCIENT_TABLET_C: ItemData(ITEM, 163 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- TREASURE_FINDER: ItemData(ITEM, 196 + item_index_offset, ItemClassification.useful),
- # Progressive Level is used for all level gating options: Progressive Level Pass, Progressive Level Cap, and Progressive Level Catch-Up
- PROGRESSIVE_LEVEL: ItemData(ITEM, 500 + item_index_offset, ItemClassification.progression, 0),
- HOMEPOINT_UNLOCK: ItemData(ITEM, 800 + item_index_offset, ItemClassification.filler, 0),
- SPECIAL_SHOUDU_STEW: ItemData(ITEM, 229 + item_index_offset, ItemClassification.progression, 0, 1),
- SPECIAL_MILK: ItemData(ITEM, 230 + item_index_offset, ItemClassification.progression, 0, 1),
- SPECIAL_FRESH_SALMON: ItemData(ITEM, 231 + item_index_offset, ItemClassification.progression, 0, 1),
- SPECIAL_ROTTEN_SALMON: ItemData(ITEM, 232 + item_index_offset, ItemClassification.progression, 0, 1),
- DEITY_EYE: ItemData(ITEM, 176 + item_index_offset, ItemClassification.progression, 0, 0, 0, 4), #Used for true astley win con and fighting Gabriel in Kill Bosses mode
- STEM_WARD: ItemData(ITEM, 177 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1), #Used for true astley win con and fighting Gabriel in Kill Bosses mode
- PROOF_OF_MERIT: ItemData(ITEM, 191 + item_index_offset, ItemClassification.filler, 0), #We don't use this so it's filler to prevent it from being jsonified
-
- #Passes for Regionsanity
- SPAWNING_MEADOWS_PASS: ItemData(PASS, 801 + item_index_offset, ItemClassification.progression, 1),
- DELENDE_PASS: ItemData(PASS, 802 + item_index_offset, ItemClassification.progression, 1),
- SOILED_DEN_PASS: ItemData(PASS, 803 + item_index_offset, ItemClassification.progression, 1),
- THE_PALE_GROTTO_PASS: ItemData(PASS, 804 + item_index_offset, ItemClassification.progression, 1),
- SEASIDE_CLIFFS_PASS: ItemData(PASS, 805 + item_index_offset, ItemClassification.progression, 1),
- DRAFT_SHAFT_CONDUIT_PASS: ItemData(PASS, 806 + item_index_offset, ItemClassification.progression, 1),
- MERCURY_SHRINE_PASS: ItemData(PASS, 807 + item_index_offset, ItemClassification.progression, 1),
- YAMAGAWA_MA_PASS: ItemData(PASS, 808 + item_index_offset, ItemClassification.progression, 1),
- PROVING_MEADOWS_PASS: ItemData(PASS, 809 + item_index_offset, ItemClassification.progression, 1),
- SKUMPARADISE_PASS: ItemData(PASS, 810 + item_index_offset, ItemClassification.progression, 1),
- #Advanced
- CAPITAL_SEQUOIA_PASS: ItemData(PASS, 811 + item_index_offset, ItemClassification.progression, 0, 1),
- JOJO_SEWERS_PASS: ItemData(PASS, 812 + item_index_offset, ItemClassification.progression, 0, 1),
- BOOMER_SOCIETY_PASS: ItemData(PASS, 813 + item_index_offset, ItemClassification.progression, 0, 1),
- ROLLING_QUINTAR_FIELDS_PASS: ItemData(PASS, 814 + item_index_offset, ItemClassification.progression, 0, 1),
- QUINTAR_NEST_PASS: ItemData(PASS, 815 + item_index_offset, ItemClassification.progression, 0, 1),
- QUINTAR_SANCTUM_PASS: ItemData(PASS, 816 + item_index_offset, ItemClassification.progression, 0, 1),
- CAPITAL_JAIL_PASS: ItemData(PASS, 817 + item_index_offset, ItemClassification.progression, 0, 1),
- CAPITAL_PIPELINE_PASS: ItemData(PASS, 818 + item_index_offset, ItemClassification.progression, 0, 1),
- COBBLESTONE_CRAG_PASS: ItemData(PASS, 819 + item_index_offset, ItemClassification.progression, 0, 1),
- OKIMOTO_NS_PASS: ItemData(PASS, 820 + item_index_offset, ItemClassification.progression, 0, 1),
- GREENSHIRE_REPRISE_PASS: ItemData(PASS, 821 + item_index_offset, ItemClassification.progression, 0, 1),
- SALMON_PASS_PASS: ItemData(PASS, 822 + item_index_offset, ItemClassification.progression, 0, 1),
- SALMON_RIVER_PASS: ItemData(PASS, 823 + item_index_offset, ItemClassification.progression, 0, 1),
- SHOUDU_WATERFRONT_PASS: ItemData(PASS, 824 + item_index_offset, ItemClassification.progression, 0, 1),
- POKO_POKO_DESERT_PASS: ItemData(PASS, 825 + item_index_offset, ItemClassification.progression, 0, 1),
- SARA_SARA_BAZAAR_PASS: ItemData(PASS, 826 + item_index_offset, ItemClassification.progression, 0, 1),
- SARA_SARA_BEACH_EAST_PASS: ItemData(PASS, 827 + item_index_offset, ItemClassification.progression, 0, 1),
- SARA_SARA_BEACH_WEST_PASS: ItemData(PASS, 828 + item_index_offset, ItemClassification.progression, 0, 1),
- ANCIENT_RESERVOIR_PASS: ItemData(PASS, 829 + item_index_offset, ItemClassification.progression, 0, 1),
- #IBEK_CAVE_PASS: ItemData(PASS, 830 + item_index_offset, ItemClassification.progression, 0, 1),
- SALMON_BAY_PASS: ItemData(PASS, 831 + item_index_offset, ItemClassification.progression, 0, 1),
- #Expert
- THE_OPEN_SEA_PASS: ItemData(PASS, 832 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- SHOUDU_PROVINCE_PASS: ItemData(PASS, 833 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- THE_UNDERCITY_PASS: ItemData(PASS, 834 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- GANYMEDE_SHRINE_PASS: ItemData(PASS, 835 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- BEAURIOR_VOLCANO_PASS: ItemData(PASS, 836 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- BEAURIOR_ROCK_PASS: ItemData(PASS, 837 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- LAKE_DELENDE_PASS: ItemData(PASS, 838 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- QUINTAR_RESERVE_PASS: ItemData(PASS, 839 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- DIONE_SHRINE_PASS: ItemData(PASS, 840 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- QUINTAR_MAUSOLEUM_PASS: ItemData(PASS, 841 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- EASTERN_CHASM_PASS: ItemData(PASS, 842 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- TALL_TALL_HEIGHTS_PASS: ItemData(PASS, 843 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- NORTHERN_CAVE_PASS: ItemData(PASS, 844 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- LANDS_END_PASS: ItemData(PASS, 845 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- SLIP_GLIDE_RIDE_PASS: ItemData(PASS, 846 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- SEQUOIA_ATHENAEUM_PASS: ItemData(PASS, 847 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- NORTHERN_STRETCH_PASS: ItemData(PASS, 848 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- CASTLE_RAMPARTS_PASS: ItemData(PASS, 849 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- THE_CHALICE_OF_TAR_PASS: ItemData(PASS, 850 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- FLYERS_CRAG_PASS: ItemData(PASS, 851 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- JIDAMBA_TANGLE_PASS: ItemData(PASS, 852 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- JIDAMBA_EACLANEYA_PASS: ItemData(PASS, 853 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- THE_DEEP_SEA_PASS: ItemData(PASS, 854 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- NEPTUNE_SHRINE_PASS: ItemData(PASS, 855 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- JADE_CAVERN_PASS: ItemData(PASS, 856 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- CONTINENTAL_TRAM_PASS: ItemData(PASS, 857 + item_index_offset, ItemClassification.progression, 0, 0, 1),
- #End Game
- ANCIENT_LABYRINTH_PASS: ItemData(PASS, 858 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- THE_SEQUOIA_PASS: ItemData(PASS, 859 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- THE_DEPTHS_PASS: ItemData(PASS, 860 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- CASTLE_SEQUOIA_PASS: ItemData(PASS, 861 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- THE_OLD_WORLD_PASS: ItemData(PASS, 862 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
- THE_NEW_WORLD_PASS: ItemData(PASS, 863 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
-
- #Animal mount summons
- PROGRESSIVE_QUINTAR_WOODWIND: ItemData(MOUNT, 39 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 3), #Quintar Pass ID 7 & Quintar Flute ID 39 & Quintar Ocarina 115
- IBEK_BELL: ItemData(MOUNT, 50 + item_index_offset, ItemClassification.progression | ItemClassification.useful),
- OWL_DRUM: ItemData(MOUNT, 49 + item_index_offset, ItemClassification.progression | ItemClassification.useful),
- PROGRESSIVE_SALMON_VIOLA: ItemData(MOUNT, 48 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 2), #Salmon Violin ID 48 & Salmon Cello ID 114
- PROGRESSIVE_MOUNT: ItemData(MOUNT, 700 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 7),
-
- #Teleport items (shards not included since they are stones but worse)
- HOME_POINT_STONE: ItemData(TELEPORT_STONE, 19 + item_index_offset, ItemClassification.useful), #Starter pack
- ARCHIPELAGO_STONE: ItemData(TELEPORT_STONE, 233 + item_index_offset, ItemClassification.useful), #Teleports you to your starting location
- GAEA_STONE: ItemData(TELEPORT_STONE, 23 + item_index_offset, ItemClassification.progression, 0, 1), #Teleport to Capital Sequoia, Advanced Regions
- MERCURY_STONE: ItemData(TELEPORT_STONE, 13 + item_index_offset, ItemClassification.progression), #Teleport to Beginner Regions
- POSEIDON_STONE: ItemData(TELEPORT_STONE, 57 + item_index_offset, ItemClassification.progression, 0, 1), #Teleport to Salmon River, Advanced Regions
- MARS_STONE: ItemData(TELEPORT_STONE, 59 + item_index_offset, ItemClassification.progression, 0, 1), #Teleport to Poko Poko Desert, Advanced Regions
- GANYMEDE_STONE: ItemData(TELEPORT_STONE, 65 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to above Shoudu Province, Expert Regions
- TRITON_STONE: ItemData(TELEPORT_STONE, 66 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to Tall Tall Heights, Expert Regions
- CALLISTO_STONE: ItemData(TELEPORT_STONE, 155 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to Lands End, Expert Regions
- EUROPA_STONE: ItemData(TELEPORT_STONE, 64 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to Jidamba Tangle, Expert Regions
- DIONE_STONE: ItemData(TELEPORT_STONE, 166 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to above Quintar Reserve, Expert Regions
- NEPTUNE_STONE: ItemData(TELEPORT_STONE, 208 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to The Deep Sea, Expert Regions
- NEW_WORLD_STONE: ItemData(TELEPORT_STONE, 140 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1), #End-Game Regions (Astley & True Astley goals hand it to the player directly instead of from the item pool)
- OLD_WORLD_STONE: ItemData(TELEPORT_STONE, 253 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1), #End-Game Regions (True Astley goal hands it to the player directly instead of from the item pool)
-
- #Weapons
- #Swords
- "Equipment - Short Sword": ItemData(EQUIPMENT, 0 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 1H, Shop, Beginner Zones
- "Equipment - Iron Sword": ItemData(EQUIPMENT, 11 + equipment_index_offset, ItemClassification.useful), #Tier 1 1H, Delende, Beginner Zones
- "Equipment - Contract": ItemData(EQUIPMENT, 71 + equipment_index_offset, ItemClassification.useful), #Tier 1 1H, Mercury Shrine, Beginner Zones
- "Equipment - Help the Prince": ItemData(EQUIPMENT, 89 + equipment_index_offset, ItemClassification.useful), #Tier 1 1H, Trial Caves/Skumparadise, Beginner Zones
- "Equipment - Craftwork Sword": ItemData(EQUIPMENT, 93 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 1H, Capital Sequoia, Advanced Zones
- "Equipment - Broadsword": ItemData(EQUIPMENT, 12 + equipment_index_offset, ItemClassification.useful), #Tier 1 2H, Yamagawa M.A., Beginner Zones
- "Equipment - Sharp Sword": ItemData(EQUIPMENT, 200 + equipment_index_offset, ItemClassification.useful), #Tier 2 1H, Skumparadise, Beginner Zones
- "Equipment - Razor Edge": ItemData(EQUIPMENT, 199 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 1H, Shop, Advanced Zones
- "Equipment - Silver Sword": ItemData(EQUIPMENT, 112 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Sword": ItemData(EQUIPMENT, 157 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 1H, Shop, Advanced Zones
- "Equipment - Longsword": ItemData(EQUIPMENT, 378 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
- "Equipment - Boomer Sword": ItemData(EQUIPMENT, 177 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 2H, Boomer Society, Advanced Zones
- "Equipment - Digested Sword": ItemData(EQUIPMENT, 227 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Scimitar": ItemData(EQUIPMENT, 379 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 1H, Shop, Advanced Zones
- "Equipment - Cutlass": ItemData(EQUIPMENT, 377 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 1H, Shoudu Province, Expert Zones
- "Equipment - Cold Touch": ItemData(EQUIPMENT, 375 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 1H, Beaurior Rock, Expert Zones
- "Equipment - Burning Blade": ItemData(EQUIPMENT, 497 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gold Sword": ItemData(EQUIPMENT, 138 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - War Sword": ItemData(EQUIPMENT, 376 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Advanced Zones
- "Equipment - Bloodbind": ItemData(EQUIPMENT, 197 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 2H, Salmon River, Advanced Zones
- "Equipment - Temporal Blade": ItemData(EQUIPMENT, 525 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 2H, Beaurior Volcano, Expert Zones
- "Equipment - Highland Blade": ItemData(EQUIPMENT, 370 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Expert Zones
- "Equipment - Hydra Edge": ItemData(EQUIPMENT, 371 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Defender": ItemData(EQUIPMENT, 380 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 1H, Lands End, Expert Zones
- "Equipment - Crystal Sword": ItemData(EQUIPMENT, 374 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 1H, Shop, Expert Zones
- "Equipment - Conquest": ItemData(EQUIPMENT, 372 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 1H, Castle Ramparts, Expert Zones
- "Equipment - Flame Sword": ItemData(EQUIPMENT, 381 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 1H, Jidamba Eaclaneya, Expert Zones
- "Equipment - Master Sword": ItemData(EQUIPMENT, 248 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 1H, Shop, Advanced Zones
- "Equipment - Rune Sword": ItemData(EQUIPMENT, 382 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 2H, Castle Ramparts, Expert Zones
- "Equipment - Auduril": ItemData(EQUIPMENT, 270 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Master Bigsword": ItemData(EQUIPMENT, 249 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 2H, Shop, Advanced Zones
- "Equipment - Training Sword": ItemData(EQUIPMENT, 532 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Life Line": ItemData(EQUIPMENT, 302 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Soul Keeper": ItemData(EQUIPMENT, 303 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 1H, The Deep Sea, Expert Zones
- "Equipment - Crabs Claw": ItemData(EQUIPMENT, 411 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 1H, The Deep Sea, Expert Zones
- "Equipment - Kings Guard": ItemData(EQUIPMENT, 316 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 1H, Castle Sequoia, End-Game Zones
- "Equipment - Diamond Sword": ItemData(EQUIPMENT, 135 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Balrog": ItemData(EQUIPMENT, 369 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Oily Sword": ItemData(EQUIPMENT, 279 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 2H, Castle Sequoia, End-Game Zones
-
- #Axes
- "Equipment - Hand Axe": ItemData(EQUIPMENT, 55 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 1H, Shop, Beginner Zones
- "Equipment - Craftwork Axe": ItemData(EQUIPMENT, 94 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 1H, Capital Sequoia, Advanced Zones
- "Equipment - Cleaver": ItemData(EQUIPMENT, 2 + equipment_index_offset, ItemClassification.useful), #Tier 1 2H, Spawning Meadows, Beginner Zones
- "Equipment - Chopper": ItemData(EQUIPMENT, 66 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 2H, Shop, Beginner Zones
- "Equipment - Hunting Axe": ItemData(EQUIPMENT, 187 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 1H, Rolling Quintar Fields, Advanced Zones
- "Equipment - Silver Axe": ItemData(EQUIPMENT, 104 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Stone Splitter": ItemData(EQUIPMENT, 201 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
- "Equipment - Broadaxe": ItemData(EQUIPMENT, 383 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
- "Equipment - Artisan Axe": ItemData(EQUIPMENT, 158 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
- "Equipment - Hatchet": ItemData(EQUIPMENT, 386 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 1H, Poko Poko Desert, Advanced Zones
- "Equipment - Axe of Light": ItemData(EQUIPMENT, 387 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gold Axe": ItemData(EQUIPMENT, 139 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - War Axe": ItemData(EQUIPMENT, 384 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Advanced Zones
- "Equipment - Berserker Axe": ItemData(EQUIPMENT, 390 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Expert Zones
- "Equipment - Gaia Axe": ItemData(EQUIPMENT, 385 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 2H, Shoudu Province, Expert Zones
- "Equipment - Master Axe": ItemData(EQUIPMENT, 251 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 1H, Shop, Advanced Zones
- "Equipment - Ancient Axe": ItemData(EQUIPMENT, 391 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 2H, Shop, Expert Zones
- "Equipment - Master Bigaxe": ItemData(EQUIPMENT, 250 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 2H, Shop, Advanced Zones
- "Equipment - Aphotic Edge": ItemData(EQUIPMENT, 388 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 1H, The Sequoia, End-Game Zones
- "Equipment - Diamond Axe": ItemData(EQUIPMENT, 136 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Decapitator": ItemData(EQUIPMENT, 280 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 2H, Castle Sequoia, End-Game Zones
- "Equipment - Ragebringer": ItemData(EQUIPMENT, 274 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 2H, Capital Sequoia, Advanced Zones
-
- #Daggers
- "Equipment - Dirk": ItemData(EQUIPMENT, 3 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
- "Equipment - Stabbers": ItemData(EQUIPMENT, 63 + equipment_index_offset, ItemClassification.useful), #Tier 1 Regular, Spawning Meadows, Beginner Zones
- "Equipment - Fishgutter": ItemData(EQUIPMENT, 77 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
- "Equipment - Poisonkiss": ItemData(EQUIPMENT, 40 + equipment_index_offset, ItemClassification.useful), #Tier 1 Regular, Pale Grotto, Beginner Zones
- "Equipment - Shank": ItemData(EQUIPMENT, 60 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
- "Equipment - Craftwork Dagger": ItemData(EQUIPMENT, 95 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 Regular, Capital Sequoia, Advanced Zones
- "Equipment - Tanto": ItemData(EQUIPMENT, 192 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Ninja, Okimoto N.S., Advanced Zones
- "Equipment - Butterfly": ItemData(EQUIPMENT, 203 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Ninja, Okimoto N.S., Advanced Zones
- "Equipment - Kris": ItemData(EQUIPMENT, 202 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
- "Equipment - Ambush Knife": ItemData(EQUIPMENT, 184 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Regular, Greenshire Reprise, Advanced Zones
- "Equipment - Rondel": ItemData(EQUIPMENT, 204 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
- "Equipment - Silver Dagger": ItemData(EQUIPMENT, 113 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Dagger": ItemData(EQUIPMENT, 159 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
- "Equipment - Parry Knife": ItemData(EQUIPMENT, 397 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Ninja, Salmon River, Advanced Zones
- "Equipment - Janbiya": ItemData(EQUIPMENT, 392 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 Ninja, Shop, Advanced Zones
- "Equipment - Sai": ItemData(EQUIPMENT, 396 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Ninja, Shop, Expert Zones
- "Equipment - Kodachi": ItemData(EQUIPMENT, 400 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Ninja, Shop, Expert Zones
- "Equipment - Butter Cutter": ItemData(EQUIPMENT, 198 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Regular, Poko Poko Desert, Advanced Zones
- "Equipment - Soul Kris": ItemData(EQUIPMENT, 305 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Regular, Shoudu Province, Expert Zones
- "Equipment - Gouger": ItemData(EQUIPMENT, 61 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Fanged Knife": ItemData(EQUIPMENT, 526 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Regular, Shop, Expert Zones
- "Equipment - Cinquedea": ItemData(EQUIPMENT, 393 + equipment_index_offset, ItemClassification.useful), #from Delende fisher; Tier 3 Regular, Delende, Beginner Zones
- "Equipment - Gold Dagger": ItemData(EQUIPMENT, 140 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Kowakizashi": ItemData(EQUIPMENT, 398 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Bone Knife": ItemData(EQUIPMENT, 395 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Poignard": ItemData(EQUIPMENT, 394 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 Regular, Shop, Expert Zones
- "Equipment - Flamespike": ItemData(EQUIPMENT, 72 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Regular, Jidamba Tangle, Expert Zones
- "Equipment - Master Dagger": ItemData(EQUIPMENT, 269 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 Regular, Shop, Advanced Zones
- "Equipment - Sange": ItemData(EQUIPMENT, 317 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Ninja, The Sequoia, End-Game Zones
- "Equipment - Yasha": ItemData(EQUIPMENT, 318 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Ninja, Shoudu Province, Expert Zones
- "Equipment - Legend Spike": ItemData(EQUIPMENT, 315 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Eclipse": ItemData(EQUIPMENT, 41 + equipment_index_offset, ItemClassification.useful, 0), #Capital Sequoia shady shop guy
- "Equipment - Mage Masher": ItemData(EQUIPMENT, 282 + equipment_index_offset, ItemClassification.useful, 0), #Capital Sequoia shady shop guy
- "Equipment - Mages Pike": ItemData(EQUIPMENT, 306 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Regular, The New World, End-Game Zones
- "Equipment - Diamond Dagger": ItemData(EQUIPMENT, 137 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Rapiers
- "Equipment - Rapier": ItemData(EQUIPMENT, 73 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Stinger": ItemData(EQUIPMENT, 1 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Toothpick": ItemData(EQUIPMENT, 42 + equipment_index_offset, ItemClassification.useful), #Tier 1, Pale Grotto, Beginner Zones
- "Equipment - Craftwork Rapier": ItemData(EQUIPMENT, 96 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Estoc": ItemData(EQUIPMENT, 207 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Scarlette": ItemData(EQUIPMENT, 206 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Fish Skewer": ItemData(EQUIPMENT, 175 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Sara Sara Bazaar, Advanced Zones
- "Equipment - Silver Rapier": ItemData(EQUIPMENT, 114 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Rapier": ItemData(EQUIPMENT, 160 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Dueller": ItemData(EQUIPMENT, 10 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Poko Poko Desert, Advanced Zones
- "Equipment - Vulture": ItemData(EQUIPMENT, 402 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Falcon Dance": ItemData(EQUIPMENT, 408 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Fleuret": ItemData(EQUIPMENT, 404 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Gold Rapier": ItemData(EQUIPMENT, 141 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Epee": ItemData(EQUIPMENT, 405 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Windsong": ItemData(EQUIPMENT, 407 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, The Chalice of Tar, Expert Zones
- "Equipment - Master Rapier": ItemData(EQUIPMENT, 252 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Nightingale": ItemData(EQUIPMENT, 401 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
- "Equipment - Chartreuse": ItemData(EQUIPMENT, 403 + equipment_index_offset, ItemClassification.useful), #Tier 5, Delende, Beginner Zones
- "Equipment - Murgleys": ItemData(EQUIPMENT, 406 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5, The Open Sea, Expert Zones
- "Equipment - Diamond Rapier": ItemData(EQUIPMENT, 142 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Katanas
- "Equipment - Craftwork Katana": ItemData(EQUIPMENT, 97 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a katana as a treat)
- "Equipment - Tachi": ItemData(EQUIPMENT, 399 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Okimoto N.S., Advanced Zones
- "Equipment - Silver Katana": ItemData(EQUIPMENT, 115 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Katana": ItemData(EQUIPMENT, 161 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Nansen": ItemData(EQUIPMENT, 363 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Mitsutada": ItemData(EQUIPMENT, 22 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Hitofuri": ItemData(EQUIPMENT, 364 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Kokaiji": ItemData(EQUIPMENT, 23 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Gold Katana": ItemData(EQUIPMENT, 143 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Hokuken": ItemData(EQUIPMENT, 588 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Tomokirimaru": ItemData(EQUIPMENT, 366 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Ichimonji": ItemData(EQUIPMENT, 362 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Master Katana": ItemData(EQUIPMENT, 253 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Muramasa": ItemData(EQUIPMENT, 367 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Shoudu Province, Expert Zones
- "Equipment - Diamond Katana": ItemData(EQUIPMENT, 144 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Spears
- "Equipment - Short Spear": ItemData(EQUIPMENT, 4 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 1, Shop, Advanced Zones
- "Equipment - Craftwork Spear": ItemData(EQUIPMENT, 98 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a spear as a treat)
- "Equipment - Javelin": ItemData(EQUIPMENT, 205 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Skewer": ItemData(EQUIPMENT, 190 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Cobblestone Crag, Advanced Zones
- "Equipment - Prodder": ItemData(EQUIPMENT, 183 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Sequoia, Advanced Zones
- "Equipment - Silver Spear": ItemData(EQUIPMENT, 116 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Spear": ItemData(EQUIPMENT, 162 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Trident": ItemData(EQUIPMENT, 409 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Salmon River, Advanced Zones
- "Equipment - Wind Lance": ItemData(EQUIPMENT, 410 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Halberd": ItemData(EQUIPMENT, 418 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Rock, Expert Zones
- "Equipment - Gold Spear": ItemData(EQUIPMENT, 145 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Voulge": ItemData(EQUIPMENT, 419 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Radiance": ItemData(EQUIPMENT, 417 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Northern Cave, Expert Zones
- "Equipment - Partizan": ItemData(EQUIPMENT, 416 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Master Spear": ItemData(EQUIPMENT, 254 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Incursier": ItemData(EQUIPMENT, 563 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Royal Guard": ItemData(EQUIPMENT, 275 + equipment_index_offset, ItemClassification.useful,0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
- "Equipment - Gungnir": ItemData(EQUIPMENT, 304 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Diamond Spear": ItemData(EQUIPMENT, 146 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Scythes
- "Equipment - Battle Scythe": ItemData(EQUIPMENT, 6 + equipment_index_offset, ItemClassification.useful), #Tier 1, Proving Meadows, Beginner Zones
- "Equipment - Craftwork Scythe": ItemData(EQUIPMENT, 99 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - War Scythe": ItemData(EQUIPMENT, 208 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Wind Sickle": ItemData(EQUIPMENT, 413 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Silver Scythe": ItemData(EQUIPMENT, 117 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Scythe": ItemData(EQUIPMENT, 163 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Thresher": ItemData(EQUIPMENT, 294 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Grim Scythe": ItemData(EQUIPMENT, 293 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Ancient Reservoir, Advanced Zones
- "Equipment - Great Thresher": ItemData(EQUIPMENT, 295 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Frost Reaper": ItemData(EQUIPMENT, 414 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Tall Tall Heights, Expert Zones
- "Equipment - Gold Scythe": ItemData(EQUIPMENT, 147 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Ember Scythe": ItemData(EQUIPMENT, 589 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gravedigger": ItemData(EQUIPMENT, 415 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
- "Equipment - Wind Thresher": ItemData(EQUIPMENT, 590 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Quintar Mausoleum, Expert Zones
- "Equipment - Master Scythe": ItemData(EQUIPMENT, 255 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Twilight": ItemData(EQUIPMENT, 412 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Adjudicator": ItemData(EQUIPMENT, 245 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Arctic Chill": ItemData(EQUIPMENT, 556 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Diamond Scythe": ItemData(EQUIPMENT, 148 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Bows
- "Equipment - Craftwork Bow": ItemData(EQUIPMENT, 105 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a bow as a treat)
- "Equipment - Short Bow": ItemData(EQUIPMENT, 7 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Hunting Bow": ItemData(EQUIPMENT, 181 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Rolling Quintar Fields, Advanced Zones
- "Equipment - Long Bow": ItemData(EQUIPMENT, 209 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Silver Bow": ItemData(EQUIPMENT, 118 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Bow": ItemData(EQUIPMENT, 164 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Battle Bow": ItemData(EQUIPMENT, 297 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Composite Bow": ItemData(EQUIPMENT, 296 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Habins Bow": ItemData(EQUIPMENT, 222 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Razor Bow": ItemData(EQUIPMENT, 298 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Elven Bow": ItemData(EQUIPMENT, 421 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gold Bow": ItemData(EQUIPMENT, 149 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Spore Shooter": ItemData(EQUIPMENT, 180 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - War Bow": ItemData(EQUIPMENT, 300 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Siege Bow": ItemData(EQUIPMENT, 301 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Rune Bow": ItemData(EQUIPMENT, 299 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Master Bow": ItemData(EQUIPMENT, 256 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Panakeia": ItemData(EQUIPMENT, 530 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artemis": ItemData(EQUIPMENT, 281 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
- "Equipment - Dream Hunter": ItemData(EQUIPMENT, 420 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
- "Equipment - Diamond Bow": ItemData(EQUIPMENT, 150 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Staves
- "Equipment - Short Staff": ItemData(EQUIPMENT, 5 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
- "Equipment - Cedar Staff": ItemData(EQUIPMENT, 62 + equipment_index_offset, ItemClassification.useful), #Tier 1 Regular, Spawning Meadows, Beginner Zones
- "Equipment - Gnarled Root": ItemData(EQUIPMENT, 15 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
- "Equipment - Craftwork Staff": ItemData(EQUIPMENT, 100 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 Regular, Capital Sequoia, Advanced Zones
- "Equipment - Bone Smasher": ItemData(EQUIPMENT, 14 + equipment_index_offset, ItemClassification.useful), #Tier 1 Beating, Delende, Beginner Zones
- "Equipment - Iron Rod": ItemData(EQUIPMENT, 426 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Beating, Capital Jail, Advanced Zones
- "Equipment - Quarterstaff": ItemData(EQUIPMENT, 210 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
- "Equipment - Walking Stick": ItemData(EQUIPMENT, 188 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Regular, Cobblestone Crag, Advanced Zones
- "Equipment - Maplewood": ItemData(EQUIPMENT, 211 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
- "Equipment - Silver Staff": ItemData(EQUIPMENT, 119 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Staff": ItemData(EQUIPMENT, 165 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
- "Equipment - Knockout Stick": ItemData(EQUIPMENT, 335 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Beating, Sara Sara Bazaar, Advanced Zones
- "Equipment - Skullbasher": ItemData(EQUIPMENT, 427 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 Beating, Shop, Advanced Zones
- "Equipment - Future Sight": ItemData(EQUIPMENT, 561 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Battle Staff": ItemData(EQUIPMENT, 67 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Beating, Shop, Expert Zones
- "Equipment - Digested Staff": ItemData(EQUIPMENT, 228 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Natures Gift": ItemData(EQUIPMENT, 423 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 Regular, Shop, Advanced Zones
- "Equipment - Life Jewel": ItemData(EQUIPMENT, 422 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Regular, Overpass (Dione Shrine), Advanced (Expert) Zones
- "Equipment - Gold Staff": ItemData(EQUIPMENT, 151 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - War Staff": ItemData(EQUIPMENT, 428 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 Beating, Shop, Expert Zones
- "Equipment - Apprentice": ItemData(EQUIPMENT, 424 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Regular, Northern Cave, Expert Zones
- "Equipment - Sages Walker": ItemData(EQUIPMENT, 425 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Regular, Slip Glide Ride, Expert Zones
- "Equipment - Master Staff": ItemData(EQUIPMENT, 257 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 Regular, Shop, Advanced Zones
- "Equipment - Beats Stick": ItemData(EQUIPMENT, 289 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Staff of Balance": ItemData(EQUIPMENT, 290 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Regular, Jidamba Eaclaneya, Expert Zones
- "Equipment - Judgement": ItemData(EQUIPMENT, 429 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Regular, Ancient Labyrinth, End-Game Zones
- "Equipment - Diamond Staff": ItemData(EQUIPMENT, 152 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Wands
- "Equipment - Ash Wand": ItemData(EQUIPMENT, 8 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Cedar Wand": ItemData(EQUIPMENT, 13 + equipment_index_offset, ItemClassification.useful), #Tier 1, Spawning Meadows, Beginner Zones
- "Equipment - Oak Wand": ItemData(EQUIPMENT, 16 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Torch": ItemData(EQUIPMENT, 64 + equipment_index_offset, ItemClassification.useful), #Tier 1, Draft Shaft Conduit, Beginner Zones
- "Equipment - Ink Stick": ItemData(EQUIPMENT, 80 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Craftwork Wand": ItemData(EQUIPMENT, 101 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Soul Wand": ItemData(EQUIPMENT, 213 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Static Rod": ItemData(EQUIPMENT, 189 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Quintar Nest, Advanced Zones
- "Equipment - Maple Wand": ItemData(EQUIPMENT, 212 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Silver Wand": ItemData(EQUIPMENT, 120 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Wand": ItemData(EQUIPMENT, 166 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Storm Rod": ItemData(EQUIPMENT, 267 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Sara Sara Bazaar, Advanced Zones
- "Equipment - Baton": ItemData(EQUIPMENT, 434 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Cursegiver": ItemData(EQUIPMENT, 432 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, The Undercity, Expert Zones
- "Equipment - Effigy": ItemData(EQUIPMENT, 433 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Gold Wand": ItemData(EQUIPMENT, 153 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Rune Wand": ItemData(EQUIPMENT, 435 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Lands End, Expert Zones
- "Equipment - Sentinel Rod": ItemData(EQUIPMENT, 431 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Stardust Wand": ItemData(EQUIPMENT, 430 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Eaclaneya, Expert Zones
- "Equipment - Master Wand": ItemData(EQUIPMENT, 258 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Aura Focus": ItemData(EQUIPMENT, 278 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Paladin Wand": ItemData(EQUIPMENT, 276 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, The Deep Sea, Expert Zones
- "Equipment - Obelisk": ItemData(EQUIPMENT, 307 + equipment_index_offset, ItemClassification.useful, 0), #black market shop (Z14_hobo shop)
- "Equipment - Flameseeker": ItemData(EQUIPMENT, 358 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Jidamba Eaclaneya, Expert Zones
- "Equipment - Diamond Wand": ItemData(EQUIPMENT, 154 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Books
- "Equipment - Moby Dick": ItemData(EQUIPMENT, 51 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Orylei": ItemData(EQUIPMENT, 65 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Craftwork Pages": ItemData(EQUIPMENT, 102 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a book as a treat)
- "Equipment - Encyclopedia": ItemData(EQUIPMENT, 214 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Gospel": ItemData(EQUIPMENT, 194 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Boomer Society, Advanced Zones
- "Equipment - Paypirbak": ItemData(EQUIPMENT, 223 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Salmon Pass, Advanced Zones
- "Equipment - Art of War": ItemData(EQUIPMENT, 224 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Okimoto N.S., Advanced Zones
- "Equipment - Silver Pages": ItemData(EQUIPMENT, 121 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Pages": ItemData(EQUIPMENT, 167 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Grimoire": ItemData(EQUIPMENT, 438 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Blank Pages": ItemData(EQUIPMENT, 437 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Sara Sara Beach, Advanced Zones
- "Equipment - Tome of Light": ItemData(EQUIPMENT, 439 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Volcano, Expert Zones
- "Equipment - Hydrology": ItemData(EQUIPMENT, 441 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Gold Pages": ItemData(EQUIPMENT, 155 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Dark Gospel": ItemData(EQUIPMENT, 440 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Beaurior Volcano, Expert Zones
- "Equipment - Divination": ItemData(EQUIPMENT, 442 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Malifice": ItemData(EQUIPMENT, 443 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
- "Equipment - Master Pages": ItemData(EQUIPMENT, 259 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Codex": ItemData(EQUIPMENT, 436 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Diamond Pages": ItemData(EQUIPMENT, 156 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Armor
- #Shields
- "Equipment - Buckler": ItemData(EQUIPMENT, 44 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Stout Shield": ItemData(EQUIPMENT, 45 + equipment_index_offset, ItemClassification.useful), #Tier 1, Spawning Meadows, Beginner Zones
- "Equipment - Iron Guard": ItemData(EQUIPMENT, 68 + equipment_index_offset, ItemClassification.useful), #Tier 1, Yamagawa M.A., Beginner Zones
- "Equipment - Stalwart Shield": ItemData(EQUIPMENT, 88 + equipment_index_offset, ItemClassification.useful), #Tier 1, Trial Caves/Skumparadise, Beginner Zones
- "Equipment - Craftwork Shield": ItemData(EQUIPMENT, 506 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Vanguard": ItemData(EQUIPMENT, 111 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Duelling Shield": ItemData(EQUIPMENT, 215 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Lucky Platter": ItemData(EQUIPMENT, 103 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Pipeline, Advanced Zones
- "Equipment - Boomer Shield": ItemData(EQUIPMENT, 178 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Boomer Society, Advanced Zones
- "Equipment - Silver Shield": ItemData(EQUIPMENT, 507 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Shield": ItemData(EQUIPMENT, 168 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Cross Shield": ItemData(EQUIPMENT, 444 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Brass Cross": ItemData(EQUIPMENT, 448 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Blood Shield": ItemData(EQUIPMENT, 560 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - The Immovable": ItemData(EQUIPMENT, 451 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Cross Guard": ItemData(EQUIPMENT, 446 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Mages Platter": ItemData(EQUIPMENT, 452 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Salmon River, Advanced Zones
- "Equipment - Gold Shield": ItemData(EQUIPMENT, 447 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Bulkwark": ItemData(EQUIPMENT, 449 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Flame Guard": ItemData(EQUIPMENT, 450 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Wizards Wall": ItemData(EQUIPMENT, 453 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
- "Equipment - Master Shield": ItemData(EQUIPMENT, 260 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Turtle Shell": ItemData(EQUIPMENT, 445 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
- "Equipment - Tower Shield": ItemData(EQUIPMENT, 344 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Jidamba Tangle, Expert Zones
- "Equipment - Nomads Guard": ItemData(EQUIPMENT, 288 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Continental Tram, Expert Zones
- "Equipment - Ether Shield": ItemData(EQUIPMENT, 277 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Mirror Shield": ItemData(EQUIPMENT, 246 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Ancient Labyrinth, End-Game Zones
- "Equipment - Diamond Shield": ItemData(EQUIPMENT, 237 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Heavy Head
- "Equipment - Chain Helm": ItemData(EQUIPMENT, 25 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Sturdy Helm": ItemData(EQUIPMENT, 26 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Storm Helm": ItemData(EQUIPMENT, 74 + equipment_index_offset, ItemClassification.useful), #Tier 1, Pale Grotto, Beginner Zones
- "Equipment - Copper Helm": ItemData(EQUIPMENT, 69 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Craftwork Helm": ItemData(EQUIPMENT, 508 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Bronze Helm": ItemData(EQUIPMENT, 106 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Scale Helm": ItemData(EQUIPMENT, 128 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Iron Helm": ItemData(EQUIPMENT, 125 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Jojo Sewers, Advanced Zones
- "Equipment - Battle Helm": ItemData(EQUIPMENT, 132 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
- "Equipment - Silver Helm": ItemData(EQUIPMENT, 509 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Helm": ItemData(EQUIPMENT, 169 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Orion Barbut": ItemData(EQUIPMENT, 465 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Iron Barbut": ItemData(EQUIPMENT, 468 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Horned Helm": ItemData(EQUIPMENT, 464 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Gold Helm": ItemData(EQUIPMENT, 469 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Cross Helm": ItemData(EQUIPMENT, 466 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Insignia Helm": ItemData(EQUIPMENT, 470 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Tall Tall Heights, Expert Zones
- "Equipment - Demon Helm": ItemData(EQUIPMENT, 467 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Master Helm": ItemData(EQUIPMENT, 261 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Guts Busby": ItemData(EQUIPMENT, 308 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Raid Helm": ItemData(EQUIPMENT, 471 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Spellsword Helm": ItemData(EQUIPMENT, 292 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Sequoia Athenaeum, Expert Zones
- "Equipment - Diamond Helm": ItemData(EQUIPMENT, 236 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Heavy Body
- "Equipment - Breastplate": ItemData(EQUIPMENT, 18 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Ring Mail": ItemData(EQUIPMENT, 28 + equipment_index_offset, ItemClassification.useful), #Tier 1, Pale Grotto, Beginner Zones
- "Equipment - Copper Suit": ItemData(EQUIPMENT, 29 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Plate of Wolf": ItemData(EQUIPMENT, 84 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Underpass, Advanced Zones
- "Equipment - Craftwork Mail": ItemData(EQUIPMENT, 510 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Bronze Suit": ItemData(EQUIPMENT, 107 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Scale Mail": ItemData(EQUIPMENT, 127 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Iron Armor": ItemData(EQUIPMENT, 126 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Jojo Sewers, Advanced Zones
- "Equipment - Battleplate": ItemData(EQUIPMENT, 43 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
- "Equipment - Silver Mail": ItemData(EQUIPMENT, 511 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Mail": ItemData(EQUIPMENT, 170 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Orion Armor": ItemData(EQUIPMENT, 455 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Plate of Tiger": ItemData(EQUIPMENT, 129 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Plate Mail": ItemData(EQUIPMENT, 460 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Knights Plate": ItemData(EQUIPMENT, 456 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, The Undercity, Expert Zones
- "Equipment - Bone Mail": ItemData(EQUIPMENT, 462 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Gold Mail": ItemData(EQUIPMENT, 459 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Sky Armor": ItemData(EQUIPMENT, 461 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
- "Equipment - Plate of Lion": ItemData(EQUIPMENT, 130 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Slip Glide Ride, Expert Zones
- "Equipment - Dragon Mail": ItemData(EQUIPMENT, 457 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Demon Plate": ItemData(EQUIPMENT, 458 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Master Mail": ItemData(EQUIPMENT, 262 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Construct Mail": ItemData(EQUIPMENT, 272 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Guardian Angel": ItemData(EQUIPMENT, 291 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Plate of Whale": ItemData(EQUIPMENT, 131 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5, The Open Sea, Expert Zones
- "Equipment - Lunar Mail": ItemData(EQUIPMENT, 463 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The New World, End-Game Zones
- "Equipment - Diamond Mail": ItemData(EQUIPMENT, 235 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Warrior Mail": ItemData(EQUIPMENT, 533 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Warlock Mail": ItemData(EQUIPMENT, 534 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Aegis Mail": ItemData(EQUIPMENT, 536 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Reaper Mail": ItemData(EQUIPMENT, 535 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Samurai Mail": ItemData(EQUIPMENT, 538 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Valkyrie Mail": ItemData(EQUIPMENT, 537 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Beastmaster Mail": ItemData(EQUIPMENT, 557 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Mimic Mail": ItemData(EQUIPMENT, 548 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
-
- #Medium Head
- "Equipment - Leather Cap": ItemData(EQUIPMENT, 24 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Beret": ItemData(EQUIPMENT, 27 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Storm Cap": ItemData(EQUIPMENT, 75 + equipment_index_offset, ItemClassification.useful), #Tier 1, Seaside Cliffs, Beginner Zones
- "Equipment - Headgear": ItemData(EQUIPMENT, 30 + equipment_index_offset, ItemClassification.useful), #Tier 1, Seaside Cliffs, Beginner Zones
- "Equipment - Craftwork Cap": ItemData(EQUIPMENT, 512 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Rugged Hat": ItemData(EQUIPMENT, 219 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Spore Blocker": ItemData(EQUIPMENT, 195 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Rolling Quintar Fields, Advanced Zones
- "Equipment - Vikings Hat": ItemData(EQUIPMENT, 220 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Silver Cap": ItemData(EQUIPMENT, 513 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Cap": ItemData(EQUIPMENT, 171 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Combat Band": ItemData(EQUIPMENT, 483 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Red Cap": ItemData(EQUIPMENT, 233 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Ancient Reservoir, Advanced Zones
- "Equipment - Bandana": ItemData(EQUIPMENT, 484 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Suitor Hat": ItemData(EQUIPMENT, 485 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Gold Cap": ItemData(EQUIPMENT, 520 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Red Hat": ItemData(EQUIPMENT, 480 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Pirate Hat": ItemData(EQUIPMENT, 481 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Ice fisher in Tall Tall Heights; Tier 4, Tall Tall Heights, Expert Zones
- "Equipment - Tall Tall Hat": ItemData(EQUIPMENT, 486 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Master Cap": ItemData(EQUIPMENT, 263 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Battle Band": ItemData(EQUIPMENT, 345 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The Sequoia, End-Game Zones
- "Equipment - Captains Hat": ItemData(EQUIPMENT, 482 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5, Sara Sara Bazaar, Advanced Zones
- "Equipment - Red Headgear": ItemData(EQUIPMENT, 487 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
- "Equipment - Diamond Cap": ItemData(EQUIPMENT, 238 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Medium Body
- "Equipment - Leather Outfit": ItemData(EQUIPMENT, 17 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Studded Armor": ItemData(EQUIPMENT, 35 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Leather Mail": ItemData(EQUIPMENT, 36 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Craftwork Vest": ItemData(EQUIPMENT, 514 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a medium body as a treat)
- "Equipment - Chain Vest": ItemData(EQUIPMENT, 217 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Combat Vest": ItemData(EQUIPMENT, 218 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Smelly Gi": ItemData(EQUIPMENT, 268 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Jojo Sewers, Advanced Zones
- "Equipment - Training Gi": ItemData(EQUIPMENT, 229 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Okimoto N.S., Advanced Zones
- "Equipment - Tuxedo": ItemData(EQUIPMENT, 176 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Sequoia, Advanced Zones
- "Equipment - Silver Vest": ItemData(EQUIPMENT, 515 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Vest": ItemData(EQUIPMENT, 172 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Power Vest": ItemData(EQUIPMENT, 472 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Red Coat": ItemData(EQUIPMENT, 57 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Ancient Reservoir, Advanced Zones
- "Equipment - Drifters Vest": ItemData(EQUIPMENT, 474 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Bandage Wrap": ItemData(EQUIPMENT, 558 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gaia Vest": ItemData(EQUIPMENT, 473 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Brigandine": ItemData(EQUIPMENT, 477 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, The Undercity, Expert Zones
- "Equipment - Gold Vest": ItemData(EQUIPMENT, 521 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Onion Gi": ItemData(EQUIPMENT, 475 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Martial Vest": ItemData(EQUIPMENT, 478 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Judo Gi": ItemData(EQUIPMENT, 479 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Tall Tall Heights, Expert Zones
- "Equipment - Master Vest": ItemData(EQUIPMENT, 264 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Quintar Pelt": ItemData(EQUIPMENT, 493 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
- "Equipment - Shadow Gi": ItemData(EQUIPMENT, 347 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Shoudu Province, Expert Zones
- "Equipment - Rex Vest": ItemData(EQUIPMENT, 476 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Slime Coat": ItemData(EQUIPMENT, 524 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Diamond Vest": ItemData(EQUIPMENT, 239 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Monk Vest": ItemData(EQUIPMENT, 539 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Rogue Vest": ItemData(EQUIPMENT, 540 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Fencer Vest": ItemData(EQUIPMENT, 541 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Hunter Vest": ItemData(EQUIPMENT, 542 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Ninja Vest": ItemData(EQUIPMENT, 543 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Nomad Vest": ItemData(EQUIPMENT, 544 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Beatsmith Vest": ItemData(EQUIPMENT, 545 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Assassin Vest": ItemData(EQUIPMENT, 546 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
-
- #Light Head
- "Equipment - Hemp Hood": ItemData(EQUIPMENT, 31 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Cotton Hood": ItemData(EQUIPMENT, 32 + equipment_index_offset, ItemClassification.useful), #Tier 1, Delende, Beginner Zones
- "Equipment - Storm Hood": ItemData(EQUIPMENT, 76 + equipment_index_offset, ItemClassification.useful), #Tier 1, Delende, Beginner Zones
- "Equipment - Holy Hat": ItemData(EQUIPMENT, 33 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Craftwork Crown": ItemData(EQUIPMENT, 516 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Silk Hat": ItemData(EQUIPMENT, 110 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Circlet": ItemData(EQUIPMENT, 133 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Quintar Sanctum, Advanced Zones
- "Equipment - Holy Miter": ItemData(EQUIPMENT, 109 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Woven Hood": ItemData(EQUIPMENT, 122 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
- "Equipment - Silver Crown": ItemData(EQUIPMENT, 517 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Crown": ItemData(EQUIPMENT, 173 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Clerics Hood": ItemData(EQUIPMENT, 341 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Wizards Hat": ItemData(EQUIPMENT, 340 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Fairys Crown": ItemData(EQUIPMENT, 352 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Quilted Hat": ItemData(EQUIPMENT, 353 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Blood Hat": ItemData(EQUIPMENT, 559 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Plague Mask": ItemData(EQUIPMENT, 342 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
- "Equipment - Guard Crown": ItemData(EQUIPMENT, 356 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Rock, Expert Zones
- "Equipment - Gold Crown": ItemData(EQUIPMENT, 522 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Bronze Hairpin": ItemData(EQUIPMENT, 34 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tall Tall Heights fisher; Tier 4, Tall Tall Heights, Expert Zones
- "Equipment - Regen Crown": ItemData(EQUIPMENT, 343 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Ravens Hood": ItemData(EQUIPMENT, 348 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Celestial Crown": ItemData(EQUIPMENT, 52 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Eaclaneya, Expert Zones
- "Equipment - Master Crown": ItemData(EQUIPMENT, 265 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
- "Equipment - Pointy Hat": ItemData(EQUIPMENT, 531 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, The Deep Sea, Expert Zones
- "Equipment - Vita Crown": ItemData(EQUIPMENT, 271 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Ancient Labyrinth, End-Game Zones
- "Equipment - Pact Crown": ItemData(EQUIPMENT, 350 + equipment_index_offset, ItemClassification.useful, 0), #black market shop (Z14_hobo shop)
- "Equipment - Protector": ItemData(EQUIPMENT, 354 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
- "Equipment - Diamond Crown": ItemData(EQUIPMENT, 240 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Light Body
- "Equipment - Hemp Robe": ItemData(EQUIPMENT, 19 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Cotton Robe": ItemData(EQUIPMENT, 20 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
- "Equipment - Mages Robe": ItemData(EQUIPMENT, 21 + equipment_index_offset, ItemClassification.useful), #Tier 1, Delende, Beginner Zones
- "Equipment - Swimmers Top": ItemData(EQUIPMENT, 81 + equipment_index_offset, ItemClassification.useful), #Tier 1, Seaside Cliffs, Beginner Zones
- "Equipment - Craftwork Robe": ItemData(EQUIPMENT, 518 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
- "Equipment - Priest Garb": ItemData(EQUIPMENT, 216 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Dress": ItemData(EQUIPMENT, 134 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Quintar Sanctum, Advanced Zones
- "Equipment - Silk Shirt": ItemData(EQUIPMENT, 108 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Woven Shirt": ItemData(EQUIPMENT, 230 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
- "Equipment - Silver Cape": ItemData(EQUIPMENT, 519 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Artisan Shirt": ItemData(EQUIPMENT, 174 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Wizards Robe": ItemData(EQUIPMENT, 124 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Clerics Robe": ItemData(EQUIPMENT, 123 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Cosplay Garb": ItemData(EQUIPMENT, 336 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Sturdy Cape": ItemData(EQUIPMENT, 359 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Shelter Dress": ItemData(EQUIPMENT, 357 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Rock, Expert Zones
- "Equipment - Gold Robe": ItemData(EQUIPMENT, 523 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Winter Cape": ItemData(EQUIPMENT, 325 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Blue Cape": ItemData(EQUIPMENT, 360 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Lands End, Expert Zones
- "Equipment - Seekers Garb": ItemData(EQUIPMENT, 324 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Slip Glide Ride, Expert Zones
- "Equipment - Ravens Cloak": ItemData(EQUIPMENT, 349 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
- "Equipment - Master Cape": ItemData(EQUIPMENT, 266 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Archmage Vest": ItemData(EQUIPMENT, 337 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Ancient Labyrinth, End-Game Zones
- "Equipment - Saviors Cape": ItemData(EQUIPMENT, 338 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Assassins Cloak": ItemData(EQUIPMENT, 273 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The Depths, End-Game Zones
- "Equipment - Stealth Cape": ItemData(EQUIPMENT, 319 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The Sequoia, End-Game Zones
- "Equipment - Shell Gown": ItemData(EQUIPMENT, 355 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Diamond Robe": ItemData(EQUIPMENT, 241 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Cleric Robe": ItemData(EQUIPMENT, 547 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Wizard Robe": ItemData(EQUIPMENT, 549 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Shaman Robe": ItemData(EQUIPMENT, 550 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Scholar Robe": ItemData(EQUIPMENT, 551 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Chemist Robe": ItemData(EQUIPMENT, 552 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Dervish Robe": ItemData(EQUIPMENT, 553 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Weaver Robe": ItemData(EQUIPMENT, 554 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
- "Equipment - Summoner Robe": ItemData(EQUIPMENT, 555 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
-
- #Accessories
- "Equipment - Fervor Charm": ItemData(EQUIPMENT, 48 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende, Beginner Zones
- "Equipment - Dodge Charm": ItemData(EQUIPMENT, 39 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Soiled Den, Beginner Zones
- "Equipment - Earring": ItemData(EQUIPMENT, 79 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende, Beginner Zones
- "Equipment - Bracer": ItemData(EQUIPMENT, 70 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende and Seaside Cliffs, Beginner Zones
- "Equipment - Jewel of Defense": ItemData(EQUIPMENT, 50 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Seaside Cliffs, Beginner Zones
- "Equipment - Scope Bit": ItemData(EQUIPMENT, 226 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Seaside Cliffs and Quintar Nest, Beginner Zones
- "Equipment - Protect Amulet": ItemData(EQUIPMENT, 49 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende, Beginner Zones
- "Equipment - Wasps Stinger": ItemData(EQUIPMENT, 86 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gem Ring": ItemData(EQUIPMENT, 332 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Earth Bangle": ItemData(EQUIPMENT, 82 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Water Bangle": ItemData(EQUIPMENT, 83 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Looters Ring": ItemData(EQUIPMENT, 54 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Delende and Greenshire Reprise, Beginner Zones
- "Equipment - Burglars Glove": ItemData(EQUIPMENT, 53 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Spawning Meadows and Proving Meadows, Beginner Zones
- "Equipment - Torpid Cuffs": ItemData(EQUIPMENT, 331 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Yamagawa M.A., Beginner Zones
- "Equipment - Squirrel Dung": ItemData(EQUIPMENT, 85 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Spawning Meadows, Beginner Zones
- "Equipment - Fang Pendant": ItemData(EQUIPMENT, 196 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Shop (Capital), Capital Sequoia, Advanced Zones
- "Equipment - Mana Ring": ItemData(EQUIPMENT, 38 + equipment_index_offset, ItemClassification.useful), #Tier 2 Shop (Capital), Skumparadise, Beginner Zones
- "Equipment - Awake Ring": ItemData(EQUIPMENT, 87 + equipment_index_offset, ItemClassification.useful), #Tier 2 Shop (Capital), Skumparadise, Beginner Zones
- "Equipment - Prayer Beads": ItemData(EQUIPMENT, 186 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Shell Amulet": ItemData(EQUIPMENT, 179 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Shop (Capital), Greenshire Reprise, Advanced Zones
- "Equipment - Samurais Glove": ItemData(EQUIPMENT, 185 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
- "Equipment - Magic Finder": ItemData(EQUIPMENT, 323 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Loot, Okimoto N.S., Advanced Zones
- "Equipment - Learners Pin": ItemData(EQUIPMENT, 492 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Unique, Capital Sequoia, Advanced Zones
- "Equipment - Givers Ring": ItemData(EQUIPMENT, 182 + equipment_index_offset, ItemClassification.useful, 0, 1),#Tier 2 Unique, Capital Sequoia, Advanced Zones
- "Equipment - Aggro Band": ItemData(EQUIPMENT, 505 + equipment_index_offset, ItemClassification.useful, 0, 1), #Delende fisher; Tier 2 Unique, Delende, Advanced Zones
- "Equipment - Hemoring": ItemData(EQUIPMENT, 56 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Float Shoes": ItemData(EQUIPMENT, 193 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Unique, Okimoto N.S. and Lake Delende, Advanced Zones
- "Equipment - Hope Cross": ItemData(EQUIPMENT, 225 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Defense Shifter": ItemData(EQUIPMENT, 191 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Shop (Sara Sara), Ancient Reservoir, Advanced Zones
- "Equipment - Resist Shifter": ItemData(EQUIPMENT, 221 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Shop (Sara Sara), Ancient Reservoir, Advanced Zones
- "Equipment - Casters Ring": ItemData(EQUIPMENT, 9 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Fearsome Ring": ItemData(EQUIPMENT, 504 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Crit Fang": ItemData(EQUIPMENT, 502 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Dancing Shoes": ItemData(EQUIPMENT, 333 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
- "Equipment - Bulk Belt": ItemData(EQUIPMENT, 243 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Poison Talon": ItemData(EQUIPMENT, 334 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - First Strike Mitt": ItemData(EQUIPMENT, 501 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Menders Ring": ItemData(EQUIPMENT, 529 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Kitsune Mask": ItemData(EQUIPMENT, 503 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Pact Ring": ItemData(EQUIPMENT, 496 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
- "Equipment - Scope Specs": ItemData(EQUIPMENT, 562 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Loot, Poko Poko Desert, Advanced Zones
- "Equipment - Loot Finder": ItemData(EQUIPMENT, 326 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Quiet Shoes": ItemData(EQUIPMENT, 495 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Knicked Knackers": ItemData(EQUIPMENT, 232 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Unique, Shoudu Province, Expert Zones
- "Equipment - Looters Pin": ItemData(EQUIPMENT, 231 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Unique, Shoudu Province, Expert Zones
- "Equipment - Acrobat Shoes": ItemData(EQUIPMENT, 320 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Unique, Shoudu Province, Expert Zones
- "Equipment - Bone Trophy": ItemData(EQUIPMENT, 37 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Gusto Fang": ItemData(EQUIPMENT, 47 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Beads of Defense": ItemData(EQUIPMENT, 328 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 4 Shop (Tall Tall), Castle Sequoia, End-Game Zones
- "Equipment - Winter Mitten": ItemData(EQUIPMENT, 322 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
- "Equipment - Glasses": ItemData(EQUIPMENT, 327 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tall Tall Heights fisher; Tier 4 Unique, Tall Tall Heights, Expert Zones
- "Equipment - Gusto Charm": ItemData(EQUIPMENT, 46 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tall Tall Heights fisher; Tier 4 Unique, Tall Tall Heights, Expert Zones
- "Equipment - Muggers Glove": ItemData(EQUIPMENT, 309 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Unique, Shoudu Province, Expert Zones
- "Equipment - Fursuit": ItemData(EQUIPMENT, 490 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #crafted but i think its funny; Tier 5 Crafting, Quintar Reserve, Expert Zones
- "Equipment - Snow Pendant": ItemData(EQUIPMENT, 528 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Ogre Ball": ItemData(EQUIPMENT, 78 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Sanity Ring": ItemData(EQUIPMENT, 330 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5 Unique, The Open Sea, Expert Zones
- "Equipment - Undead Ring": ItemData(EQUIPMENT, 491 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, Quintar Reserve, Expert Zones
- "Equipment - Fairys Ring": ItemData(EQUIPMENT, 329 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5 Unique, The Open Sea, Expert Zones
- "Equipment - Oven Mitt": ItemData(EQUIPMENT, 321 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, The Deep Sea, Expert Zones
- "Equipment - Autumns Oath": ItemData(EQUIPMENT, 234 + equipment_index_offset, ItemClassification.useful), #Tier 5 Unique, Yamagawa M.A., Beginner Zones
- "Equipment - Springs Oath": ItemData(EQUIPMENT, 489 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Overpass (Okimoto N.S.), Advanced Zones
- "Equipment - Lucky Socks": ItemData(EQUIPMENT, 498 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Capital Sequoia, Advanced Zones
- "Equipment - Lucky Briefs": ItemData(EQUIPMENT, 499 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Capital Sequoia, Advanced Zones
- "Equipment - Tall Stand Ring": ItemData(EQUIPMENT, 244 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, Shoudu Province, Expert Zones
- "Equipment - Nomads Emblem": ItemData(EQUIPMENT, 287 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5 Unique, The Open Sea, Expert Zones
- "Equipment - Lockon Lense": ItemData(EQUIPMENT, 373 + equipment_index_offset, ItemClassification.useful, 0), #Quintar Enthusiast battle drop
- "Equipment - Red Hairpin": ItemData(EQUIPMENT, 247 + equipment_index_offset, ItemClassification.useful), #Delende fisher; #Tier 5 Unique, Delende, Beginner Zones
- "Equipment - Stone of Jodan": ItemData(EQUIPMENT, 286 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Jojo Sewers, Advanced Zones
- "Equipment - Ribbon": ItemData(EQUIPMENT, 346 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, Shoudu Province, Expert Zones
- "Equipment - Hand of Midas": ItemData(EQUIPMENT, 494 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Unique, The Sequoia, End-Game Zones
- "Equipment - Ghendolfs Ring": ItemData(EQUIPMENT, 242 + equipment_index_offset, ItemClassification.useful, 0),
- "Equipment - Master Material": ItemData(EQUIPMENT, 284 + equipment_index_offset, ItemClassification.useful, 0),
-
- #Progressive Equipment
- #the highest equipment id is 590 so these ids start at 600
- #Shopsanity ids are in brackets in the lists at the end
- "Equipment - Progressive 1H Sword": ItemData(EQUIPMENT, 900 + equipment_index_offset, ItemClassification.useful, 4, 1, 7, 1, 1, 4, 1, 0), #1H Sword IDs [0], 11, 71, 89, 93, 200, [199], [157], [379], 377, 375, 380, [374], 372, 381, [248], 303, 411, 316
- "Equipment - Progressive 2H Sword": ItemData(EQUIPMENT, 901 + equipment_index_offset, ItemClassification.useful, 1, 2, 2, 1, 0, 3, 1, 0), # 2H Sword IDs 12, [378], 177, [376], 197, 525, [370], 382, [249], 279
- "Equipment - Progressive 1H Axe": ItemData(EQUIPMENT, 902 + equipment_index_offset, ItemClassification.useful, 0, 3, 0, 1, 1, 1, 0, 0), # 1H Axe IDs [55], 94, 187, 386, [251], 388
- "Equipment - Progressive 2H Axe": ItemData(EQUIPMENT, 903 + equipment_index_offset, ItemClassification.useful, 1, 1, 1, 1, 1, 5, 2, 0), # 2H Axe IDs 2, [66], [201], [383], [158], [384], [390], 385, [391], [250], 280, 274
- "Equipment - Progressive Dagger": ItemData(EQUIPMENT, 904 + equipment_index_offset, ItemClassification.useful, 3, 3, 2, 1, 3, 4, 2, 0), # Dagger IDs [3], 63, [77], 40, [60], 95, [202], 184, [204], [159], 198, 305, [526], 393, [394], 72, [269], 306
- "Equipment - Progressive Ninja Dagger": ItemData(EQUIPMENT, 905 + equipment_index_offset, ItemClassification.useful, 0, 3, 1, 1, 0, 1, 2, 0), # Ninja Dagger IDs 192, 203, 397, [392], [396], [400], 317, 318
- "Equipment - Progressive Rapier": ItemData(EQUIPMENT, 906 + equipment_index_offset, ItemClassification.useful, 2, 3, 3, 1, 2, 5, 2, 0), # Rapier IDs [73], [1], 42, 96, [207], [206], 175, [160], 10, [402], [408], 404, [405], 407, [252], 401, 403, 406
- "Equipment - Progressive Katana": ItemData(EQUIPMENT, 907 + equipment_index_offset, ItemClassification.useful, 1, 1, 1, 0, 0, 3, 4, 0), # Katana IDs 97, 399, [161], [363], [22], [364], [23], [366], [253], 367
- "Equipment - Progressive Spear": ItemData(EQUIPMENT, 908 + equipment_index_offset, ItemClassification.useful, 1, 3, 3, 1, 0, 5, 1, 0), # Spear IDs [4], 98, [205], 190, 183, [162], 409, [410], 418, [419], 417, 416, [254], 275
- "Equipment - Progressive Scythe": ItemData(EQUIPMENT, 909 + equipment_index_offset, ItemClassification.useful, 1, 2, 3, 0, 0, 4, 1, 0), # Scythe IDs 6, 99, [208], [163], [294], 293, [295], 414, 415, 590, [255]
- "Equipment - Progressive Bow": ItemData(EQUIPMENT, 910 + equipment_index_offset, ItemClassification.useful, 1, 1, 2, 1, 0, 5, 4), # Bow IDs 105, [7], 181, [209], [164], [297], [296], [298], [300], 301, 299, [256], [281], 420
- "Equipment - Progressive Staff": ItemData(EQUIPMENT, 911 + equipment_index_offset, ItemClassification.useful, 1, 2, 4, 1, 2, 5, 0, 0), # Staff IDs [5], 62, [15], 100, [210], 188, [211], [165], [423], 422, 424, 425, [257], 290, 429
- "Equipment - Progressive Beating Staff": ItemData(EQUIPMENT, 912 + equipment_index_offset, ItemClassification.useful, 1, 2, 0, 0, 0, 1, 2, 0), # Beating Staff IDs 14, 426, 335, [427], [67], [428]
- "Equipment - Progressive Wand": ItemData(EQUIPMENT, 913 + equipment_index_offset, ItemClassification.useful, 2, 3, 5, 0, 2, 5, 2, 0), # Wand IDs [8], 13, [16], 64, 101, [213], 189, [212], [166], 267, [434], 432, [433], 435, [431], 430, [258], 276, 358
- "Equipment - Progressive Book": ItemData(EQUIPMENT, 914 + equipment_index_offset, ItemClassification.useful, 1, 4, 3, 0, 1, 5, 2, 0), # Book IDs [51], [65], 102, [214], 194, 223, 224, [167], [438], 437, 439, [441], 440, [442], 443, [259] (with shopsanity on i graduated craftwork to advanced)
- "Equipment - Progressive Shield": ItemData(EQUIPMENT, 915 + equipment_index_offset, ItemClassification.useful, 3, 4, 5, 1, 1, 5, 4, 0), # Shield IDs [44], 45, 68, 88, 506, [111], [215], 103, 178, [168], [444], [448], 451, [446], 452, [449], 450, 453, [260], [445], 344, 288, 246
- "Equipment - Progressive Heavy Head": ItemData(EQUIPMENT, 916 + equipment_index_offset, ItemClassification.useful, 1, 3, 4, 0, 3, 5, 2, 0), # Heavy Head IDs [25], [26], 74, [69], 508, [106], [128], 125, 132, [169], [465], [468], 464, [466], 470, 467, [261], 292
- "Equipment - Progressive Heavy Body": ItemData(EQUIPMENT, 917 + equipment_index_offset, ItemClassification.useful, 1, 4, 14, 1, 2, 5, 2, 0), # Heavy Body IDs [18], 28, [29], 84, 510, [107], [127], 126, 43, [170], [455], [460], 456, 462, 461, 130, [457], 458, [262], 131, 463, 533, 534, 536, 535, 538, 537, 557, 548
- "Equipment - Progressive Medium Head": ItemData(EQUIPMENT, 918 + equipment_index_offset, ItemClassification.useful, 2, 4, 2, 1, 2, 5, 3, 0), # Medium Head IDs [24], [27], 75, 30, 512, [219], 195, [220], [171], [483], 233, [484], 485, 481, [486], [263], 345, 482, [487]
- "Equipment - Progressive Medium Body": ItemData(EQUIPMENT, 919 + equipment_index_offset, ItemClassification.useful, 1, 4, 12, 0, 2, 6, 3, 0), # Medium Body IDs [17], [35], [36], 514, [217], [218], 268, 229, 176, [172], [472], 57, [474], 473, 477, [478], 479, [264], [493], 347, 539, 540, 541, 542, 543, 544, 545, 546 (with shopsanity on i graduated craftwork to advanced)
- "Equipment - Progressive Light Head": ItemData(EQUIPMENT, 920 + equipment_index_offset, ItemClassification.useful, 2, 3, 6, 2, 2, 5, 3, 0), # Light Head IDs [31], 32, 76, [33], 516, [110], 133, [109], 122, [173], [341], [340], [353], 342, 356, 34, [343], 348, 52, [265], 531, 271, 354
- "Equipment - Progressive Light Body": ItemData(EQUIPMENT, 921 + equipment_index_offset, ItemClassification.useful, 2, 3, 12, 3, 2, 5, 3, 0), # Light Body IDs [19], [20], 21, 81, 518, [216], 134, [108], 230, [174], [124], [123], [359], 357, [325], 360, 324, 349, [266], 337, 273, 319, 547, 549, 550, 551, 552, 553, 554, 555
-
- #Maps
- #Beginner
- SPAWNING_MEADOWS_MAP: ItemData(MAP, 73 + item_index_offset, ItemClassification.useful),
- DELENDE_MAP: ItemData(MAP, 74 + item_index_offset, ItemClassification.useful),
- BASEMENT_MAP: ItemData(MAP, 213 + item_index_offset, ItemClassification.useful),
- MERCURY_SHRINE_MAP: ItemData(MAP, 87 + item_index_offset, ItemClassification.useful),
- SOILED_DEN_MAP: ItemData(MAP, 79 + item_index_offset, ItemClassification.useful),
- THE_PALE_GROTTO_MAP: ItemData(MAP, 75 + item_index_offset, ItemClassification.useful),
- SEASIDE_CLIFFS_MAP: ItemData(MAP, 76 + item_index_offset, ItemClassification.useful),
- DRAFT_SHAFT_CONDUIT_MAP: ItemData(MAP, 77 + item_index_offset, ItemClassification.useful),
- YAMAGAWA_MA_MAP: ItemData(MAP, 80 + item_index_offset, ItemClassification.useful),
- PROVING_MEADOWS_MAP: ItemData(MAP, 78 + item_index_offset, ItemClassification.useful),
- TRIAL_CAVES_MAP: ItemData(MAP, 214 + item_index_offset, ItemClassification.useful),
- SKUMPARADISE_MAP: ItemData(MAP, 82 + item_index_offset, ItemClassification.useful),
- #Advanced
- CAPITAL_COURTYARD_MAP: ItemData(MAP, 83 + item_index_offset, ItemClassification.useful, 0, 1),
- CAPITAL_SEQUOIA_MAP: ItemData(MAP, 84 + item_index_offset, ItemClassification.useful, 0, 1),
- JOJO_SEWERS_MAP: ItemData(MAP, 85 + item_index_offset, ItemClassification.useful, 0, 1),
- BOOMER_SOCIETY_MAP: ItemData(MAP, 89 + item_index_offset, ItemClassification.useful, 0, 1),
- ROLLING_QUINTAR_FIELDS_MAP: ItemData(MAP, 90 + item_index_offset, ItemClassification.useful, 0, 1),
- QUINTAR_NEST_MAP: ItemData(MAP, 92 + item_index_offset, ItemClassification.useful, 0, 1),
- QUINTAR_SANCTUM_MAP: ItemData(MAP, 120 + item_index_offset, ItemClassification.useful, 0, 1),
- CAPITAL_JAIL_MAP: ItemData(MAP, 94 + item_index_offset, ItemClassification.useful, 0, 1),
- CAPITAL_PIPELINE_MAP: ItemData(MAP, 170 + item_index_offset, ItemClassification.useful, 0, 1),
- COBBLESTONE_CRAG_MAP: ItemData(MAP, 96 + item_index_offset, ItemClassification.useful, 0, 1),
- OKIMOTO_NS_MAP: ItemData(MAP, 98 + item_index_offset, ItemClassification.useful, 0, 1),
- GREENSHIRE_REPRISE_MAP: ItemData(MAP, 86 + item_index_offset, ItemClassification.useful, 0, 1),
- SALMON_PASS_MAP: ItemData(MAP, 99 + item_index_offset, ItemClassification.useful, 0, 1),
- SALMON_RIVER_MAP: ItemData(MAP, 100 + item_index_offset, ItemClassification.useful, 0, 1),
- POSEIDON_SHRINE_MAP: ItemData(MAP, 101 + item_index_offset, ItemClassification.useful, 0, 1),
- RIVER_CATS_EGO_MAP: ItemData(MAP, 217 + item_index_offset, ItemClassification.useful, 0, 1),
- POKO_POKO_DESERT_MAP: ItemData(MAP, 103 + item_index_offset, ItemClassification.useful, 0, 1),
- SARA_SARA_BAZAAR_MAP: ItemData(MAP, 104 + item_index_offset, ItemClassification.useful, 0, 1),
- SARA_SARA_BEACH_MAP: ItemData(MAP, 105 + item_index_offset, ItemClassification.useful, 0, 1),
- ANCIENT_RESERVOIR_MAP: ItemData(MAP, 106 + item_index_offset, ItemClassification.useful, 0, 1),
- SALMON_BAY_MAP: ItemData(MAP, 128 + item_index_offset, ItemClassification.useful, 0, 1),
- OVERPASS_MAP: ItemData(MAP, 215 + item_index_offset, ItemClassification.useful), #in Beginner bc of Yamagawa (and Regionsanity expects you to have it unlocked at the start)
- UNDERPASS_MAP: ItemData(MAP, 216 + item_index_offset, ItemClassification.useful), #in Beginner bc of Delende (and Regionsanity expects you to have it unlocked at the start)
- #Expert
- THE_OPEN_SEA_MAP: ItemData(MAP, 198 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- SHOUDU_PROVINCE_MAP: ItemData(MAP, 107 + item_index_offset, ItemClassification.useful, 0, 1), #in Advanced for Shoudu Waterfront
- THE_UNDERCITY_MAP: ItemData(MAP, 108 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- GANYMEDE_SHRINE_MAP: ItemData(MAP, 117 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- BEAURIOR_VOLCANO_MAP: ItemData(MAP, 109 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- BEAURIOR_ROCK_MAP: ItemData(MAP, 110 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- LAKE_DELENDE_MAP: ItemData(MAP, 121 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- QUINTAR_RESERVE_MAP: ItemData(MAP, 119 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- DIONE_SHRINE_MAP: ItemData(MAP, 199 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- FLYERS_LOOKOUT_MAP: ItemData(MAP, 223 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- QUINTAR_MAUSOLEUM_MAP: ItemData(MAP, 211 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- EASTERN_CHASM_MAP: ItemData(MAP, 219 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- TALL_TALL_HEIGHTS_MAP: ItemData(MAP, 112 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- NORTHERN_CAVE_MAP: ItemData(MAP, 194 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- LANDS_END_MAP: ItemData(MAP, 130 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- SLIP_GLIDE_RIDE_MAP: ItemData(MAP, 113 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- SEQUOIA_ATHENAEUM_MAP: ItemData(MAP, 220 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- NORTHERN_STRETCH_MAP: ItemData(MAP, 218 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- CASTLE_RAMPARTS_MAP: ItemData(MAP, 127 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- THE_CHALICE_OF_TAR_MAP: ItemData(MAP, 221 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- FLYERS_CRAG_MAP: ItemData(MAP, 222 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- JIDAMBA_TANGLE_MAP: ItemData(MAP, 122 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- JIDAMBA_EACLANEYA_MAP: ItemData(MAP, 123 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- THE_DEEP_SEA_MAP: ItemData(MAP, 124 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- NEPTUNE_SHRINE_MAP: ItemData(MAP, 206 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- JADE_CAVERN_MAP: ItemData(MAP, 228 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- CONTINENTAL_TRAM_MAP: ItemData(MAP, 126 + item_index_offset, ItemClassification.useful, 0, 0, 1),
- #End-Game
- ANCIENT_LABYRINTH_MAP: ItemData(MAP, 210 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
- THE_SEQUOIA_MAP: ItemData(MAP, 111 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
- THE_DEPTHS_MAP: ItemData(MAP, 195 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
- CASTLE_SEQUOIA_MAP: ItemData(MAP, 209 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
- THE_OLD_WORLD_MAP: ItemData(MAP, 254 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1), #if player is on True Astley goal, get_item_pool in init adds this if you didn't pick to include End-Game Regions
- THE_NEW_WORLD_MAP: ItemData(MAP, 125 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1), #if player is on Astley/True Astley goal, get_item_pool in init adds this if you didn't pick to include End-Game Regions
-
- # Teleport Shards
- "Item - Gaea Shard": ItemData(TELEPORT_SHARD, 22 + item_index_offset, ItemClassification.useful, 0),
- "Item - Mercury Shard": ItemData(TELEPORT_SHARD, 12 + item_index_offset, ItemClassification.useful, 0),
- "Item - Poseidon Shard": ItemData(TELEPORT_SHARD, 56 + item_index_offset, ItemClassification.useful, 0),
- "Item - Mars Shard": ItemData(TELEPORT_SHARD, 58 + item_index_offset, ItemClassification.useful, 0),
- "Item - Ganymede Shard": ItemData(TELEPORT_SHARD, 62 + item_index_offset, ItemClassification.useful, 0),
- "Item - Triton Shard": ItemData(TELEPORT_SHARD, 63 + item_index_offset, ItemClassification.useful, 0),
- "Item - Callisto Shard": ItemData(TELEPORT_SHARD, 154 + item_index_offset, ItemClassification.useful, 0),
- "Item - Europa Shard": ItemData(TELEPORT_SHARD, 61 + item_index_offset, ItemClassification.useful, 0),
- "Item - Dione Shard": ItemData(TELEPORT_SHARD, 165 + item_index_offset, ItemClassification.useful, 0),
- "Item - Neptune Shard": ItemData(TELEPORT_SHARD, 207 + item_index_offset, ItemClassification.useful, 0),
- "Item - New World Shard": ItemData(TELEPORT_SHARD, 139 + item_index_offset, ItemClassification.useful, 0),
- "Item - Old World Shard": ItemData(TELEPORT_SHARD, 252 + item_index_offset, ItemClassification.useful, 0),
-
- #Seeds
- "Item - Sketchy Seed": ItemData(ITEM, 178 + item_index_offset, ItemClassification.filler, 0),
- "Item - Tuber Seed": ItemData(ITEM, 179 + item_index_offset, ItemClassification.filler, 0),
- "Item - Spore Seed": ItemData(ITEM, 180 + item_index_offset, ItemClassification.filler, 0),
- "Item - Gourmet Seed": ItemData(ITEM, 181 + item_index_offset, ItemClassification.filler, 0),
- "Item - Fetish Seed": ItemData(ITEM, 182 + item_index_offset, ItemClassification.filler, 0),
- "Item - Tear Seed": ItemData(ITEM, 187 + item_index_offset, ItemClassification.filler, 0),
- "Item - Colony Seed": ItemData(ITEM, 188 + item_index_offset, ItemClassification.filler, 0),
- "Item - Watery Seed": ItemData(ITEM, 189 + item_index_offset, ItemClassification.filler, 0),
- "Item - Bloody Seed": ItemData(ITEM, 190 + item_index_offset, ItemClassification.filler, 0),
- "Item - Dark Seed": ItemData(ITEM, 183 + item_index_offset, ItemClassification.filler, 0),
-
- #Seals
- "Equipment - Warrior Seal": ItemData(EQUIPMENT, 564 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Monk Seal": ItemData(EQUIPMENT, 565 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Rogue Seal": ItemData(EQUIPMENT, 566 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Cleric Seal": ItemData(EQUIPMENT, 567 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Wizard Seal": ItemData(EQUIPMENT, 568 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Warlock Seal": ItemData(EQUIPMENT, 569 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Fencer Seal": ItemData(EQUIPMENT, 570 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Shaman Seal": ItemData(EQUIPMENT, 571 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Scholar Seal": ItemData(EQUIPMENT, 572 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Aegis Seal": ItemData(EQUIPMENT, 573 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Hunter Seal": ItemData(EQUIPMENT, 574 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Chemist Seal": ItemData(EQUIPMENT, 575 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Reaper Seal": ItemData(EQUIPMENT, 576 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Ninja Seal": ItemData(EQUIPMENT, 577 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Nomad Seal": ItemData(EQUIPMENT, 578 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Dervish Seal": ItemData(EQUIPMENT, 579 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Beatsmith Seal": ItemData(EQUIPMENT, 580 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Samurai Seal": ItemData(EQUIPMENT, 581 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Assassin Seal": ItemData(EQUIPMENT, 582 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Valkyrie Seal": ItemData(EQUIPMENT, 583 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Weaver Seal": ItemData(EQUIPMENT, 584 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Summoner Seal": ItemData(EQUIPMENT, 585 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Beastmaster Seal": ItemData(EQUIPMENT, 586 + equipment_index_offset, ItemClassification.filler, 0),
- "Equipment - Mimic Seal": ItemData(EQUIPMENT, 587 + equipment_index_offset, ItemClassification.filler, 0),
-
- #Quintar Raising
- "Item - Quintar Grass": ItemData(ITEM, 157 + item_index_offset, ItemClassification.filler, 0),
- "Item - Quintar Wheat": ItemData(ITEM, 202 + item_index_offset, ItemClassification.filler, 0),
- "Item - Quintar Berries": ItemData(ITEM, 203 + item_index_offset, ItemClassification.filler, 0),
- "Item - Quintar Cheese": ItemData(ITEM, 204 + item_index_offset, ItemClassification.filler, 0),
- "Item - Quintar Cookie": ItemData(ITEM, 205 + item_index_offset, ItemClassification.filler, 0),
- "Item - Name Tag": ItemData(ITEM, 200 + item_index_offset, ItemClassification.filler, 0),
- "Item - Incubator": ItemData(ITEM, 201 + item_index_offset, ItemClassification.filler, 0),
-
- #Currency
- #"Currency": ItemData("Currency", 0 + index_offset, ItemClassification.filler),
-
- #Summons
- SHAKU_SUMMON: ItemData(SUMMON, 223 + summon_index_offset, ItemClassification.useful),
- PAMOA_SUMMON: ItemData(SUMMON, 224 + summon_index_offset, ItemClassification.useful),
- GUABA_SUMMON: ItemData(SUMMON, 225 + summon_index_offset, ItemClassification.useful),
- NILTSI_SUMMON: ItemData(SUMMON, 226 + summon_index_offset, ItemClassification.useful),
- IOSKE_SUMMON: ItemData(SUMMON, 227 + summon_index_offset, ItemClassification.useful),
- COYOTE_SUMMON: ItemData(SUMMON, 228 + summon_index_offset, ItemClassification.useful),
- "Summon - Pinga": ItemData(SUMMON, 230 + summon_index_offset, ItemClassification.useful, 0), #(0 in pool bc you start with Pinga as a summoner)
- TIRA_SUMMON: ItemData(SUMMON, 231 + summon_index_offset, ItemClassification.useful),
- JUSES_SUMMON: ItemData(SUMMON, 232 + summon_index_offset, ItemClassification.useful),
- PAH_SUMMON: ItemData(SUMMON, 234 + summon_index_offset, ItemClassification.useful),
-
- #Monster Abilities for Scholar
- ROOST: ItemData(SCHOLAR_ABILITY, 25 + scholar_index_offset, ItemClassification.useful, 0, 1),
- LUCKY_DICE: ItemData(SCHOLAR_ABILITY, 70 + scholar_index_offset, ItemClassification.useful, 0, 1),
- SUN_BATH: ItemData(SCHOLAR_ABILITY, 101 + scholar_index_offset, ItemClassification.useful, 0, 1),
- SLEEP_AURA: ItemData(SCHOLAR_ABILITY, 186 + scholar_index_offset, ItemClassification.useful, 0, 1),
- REGENERATE: ItemData(SCHOLAR_ABILITY, 197 + scholar_index_offset, ItemClassification.useful, 0, 1),
- REVERSE_POLARITY: ItemData(SCHOLAR_ABILITY, 198 + scholar_index_offset, ItemClassification.progression), #left in pool so you can merc Gran
- BARRIER: ItemData(SCHOLAR_ABILITY, 199 + scholar_index_offset, ItemClassification.useful, 0, 1),
- MP_SICKLE: ItemData(SCHOLAR_ABILITY, 200 + scholar_index_offset, ItemClassification.useful, 0, 1),
- ADRENALINE: ItemData(SCHOLAR_ABILITY, 202 + scholar_index_offset, ItemClassification.useful, 0, 1),
- FIRE_BREATH: ItemData(SCHOLAR_ABILITY, 205 + scholar_index_offset, ItemClassification.useful, 0, 1),
- EXPLODE: ItemData(SCHOLAR_ABILITY, 206 + scholar_index_offset, ItemClassification.useful, 0, 1),
- WHIRLWIND: ItemData(SCHOLAR_ABILITY, 207 + scholar_index_offset, ItemClassification.useful, 0, 1),
- ATMOSHEAR: ItemData(SCHOLAR_ABILITY, 213 + scholar_index_offset, ItemClassification.useful, 0, 1),
- BUILD_LIFE: ItemData(SCHOLAR_ABILITY, 245 + scholar_index_offset, ItemClassification.useful, 0, 1),
- AERO: ItemData(SCHOLAR_ABILITY, 264 + scholar_index_offset, ItemClassification.useful, 0, 1),
- INSULT: ItemData(SCHOLAR_ABILITY, 363 + scholar_index_offset, ItemClassification.useful, 0, 1),
- INFUSION: ItemData(SCHOLAR_ABILITY, 364 + scholar_index_offset, ItemClassification.useful, 0, 1),
- OVERlOAD: ItemData(SCHOLAR_ABILITY, 365 + scholar_index_offset, ItemClassification.useful, 0, 1),
- REFLECTION: ItemData(SCHOLAR_ABILITY, 366 + scholar_index_offset, ItemClassification.useful, 0, 1),
- LIFEGIVER: ItemData(SCHOLAR_ABILITY, 376 + scholar_index_offset, ItemClassification.useful, 0, 1),
-
- #Traps
- "Trap - Dialog Trap": ItemData(TRAP, 1 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Crag Demon Trap": ItemData(TRAP, 2 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Clothes Fall Off Trap": ItemData(TRAP, 3 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Oregon Trap": ItemData(TRAP, 4 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Teleport Trap": ItemData(TRAP, 5 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Moon Jump Trap": ItemData(TRAP, 6 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Snail Jump Trap": ItemData(TRAP, 7 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Dismount Trap": ItemData(TRAP, 8 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Dunk Tank Trap": ItemData(TRAP, 9 + trap_index_offset, ItemClassification.trap, 0),
- "Trap - Trap Pack Trap": ItemData(TRAP, 10 + trap_index_offset, ItemClassification.trap, 0),
-}
-
-progressive_equipment: Tuple[str, ...] = (
- "Equipment - Progressive 1H Sword",
- "Equipment - Progressive 2H Sword",
- "Equipment - Progressive 1H Axe",
- "Equipment - Progressive 2H Axe",
- "Equipment - Progressive Dagger",
- "Equipment - Progressive Ninja Dagger",
- "Equipment - Progressive Rapier",
- "Equipment - Progressive Katana",
- "Equipment - Progressive Spear",
- "Equipment - Progressive Scythe",
- "Equipment - Progressive Bow",
- "Equipment - Progressive Staff",
- "Equipment - Progressive Beating Staff",
- "Equipment - Progressive Wand",
- "Equipment - Progressive Book",
- "Equipment - Progressive Shield",
- "Equipment - Progressive Heavy Head",
- "Equipment - Progressive Heavy Body",
- "Equipment - Progressive Medium Head",
- "Equipment - Progressive Medium Body",
- "Equipment - Progressive Light Head",
- "Equipment - Progressive Light Body",
- )
-
-non_progressive_equipment: Tuple[str, ...] = (
- #Weapons
- #Swords
- "Equipment - Iron Sword",
- "Equipment - Contract",
- "Equipment - Help the Prince",
- "Equipment - Craftwork Sword",
- "Equipment - Broadsword",
- "Equipment - Sharp Sword",
- #"Equipment - Silver Sword",
- "Equipment - Boomer Sword",
- #"Equipment - Digested Sword",
- "Equipment - Cutlass",
- "Equipment - Cold Touch",
- #"Equipment - Burning Blade",
- #"Equipment - Gold Sword",
- "Equipment - Bloodbind",
- "Equipment - Temporal Blade",
- #"Equipment - Hydra Edge",
- "Equipment - Defender",
- "Equipment - Conquest",
- "Equipment - Flame Sword",
- "Equipment - Rune Sword",
- #"Equipment - Auduril",
- #"Equipment - Training Sword",
- #"Equipment - Life Line",
- "Equipment - Soul Keeper",
- "Equipment - Crabs Claw",
- "Equipment - Kings Guard",
- #"Equipment - Diamond Sword",
- #"Equipment - Balrog",
- "Equipment - Oily Sword",
-
- #Axes
- "Equipment - Craftwork Axe",
- "Equipment - Cleaver",
- "Equipment - Hunting Axe",
- #"Equipment - Silver Axe",
- "Equipment - Hatchet",
- #"Equipment - Axe of Light",
- #"Equipment - Gold Axe",
- "Equipment - Gaia Axe",
- #"Equipment - Ancient Axe",
- #"Equipment - Master Bigaxe",
- "Equipment - Aphotic Edge",
- #"Equipment - Diamond Axe",
- "Equipment - Decapitator",
- "Equipment - Ragebringer",
-
- #Daggers
- "Equipment - Stabbers",
- "Equipment - Poisonkiss",
- "Equipment - Craftwork Dagger",
- "Equipment - Tanto",
- "Equipment - Butterfly",
- "Equipment - Ambush Knife",
- #"Equipment - Silver Dagger",
- "Equipment - Parry Knife",
- "Equipment - Butter Cutter",
- "Equipment - Soul Kris",
- #"Equipment - Gouger",
- "Equipment - Cinquedea",
- #"Equipment - Gold Dagger",
- #"Equipment - Kowakizashi",
- #"Equipment - Bone Knife",
- "Equipment - Flamespike",
- "Equipment - Sange",
- "Equipment - Yasha",
- #"Equipment - Legend Spike",
- #"Equipment - Eclipse",
- #"Equipment - Mage Masher",
- "Equipment - Mages Pike",
- #"Equipment - Diamond Dagger",
-
- #Rapiers
- "Equipment - Toothpick",
- "Equipment - Craftwork Rapier",
- "Equipment - Fish Skewer",
- #"Equipment - Silver Rapier",
- "Equipment - Dueller",
- "Equipment - Fleuret",
- #"Equipment - Gold Rapier",
- "Equipment - Windsong",
- "Equipment - Nightingale",
- "Equipment - Chartreuse",
- "Equipment - Murgleys",
- #"Equipment - Diamond Rapier",
-
- #Katanas
- "Equipment - Craftwork Katana",
- "Equipment - Tachi",
- #"Equipment - Silver Katana",
- #"Equipment - Gold Katana",
- #"Equipment - Hokuken",
- #"Equipment - Ichimonji",
- "Equipment - Muramasa",
- #"Equipment - Diamond Katana",
-
- #Spears
- "Equipment - Craftwork Spear",
- "Equipment - Skewer",
- "Equipment - Prodder",
- #"Equipment - Silver Spear",
- "Equipment - Trident",
- "Equipment - Halberd",
- #"Equipment - Gold Spear",
- "Equipment - Radiance",
- "Equipment - Partizan",
- #"Equipment - Incursier",
- "Equipment - Royal Guard",
- #"Equipment - Gungnir",
- #"Equipment - Diamond Spear",
-
- #Scythes
- "Equipment - Battle Scythe",
- "Equipment - Craftwork Scythe",
- #"Equipment - Wind Sickle",
- #"Equipment - Silver Scythe",
- "Equipment - Grim Scythe",
- "Equipment - Frost Reaper",
- #"Equipment - Gold Scythe",
- #"Equipment - Ember Scythe",
- "Equipment - Gravedigger",
- "Equipment - Wind Thresher",
- #"Equipment - Adjudicator",
- #"Equipment - Twilight",
- #"Equipment - Arctic Chill",
- #"Equipment - Diamond Scythe",
-
- #Bows
- "Equipment - Craftwork Bow",
- "Equipment - Hunting Bow",
- #"Equipment - Silver Bow",
- #"Equipment - Habins Bow",
- #"Equipment - Elven Bow",
- #"Equipment - Gold Bow",
- #"Equipment - Spore Shooter",
- "Equipment - Siege Bow",
- "Equipment - Rune Bow",
- #"Equipment - Panakeia",
- "Equipment - Dream Hunter",
- #"Equipment - Diamond Bow",
-
- #Staves
- "Equipment - Cedar Staff",
- "Equipment - Craftwork Staff",
- "Equipment - Bone Smasher",
- "Equipment - Iron Rod",
- "Equipment - Walking Stick",
- #"Equipment - Silver Staff",
- "Equipment - Knockout Stick",
- #"Equipment - Future Sight",
- #"Equipment - Digested Staff",
- "Equipment - Life Jewel",
- #"Equipment - Gold Staff",
- "Equipment - Apprentice",
- "Equipment - Sages Walker",
- #"Equipment - Beats Stick",
- "Equipment - Staff of Balance",
- "Equipment - Judgement",
- #"Equipment - Diamond Staff",
-
- #Wands
- "Equipment - Cedar Wand",
- "Equipment - Torch",
- #"Equipment - Ink Stick",
- "Equipment - Craftwork Wand",
- "Equipment - Static Rod",
- #"Equipment - Silver Wand",
- "Equipment - Storm Rod",
- "Equipment - Cursegiver",
- #"Equipment - Gold Wand",
- "Equipment - Rune Wand",
- "Equipment - Stardust Wand",
- #"Equipment - Aura Focus",
- "Equipment - Paladin Wand",
- #"Equipment - Obelisk",
- "Equipment - Flameseeker",
- #"Equipment - Diamond Wand",
-
- #Books
- "Equipment - Craftwork Pages",
- "Equipment - Gospel",
- "Equipment - Paypirbak",
- "Equipment - Art of War",
- #"Equipment - Silver Pages",
- "Equipment - Blank Pages",
- "Equipment - Tome of Light",
- #"Equipment - Gold Pages",
- "Equipment - Dark Gospel",
- "Equipment - Malifice",
- #"Equipment - Codex",
- #"Equipment - Diamond Pages",
-
- #Armor
- #Shields
- "Equipment - Stout Shield",
- "Equipment - Iron Guard",
- "Equipment - Stalwart Shield",
- "Equipment - Craftwork Shield",
- "Equipment - Lucky Platter",
- "Equipment - Boomer Shield",
- #"Equipment - Silver Shield",
- #"Equipment - Blood Shield",
- "Equipment - The Immovable",
- "Equipment - Mages Platter",
- #"Equipment - Gold Shield",
- "Equipment - Flame Guard",
- "Equipment - Wizards Wall",
- "Equipment - Tower Shield",
- "Equipment - Nomads Guard",
- #"Equipment - Ether Shield",
- "Equipment - Mirror Shield",
- #"Equipment - Diamond Shield",
-
- #Heavy Head
- "Equipment - Storm Helm",
- "Equipment - Craftwork Helm",
- "Equipment - Iron Helm",
- "Equipment - Battle Helm",
- #"Equipment - Silver Helm",
- "Equipment - Horned Helm",
- #"Equipment - Gold Helm",
- "Equipment - Insignia Helm",
- "Equipment - Demon Helm",
- #"Equipment - Guts Busby",
- #"Equipment - Raid Helm",
- "Equipment - Spellsword Helm",
- #"Equipment - Diamond Helm",
-
- #Heavy Body
- "Equipment - Ring Mail",
- "Equipment - Plate of Wolf",
- "Equipment - Craftwork Mail",
- "Equipment - Iron Armor",
- "Equipment - Battleplate",
- #"Equipment - Silver Mail",
- #"Equipment - Plate of Tiger",
- "Equipment - Knights Plate",
- "Equipment - Bone Mail",
- #"Equipment - Gold Mail",
- "Equipment - Sky Armor",
- "Equipment - Plate of Lion",
- "Equipment - Demon Plate",
- #"Equipment - Construct Mail",
- #"Equipment - Guardian Angel",
- "Equipment - Plate of Whale",
- "Equipment - Lunar Mail",
- #"Equipment - Diamond Mail",
- "Equipment - Warrior Mail",
- "Equipment - Warlock Mail",
- "Equipment - Aegis Mail",
- "Equipment - Reaper Mail",
- "Equipment - Samurai Mail",
- "Equipment - Valkyrie Mail",
- "Equipment - Beastmaster Mail",
- "Equipment - Mimic Mail",
-
- #Medium Head
- "Equipment - Storm Cap",
- "Equipment - Headgear",
- "Equipment - Craftwork Cap",
- "Equipment - Spore Blocker",
- #"Equipment - Silver Cap",
- "Equipment - Red Cap",
- "Equipment - Suitor Hat",
- #"Equipment - Gold Cap",
- #"Equipment - Red Hat",
- "Equipment - Pirate Hat",
- "Equipment - Battle Band",
- "Equipment - Captains Hat",
- #"Equipment - Diamond Cap",
-
- #Medium Body
- "Equipment - Craftwork Vest",
- "Equipment - Smelly Gi",
- "Equipment - Training Gi",
- "Equipment - Tuxedo",
- #"Equipment - Silver Vest",
- "Equipment - Red Coat",
- #"Equipment - Bandage Wrap",
- "Equipment - Gaia Vest",
- "Equipment - Brigandine",
- #"Equipment - Gold Vest",
- #"Equipment - Onion Gi",
- "Equipment - Judo Gi",
- "Equipment - Shadow Gi",
- #"Equipment - Rex Vest",
- #"Equipment - Slime Coat",
- #"Equipment - Diamond Vest",
- "Equipment - Monk Vest",
- "Equipment - Rogue Vest",
- "Equipment - Fencer Vest",
- "Equipment - Hunter Vest",
- "Equipment - Ninja Vest",
- "Equipment - Nomad Vest",
- "Equipment - Beatsmith Vest",
- "Equipment - Assassin Vest",
-
- #Light Head
- "Equipment - Cotton Hood",
- "Equipment - Storm Hood",
- "Equipment - Craftwork Crown",
- "Equipment - Circlet",
- "Equipment - Woven Hood",
- #"Equipment - Silver Crown",
- #"Equipment - Fairys Crown",
- #"Equipment - Blood Hat",
- "Equipment - Plague Mask",
- "Equipment - Guard Crown",
- #"Equipment - Gold Crown",
- "Equipment - Bronze Hairpin",
- "Equipment - Ravens Hood",
- "Equipment - Celestial Crown",
- "Equipment - Pointy Hat",
- "Equipment - Vita Crown",
- #"Equipment - Pact Crown",
- "Equipment - Protector",
- #"Equipment - Diamond Crown",
-
- #Light Body
- "Equipment - Mages Robe",
- "Equipment - Swimmers Top",
- "Equipment - Craftwork Robe",
- "Equipment - Dress",
- "Equipment - Woven Shirt",
- #"Equipment - Silver Cape",
- #"Equipment - Cosplay Garb",
- "Equipment - Shelter Dress",
- #"Equipment - Gold Robe",
- "Equipment - Blue Cape",
- "Equipment - Seekers Garb",
- "Equipment - Ravens Cloak",
- "Equipment - Archmage Vest",
- #"Equipment - Saviors Cape",
- "Equipment - Assassins Cloak",
- "Equipment - Stealth Cape",
- #"Equipment - Shell Gown",
- #"Equipment - Diamond Robe",
- "Equipment - Cleric Robe",
- "Equipment - Wizard Robe",
- "Equipment - Shaman Robe",
- "Equipment - Scholar Robe",
- "Equipment - Chemist Robe",
- "Equipment - Dervish Robe",
- "Equipment - Weaver Robe",
- "Equipment - Summoner Robe",
-
- #Accessories aren't replaced by progressive items
-
- #Shop Equipment
- # Weapons
- # Swords
- "Equipment - Short Sword",
- "Equipment - Razor Edge",
- "Equipment - Artisan Sword",
- "Equipment - Longsword",
- "Equipment - Scimitar",
- "Equipment - War Sword",
- "Equipment - Highland Blade",
- "Equipment - Crystal Sword",
- "Equipment - Master Sword",
- "Equipment - Master Bigsword",
-
- # Axes
- "Equipment - Hand Axe",
- "Equipment - Chopper",
- "Equipment - Stone Splitter",
- "Equipment - Broadaxe",
- "Equipment - Artisan Axe",
- "Equipment - War Axe",
- "Equipment - Berserker Axe",
- "Equipment - Master Axe",
- "Equipment - Ancient Axe",
- "Equipment - Master Bigaxe",
-
- # Daggers
- "Equipment - Dirk",
- "Equipment - Fishgutter",
- "Equipment - Shank",
- "Equipment - Kris",
- "Equipment - Rondel",
- "Equipment - Artisan Dagger",
- "Equipment - Janbiya",
- "Equipment - Sai",
- "Equipment - Kodachi",
- "Equipment - Fanged Knife",
- "Equipment - Poignard",
- "Equipment - Master Dagger",
-
- # Rapiers
- "Equipment - Rapier",
- "Equipment - Stinger",
- "Equipment - Estoc",
- "Equipment - Scarlette",
- "Equipment - Artisan Rapier",
- "Equipment - Vulture",
- "Equipment - Falcon Dance",
- "Equipment - Epee",
- "Equipment - Master Rapier",
-
- # Katanas
- "Equipment - Artisan Katana",
- "Equipment - Nansen",
- "Equipment - Mitsutada",
- "Equipment - Hitofuri",
- "Equipment - Kokaiji",
- "Equipment - Tomokirimaru",
- "Equipment - Master Katana",
-
- # Spears
- "Equipment - Short Spear",
- "Equipment - Javelin",
- "Equipment - Artisan Spear",
- "Equipment - Wind Lance",
- "Equipment - Voulge",
- "Equipment - Master Spear",
-
- # Scythes
- "Equipment - War Scythe",
- "Equipment - Artisan Scythe",
- "Equipment - Thresher",
- "Equipment - Great Thresher",
- "Equipment - Master Scythe",
-
- # Bows
- "Equipment - Short Bow",
- "Equipment - Long Bow",
- "Equipment - Artisan Bow",
- "Equipment - Battle Bow",
- "Equipment - Composite Bow",
- "Equipment - Razor Bow",
- "Equipment - War Bow",
- "Equipment - Master Bow",
- "Equipment - Artemis",
-
- # Staves
- "Equipment - Short Staff",
- "Equipment - Gnarled Root",
- "Equipment - Quarterstaff",
- "Equipment - Maplewood",
- "Equipment - Artisan Staff",
- "Equipment - Skullbasher",
- "Equipment - Battle Staff",
- "Equipment - Natures Gift",
- "Equipment - War Staff",
- "Equipment - Master Staff",
-
- # Wands
- "Equipment - Ash Wand",
- "Equipment - Oak Wand",
- "Equipment - Soul Wand",
- "Equipment - Maple Wand",
- "Equipment - Artisan Wand",
- "Equipment - Baton",
- "Equipment - Effigy",
- "Equipment - Sentinel Rod",
- "Equipment - Master Wand",
-
- # Books
- "Equipment - Moby Dick",
- "Equipment - Orylei",
- "Equipment - Encyclopedia",
- "Equipment - Artisan Pages",
- "Equipment - Grimoire",
- "Equipment - Hydrology",
- "Equipment - Divination",
- "Equipment - Master Pages",
-
- # Armor
- # Shields
- "Equipment - Buckler",
- "Equipment - Vanguard",
- "Equipment - Duelling Shield",
- "Equipment - Artisan Shield",
- "Equipment - Cross Shield",
- "Equipment - Brass Cross",
- "Equipment - Cross Guard",
- "Equipment - Bulkwark",
- "Equipment - Master Shield",
- "Equipment - Turtle Shell",
-
- # Heavy Head
- "Equipment - Chain Helm",
- "Equipment - Sturdy Helm",
- "Equipment - Copper Helm",
- "Equipment - Bronze Helm",
- "Equipment - Scale Helm",
- "Equipment - Artisan Helm",
- "Equipment - Orion Barbut",
- "Equipment - Iron Barbut",
- "Equipment - Cross Helm",
- "Equipment - Master Helm",
-
- # Heavy Body
- "Equipment - Breastplate",
- "Equipment - Copper Suit",
- "Equipment - Bronze Suit",
- "Equipment - Scale Mail",
- "Equipment - Artisan Mail",
- "Equipment - Orion Armor",
- "Equipment - Plate Mail",
- "Equipment - Dragon Mail",
- "Equipment - Master Mail",
-
- # Medium Head
- "Equipment - Leather Cap",
- "Equipment - Beret",
- "Equipment - Rugged Hat",
- "Equipment - Vikings Hat",
- "Equipment - Artisan Cap",
- "Equipment - Combat Band",
- "Equipment - Bandana",
- "Equipment - Tall Tall Hat",
- "Equipment - Master Cap",
- "Equipment - Red Headgear",
-
- # Medium Body
- "Equipment - Leather Outfit",
- "Equipment - Studded Armor",
- "Equipment - Leather Mail",
- "Equipment - Chain Vest",
- "Equipment - Combat Vest",
- "Equipment - Artisan Vest",
- "Equipment - Power Vest",
- "Equipment - Drifters Vest",
- "Equipment - Martial Vest",
- "Equipment - Master Vest",
- "Equipment - Quintar Pelt",
-
- # Light Head
- "Equipment - Hemp Hood",
- "Equipment - Holy Hat",
- "Equipment - Silk Hat",
- "Equipment - Holy Miter",
- "Equipment - Artisan Crown",
- "Equipment - Clerics Hood",
- "Equipment - Wizards Hat",
- "Equipment - Quilted Hat",
- "Equipment - Regen Crown",
- "Equipment - Master Crown",
-
- # Light Body
- "Equipment - Hemp Robe",
- "Equipment - Cotton Robe",
- "Equipment - Priest Garb",
- "Equipment - Silk Shirt",
- "Equipment - Artisan Shirt",
- "Equipment - Wizards Robe",
- "Equipment - Clerics Robe",
- "Equipment - Sturdy Cape",
- "Equipment - Winter Cape",
- "Equipment - Master Cape",
-
- #Accessories aren't replaced by progressive items
-)
-
-# shop_accessories: Tuple[str, ...] = (
-# "Equipment - Prayer Beads",
-# "Equipment - Samurais Glove",
-# "Equipment - Casters Ring",
-# "Equipment - Fearsome Ring",
-# "Equipment - Crit Fang",
-# "Equipment - Dancing Shoes",
-# # "Equipment - Bulk Belt",
-# # "Equipment - Poison Talon",
-# # "Equipment - First Strike Mitt",
-# # "Equipment - Menders Ring",
-# # "Equipment - Kitsune Mask",
-# # "Equipment - Pact Ring",
-# # "Equipment - Gusto Fang",
-# # "Equipment - Winter Mitten"
-# )
-
-optional_scholar_abilities: Tuple[str, ...] = (
- ROOST,
- LUCKY_DICE,
- SUN_BATH,
- SLEEP_AURA,
- REGENERATE,
- #"Scholar - Reverse Polarity" leaving this one always in the pool so you can merc Gran
- BARRIER,
- MP_SICKLE,
- ADRENALINE,
- FIRE_BREATH,
- EXPLODE,
- WHIRLWIND,
- ATMOSHEAR,
- BUILD_LIFE,
- AERO,
- INSULT,
- INFUSION,
- OVERlOAD,
- REFLECTION,
- LIFEGIVER
-)
-
-default_starting_job_list: List[str] = [
- WARRIOR_JOB,
- MONK_JOB,
- ROGUE_JOB,
- CLERIC_JOB,
- WIZARD_JOB,
- WARLOCK_JOB,
-]
-
-job_crystal_beginner_dictionary: Dict[str, str] = {
- FENCER_JOB: THE_PALE_GROTTO_DISPLAY_NAME + FENCER_JOB_CRYSTAL_LOCATION,
- SHAMAN_JOB: DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + SHAMAN_JOB_CRYSTAL_LOCATION,
- SCHOLAR_JOB: YAMAGAWA_MA_DISPLAY_NAME + SCHOLAR_JOB_CRYSTAL_LOCATION,
- AEGIS_JOB: SKUMPARADISE_DISPLAY_NAME + AEGIS_JOB_CRYSTAL_LOCATION,
-}
-
-job_crystal_advanced_dictionary: Dict[str, str] = {
- HUNTER_JOB: QUINTAR_NEST_DISPLAY_NAME + HUNTER_JOB_CRYSTAL_LOCATION,
- CHEMIST_JOB: QUINTAR_SANCTUM_DISPLAY_NAME + CHEMIST_JOB_CRYSTAL_LOCATION,
- REAPER_JOB: CAPITAL_JAIL_DISPLAY_NAME + REAPER_JOB_CRYSTAL_LOCATION,
- NINJA_JOB: OKIMOTO_NS_DISPLAY_NAME + NINJA_JOB_CRYSTAL_LOCATION,
- NOMAD_JOB: RIVER_CATS_EGO_AP_REGION + NOMAD_JOB_CRYSTAL_LOCATION,
- DERVISH_JOB: ANCIENT_RESERVOIR_DISPLAY_NAME + DERVISH_JOB_CRYSTAL_LOCATION,
- BEATSMITH_JOB: CAPITAL_SEQUOIA_DISPLAY_NAME + BEATSMITH_JOB_CRYSTAL_LOCATION,
-}
-
-job_crystal_expert_dictionary: Dict[str, str] = {
- SAMURAI_JOB: SHOUDU_PROVINCE_DISPLAY_NAME + SAMURAI_JOB_CRYSTAL_LOCATION,
- ASSASSIN_JOB: THE_UNDERCITY_DISPLAY_NAME + ASSASSIN_JOB_CRYSTAL_LOCATION,
- VALKYRIE_JOB: BEAURIOR_VOLCANO_DISPLAY_NAME + VALKYRIE_JOB_CRYSTAL_LOCATION,
- SUMMONER_JOB: SLIP_GLIDE_RIDE_DISPLAY_NAME + SUMMONER_JOB_CRYSTAL_LOCATION,
- BEASTMASTER_JOB: CASTLE_RAMPARTS_DISPLAY_NAME + BEASTMASTER_JOB_CRYSTAL_LOCATION,
- WEAVER_JOB: JIDAMBA_EACLANEYA_DISPLAY_NAME + WEAVER_JOB_CRYSTAL_LOCATION,
- MIMIC_JOB: THE_CHALICE_OF_TAR_DISPLAY_NAME + MIMIC_JOB_CRYSTAL_LOCATION,
-}
-
-key_rings: Tuple[str, ...] = (
- PRISON_KEY_RING,
- BEAURIOR_KEY_RING,
- SLIP_GLIDE_RIDE_KEY_RING,
- ICE_PUZZLE_KEY_RING,
- JIDAMBA_KEY_RING,
-)
-
-dungeon_keys: Tuple[str, ...] = (
- CELL_KEY,
- SOUTH_WING_KEY,
- EAST_WING_KEY,
- WEST_WING_KEY,
- DARK_WING_KEY,
- SMALL_KEY,
- BEAURIOR_BOSS_KEY,
- RED_DOOR_KEY,
- ICE_PUZZLE_KEY,
- FOLIAGE_KEY,
- CAVE_KEY,
- CANOPY_KEY
-)
-
-singleton_keys: Tuple[str, ...] = (
- GARDENERS_KEY,
- COURTYARD_KEY,
- LUXURY_KEY,
- ROOM_ONE_KEY,
- PYRAMID_KEY,
- TRAM_KEY,
- ICE_CELL_KEY,
- RAMPART_KEY,
- FORGOTTEN_KEY
-)
-
-display_region_name_to_pass_dict: Dict[str, str] = {
- #Beginner
- SPAWNING_MEADOWS_DISPLAY_NAME: SPAWNING_MEADOWS_PASS,
- DELENDE_DISPLAY_NAME: DELENDE_PASS,
- SOILED_DEN_DISPLAY_NAME: SOILED_DEN_PASS,
- THE_PALE_GROTTO_DISPLAY_NAME: THE_PALE_GROTTO_PASS,
- SEASIDE_CLIFFS_DISPLAY_NAME: SEASIDE_CLIFFS_PASS,
- DRAFT_SHAFT_CONDUIT_DISPLAY_NAME: DRAFT_SHAFT_CONDUIT_PASS,
- MERCURY_SHRINE_DISPLAY_NAME: MERCURY_SHRINE_PASS,
- YAMAGAWA_MA_DISPLAY_NAME: YAMAGAWA_MA_PASS,
- PROVING_MEADOWS_DISPLAY_NAME: PROVING_MEADOWS_PASS,
- SKUMPARADISE_DISPLAY_NAME: SKUMPARADISE_PASS,
- #Advanced
- CAPITAL_SEQUOIA_DISPLAY_NAME: CAPITAL_SEQUOIA_PASS,
- JOJO_SEWERS_DISPLAY_NAME: JOJO_SEWERS_PASS,
- BOOMER_SOCIETY_DISPLAY_NAME: BOOMER_SOCIETY_PASS,
- ROLLING_QUINTAR_FIELDS_DISPLAY_NAME: ROLLING_QUINTAR_FIELDS_PASS,
- QUINTAR_NEST_DISPLAY_NAME: QUINTAR_NEST_PASS,
- QUINTAR_SANCTUM_DISPLAY_NAME: QUINTAR_SANCTUM_PASS,
- CAPITAL_JAIL_DISPLAY_NAME: CAPITAL_JAIL_PASS,
- CAPITAL_PIPELINE_DISPLAY_NAME: CAPITAL_PIPELINE_PASS,
- COBBLESTONE_CRAG_DISPLAY_NAME: COBBLESTONE_CRAG_PASS,
- OKIMOTO_NS_DISPLAY_NAME: OKIMOTO_NS_PASS,
- GREENSHIRE_REPRISE_DISPLAY_NAME: GREENSHIRE_REPRISE_PASS,
- SALMON_PASS_DISPLAY_NAME: SALMON_PASS_PASS,
- SALMON_RIVER_DISPLAY_NAME: SALMON_RIVER_PASS,
- SHOUDU_WATERFRONT_DISPLAY_NAME: SHOUDU_WATERFRONT_PASS,
- POKO_POKO_DESERT_DISPLAY_NAME: POKO_POKO_DESERT_PASS,
- SARA_SARA_BAZAAR_DISPLAY_NAME: SARA_SARA_BAZAAR_PASS,
- SARA_SARA_BEACH_EAST_DISPLAY_NAME: SARA_SARA_BEACH_EAST_PASS,
- SARA_SARA_BEACH_WEST_DISPLAY_NAME: SARA_SARA_BEACH_WEST_PASS,
- ANCIENT_RESERVOIR_DISPLAY_NAME: ANCIENT_RESERVOIR_PASS,
- SALMON_BAY_DISPLAY_NAME: SALMON_BAY_PASS,
- #Expert
- THE_OPEN_SEA_DISPLAY_NAME: THE_OPEN_SEA_PASS,
- SHOUDU_PROVINCE_DISPLAY_NAME: SHOUDU_PROVINCE_PASS,
- THE_UNDERCITY_DISPLAY_NAME: THE_UNDERCITY_PASS,
- GANYMEDE_SHRINE_DISPLAY_NAME: GANYMEDE_SHRINE_PASS,
- BEAURIOR_VOLCANO_DISPLAY_NAME: BEAURIOR_VOLCANO_PASS,
- BEAURIOR_ROCK_DISPLAY_NAME: BEAURIOR_ROCK_PASS,
- LAKE_DELENDE_DISPLAY_NAME: LAKE_DELENDE_PASS,
- QUINTAR_RESERVE_DISPLAY_NAME: QUINTAR_RESERVE_PASS,
- DIONE_SHRINE_DISPLAY_NAME: DIONE_SHRINE_PASS,
- QUINTAR_MAUSOLEUM_DISPLAY_NAME: QUINTAR_MAUSOLEUM_PASS,
- EASTERN_CHASM_DISPLAY_NAME: EASTERN_CHASM_PASS,
- TALL_TALL_HEIGHTS_DISPLAY_NAME: TALL_TALL_HEIGHTS_PASS,
- NORTHERN_CAVE_DISPLAY_NAME: NORTHERN_CAVE_PASS,
- LANDS_END_DISPLAY_NAME: LANDS_END_PASS,
- SLIP_GLIDE_RIDE_DISPLAY_NAME: SLIP_GLIDE_RIDE_PASS,
- SEQUOIA_ATHENAEUM_DISPLAY_NAME: SEQUOIA_ATHENAEUM_PASS,
- NORTHERN_STRETCH_DISPLAY_NAME: NORTHERN_STRETCH_PASS,
- CASTLE_RAMPARTS_DISPLAY_NAME: CASTLE_RAMPARTS_PASS,
- THE_CHALICE_OF_TAR_DISPLAY_NAME: THE_CHALICE_OF_TAR_PASS,
- FLYERS_CRAG_DISPLAY_NAME: FLYERS_CRAG_PASS,
- JIDAMBA_TANGLE_DISPLAY_NAME: JIDAMBA_TANGLE_PASS,
- JIDAMBA_EACLANEYA_DISPLAY_NAME: JIDAMBA_EACLANEYA_PASS,
- THE_DEEP_SEA_DISPLAY_NAME: THE_DEEP_SEA_PASS,
- NEPTUNE_SHRINE_DISPLAY_NAME: NEPTUNE_SHRINE_PASS,
- JADE_CAVERN_DISPLAY_NAME: JADE_CAVERN_PASS,
- CONTINENTAL_TRAM_DISPLAY_NAME: CONTINENTAL_TRAM_PASS,
- #End Game
- ANCIENT_LABYRINTH_DISPLAY_NAME: ANCIENT_LABYRINTH_PASS,
- THE_SEQUOIA_DISPLAY_NAME: THE_SEQUOIA_PASS,
- THE_DEPTHS_DISPLAY_NAME: THE_DEPTHS_PASS,
- CASTLE_SEQUOIA_DISPLAY_NAME: CASTLE_SEQUOIA_PASS,
- THE_OLD_WORLD_DISPLAY_NAME: THE_OLD_WORLD_PASS,
- THE_NEW_WORLD_DISPLAY_NAME: THE_NEW_WORLD_PASS,
-}
-
-filler_items: Tuple[str, ...] = (
- "Item - Tonic",
- "Item - Potion",
- "Item - Z-Potion",
- "Item - Tincture",
- "Item - Ether",
- "Item - Zether",
- "Item - Fenix Juice",
- "Item - Fenix Syrup",
- "Item - Nans Stew",
- "Item - Nans Cocoa",
- "Item - Nans Secret Recipe",
- "Item - Nuts",
- "Item - Milk",
- "Item - Sweet Pop Candy",
- "Item - Sour Pop Candy",
- "Item - Bitter Pop Candy",
- "Item - Decent Cod",
- "Item - Fresh Salmon",
- "Item - Scroll",
- # add currency?
-)
-
-def get_item_names_per_category() -> Dict[str, Set[str]]:
- categories: Dict[str, Set[str]] = {}
-
- for name, data in item_table.items():
- if data.category != "Events":
- categories.setdefault(data.category, set()).add(name)
-
- return categories
-
-def get_starting_jobs(world: "CrystalProjectWorld") -> List[str]:
- if world.options.job_rando.value == world.options.job_rando.option_full:
- return get_random_starting_jobs(world, world.options.starting_job_quantity.value)
- else:
- return default_starting_job_list
-
-def get_random_starting_jobs(self, count:int) -> List[str]:
- if self.options.use_mods.value == self.options.use_mods.option_true:
- return self.random.sample(list(self.item_name_groups[JOB]), count)
- else:
- return self.random.sample(list(self.base_game_jobs), count)
-
-def set_jobs_at_default_locations(world: "CrystalProjectWorld", player_name:str) -> Tuple[int, List[str]]:
- job_crystal_dictionary: Dict[str, str] = job_crystal_beginner_dictionary.copy() #if we don't use copy it means updating job_crystal_dictionary messes with the beginner dict too
- jobs_not_to_exclude: List[str] = []
-
- if world.options.included_regions.value == world.options.included_regions.option_advanced:
- job_crystal_dictionary.update(job_crystal_advanced_dictionary)
-
- if (world.options.included_regions.value == world.options.included_regions.option_expert
- or world.options.included_regions.value == world.options.included_regions.option_all):
- job_crystal_dictionary.update(job_crystal_advanced_dictionary)
- job_crystal_dictionary.update(job_crystal_expert_dictionary)
-
- for job_name in job_crystal_dictionary:
- try:
- world.get_location(job_crystal_dictionary[job_name]).place_locked_item(world.create_item(job_name))
- #message = "Placing" + job_name + " at " + job_crystal_dictionary[job_name]
- #world.logger.info(message)
- except KeyError:
- jobs_not_to_exclude.append(job_name)
- message = f"For player {player_name}: the crystal where {job_name} was placed was updated by a mod. It has been forced to be randomized instead of in its default location."
- logging.getLogger().info(message)
-
-
+import logging
+from typing import Dict, Set, Tuple, NamedTuple, Optional, List, TYPE_CHECKING
+from BaseClasses import ItemClassification
+from .constants.item_groups import *
+from .constants.jobs import *
+from .constants.keys import *
+from .constants.key_items import *
+from .constants.maps import *
+from .constants.mounts import *
+from .constants.scholar_abilities import *
+from .constants.summons import *
+from .constants.teleport_stones import *
+from .constants.region_passes import *
+from .constants.display_regions import *
+from .constants.crystal_locations import *
+
+if TYPE_CHECKING:
+ from . import CrystalProjectWorld
+
+class ItemData(NamedTuple):
+ category: str
+ code: int
+ classification: ItemClassification
+ #For maguffins, we use Progression + Deprioritize + SkipBalancing
+ #For progression items that you need a lot to unlock not much, and are therefore unsatisfying, we use Progression + Deprioritize
+ #For really neat progression items that are a BIG DEAL, we use Progression + Useful, just because that comes out a gold color instead of purple when colored text is used.
+ # deprioritize means it won't be picked for a priority location, because it would feel unsatisfying to get i.e. clam from a priority location
+ # skip balancing means that if you used progression balancing to try to force one player's items earlier than another, it will fill those with i.e. non-clams
+ #Amount found in each region type; added together for each set you're including
+ beginnerAmount: Optional[int] = 1
+ advancedAmount: Optional[int] = 0
+ expertAmount: Optional[int] = 0
+ endGameAmount: Optional[int] = 0
+ # Amount found in each region type's shops; added together for each set you're including
+ beginnerShops: Optional[int] = 0
+ advancedShops: Optional[int] = 0
+ expertShops: Optional[int] = 0
+ endGameShops: Optional[int] = 0
+
+class Job(NamedTuple):
+ name: str
+ id: int
+
+#Archipelago does not like it if an item has a code of 0, Crystal project starts its database
+#Ids at 0, so we add an offset to the id of each item and remove that offset in the client
+job_index_offset = 1
+item_index_offset = 101
+equipment_index_offset = 1001
+summon_index_offset = 10001
+scholar_index_offset = 100001
+trap_index_offset = 1000001
+
+item_table: Dict[str, ItemData] = {
+ #Jobs
+ WARRIOR_JOB: ItemData(JOB, 0 + job_index_offset, ItemClassification.progression),
+ MONK_JOB: ItemData(JOB, 5 + job_index_offset, ItemClassification.progression),
+ ROGUE_JOB: ItemData(JOB, 2 + job_index_offset, ItemClassification.progression),
+ CLERIC_JOB: ItemData(JOB, 4 + job_index_offset, ItemClassification.progression),
+ WIZARD_JOB: ItemData(JOB, 3 + job_index_offset, ItemClassification.progression),
+ WARLOCK_JOB: ItemData(JOB, 14 + job_index_offset, ItemClassification.progression),
+ FENCER_JOB: ItemData(JOB, 1 + job_index_offset, ItemClassification.progression),
+ SHAMAN_JOB: ItemData(JOB, 8 + job_index_offset, ItemClassification.progression),
+ SCHOLAR_JOB: ItemData(JOB, 13 + job_index_offset, ItemClassification.progression), #requirement for Grans subbasement
+ AEGIS_JOB: ItemData(JOB, 10 + job_index_offset, ItemClassification.progression),
+ HUNTER_JOB: ItemData(JOB, 7 + job_index_offset, ItemClassification.progression),
+ CHEMIST_JOB: ItemData(JOB, 17 + job_index_offset, ItemClassification.progression),
+ REAPER_JOB: ItemData(JOB, 6 + job_index_offset, ItemClassification.progression),
+ NINJA_JOB: ItemData(JOB, 18 + job_index_offset, ItemClassification.progression),
+ NOMAD_JOB: ItemData(JOB, 12 + job_index_offset, ItemClassification.progression),
+ DERVISH_JOB: ItemData(JOB, 11 + job_index_offset, ItemClassification.progression),
+ BEATSMITH_JOB: ItemData(JOB, 9 + job_index_offset, ItemClassification.progression),
+ SAMURAI_JOB: ItemData(JOB, 20 + job_index_offset, ItemClassification.progression),
+ ASSASSIN_JOB: ItemData(JOB, 19 + job_index_offset, ItemClassification.progression),
+ VALKYRIE_JOB: ItemData(JOB, 15 + job_index_offset, ItemClassification.progression),
+ SUMMONER_JOB: ItemData(JOB, 21 + job_index_offset, ItemClassification.progression), #Required for summon fights; only job checked by NPCs
+ BEASTMASTER_JOB: ItemData(JOB, 23 + job_index_offset, ItemClassification.progression),
+ WEAVER_JOB: ItemData(JOB, 16 + job_index_offset, ItemClassification.progression),
+ MIMIC_JOB: ItemData(JOB, 22 + job_index_offset, ItemClassification.progression),
+
+ #Consumables
+ "Item - Tonic": ItemData(ITEM, 18 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Potion": ItemData(ITEM, 0 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Z-Potion": ItemData(ITEM, 102 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Tincture": ItemData(ITEM, 47 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Ether": ItemData(ITEM, 1 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Zether": ItemData(ITEM, 142 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Fenix Juice": ItemData(ITEM, 2 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Fenix Syrup": ItemData(ITEM, 145 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Nans Stew": ItemData(ITEM, 9 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Nans Cocoa": ItemData(ITEM, 8 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Nans Secret Recipe": ItemData(ITEM, 54 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Nuts": ItemData(ITEM, 14 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Milk": ItemData(ITEM, 20 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Thirsty Lad, Poko Poko Desert, Advanced Regions
+ "Item - Shoudu Stew": ItemData(ITEM, 132 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Foreign Foodie, Sara Sara Bazaar, Advanced Regions
+ "Item - Sweet Pop Candy": ItemData(ITEM, 34 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Sour Pop Candy": ItemData(ITEM, 35 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Bitter Pop Candy": ItemData(ITEM, 171 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Rotten Salmon": ItemData(ITEM, 11 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Fish Merchant, Sara Sara Bazaar, Advanced Regions
+ "Item - Decent Cod": ItemData(ITEM, 38 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Fresh Salmon": ItemData(ITEM, 10 + item_index_offset, ItemClassification.filler, 0), #Turn-in: Fish Merchant, Sara Sara Bazaar, Advanced Regions
+ "Item - Scroll": ItemData(ITEM, 263 + item_index_offset, ItemClassification.filler, 0),
+
+ #Bag upgrades
+ "Item - Tonic Pouch": ItemData(ITEM, 133 + item_index_offset, ItemClassification.useful, 7, 9, 1, 0), #17
+ "Item - Potion Pouch": ItemData(ITEM, 134 + item_index_offset, ItemClassification.useful, 0, 5, 8, 0), #13
+ "Item - Z-Potion Pouch": ItemData(ITEM, 143 + item_index_offset, ItemClassification.useful, 1, 0, 2, 1), #5
+ "Item - Tincture Pouch": ItemData(ITEM, 135 + item_index_offset, ItemClassification.useful, 4, 8, 2, 0), #14
+ "Item - Ether Pouch": ItemData(ITEM, 136 + item_index_offset, ItemClassification.useful, 0, 6, 5, 0), #11
+ "Item - Zether Pouch": ItemData(ITEM, 144 + item_index_offset, ItemClassification.useful, 0, 1, 1, 3), #5
+ "Item - Fenix Juice Pouch": ItemData(ITEM, 137 + item_index_offset, ItemClassification.useful, 1, 1, 0, 0), #2
+ "Item - Fenix Syrup Pouch": ItemData(ITEM, 146 + item_index_offset, ItemClassification.useful, 0, 2, 0, 0), #2
+ "Item - Nuts Sack": ItemData(ITEM, 184 + item_index_offset, ItemClassification.useful, 0, 1), #Capital Sequoia, Advanced Zones
+ "Item - Milk Bag": ItemData(ITEM, 138 + item_index_offset, ItemClassification.useful, 0, 1), #Poko Poko Desert, Advanced Zones
+ "Item - Decent Cod Bag": ItemData(ITEM, 185 + item_index_offset, ItemClassification.useful, 0, 0, 1), #Shoudu Province, Expert Zones
+
+ #Fishing
+ "Item - Flimsy Rod": ItemData(TACKLE, 55 + item_index_offset, ItemClassification.progression, 1),
+ "Item - Tough Rod": ItemData(TACKLE, 150 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ "Item - Super Rod": ItemData(TACKLE, 151 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ "Item - Plug Lure": ItemData(TACKLE, 91 + item_index_offset, ItemClassification.progression, 1),
+ "Item - Fly Lure": ItemData(TACKLE, 149 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ "Item - Jigging Lure": ItemData(TACKLE, 97 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+
+ #Ore
+ "Item - Silver Ore": ItemData(ORE, 3 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Capital Blacksmith, Capital Sequoia, Advanced Regions
+ "Item - Silver Ingot": ItemData(ORE, 67 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Capital Blacksmith, Capital Sequoia, Advanced Regions
+ "Item - Silver Dust": ItemData(ORE, 68 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Capital Blacksmith, Capital Sequoia, Advanced Regions
+ "Item - Gold Ore": ItemData(ORE, 4 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Armorer in Sara Sara Bazaar and Weaponsmith in Shoudu Province, Advanced Regions
+ "Item - Gold Ingot": ItemData(ORE, 69 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Armorer in Sara Sara Bazaar and Weaponsmith in Shoudu Province, Advanced Regions
+ "Item - Gold Dust": ItemData(ORE, 70 + item_index_offset, ItemClassification.useful, 0, 18), #Used by Armorer in Sara Sara Bazaar and Weaponsmith in Shoudu Province, Advanced Regions
+ "Item - Diamond Ore": ItemData(ORE, 5 + item_index_offset, ItemClassification.useful, 0, 0, 18), #Used by Armorer in Tall Tall Heights and Weaponsmith in Jidamba Tangle, Expert Regions
+ "Item - Diamond Ingot": ItemData(ORE, 71 + item_index_offset, ItemClassification.useful, 0, 0, 18), #Used by Armorer in Tall Tall Heights and Weaponsmith in Jidamba Tangle, Expert Regions
+ "Item - Diamond Dust": ItemData(ORE, 72 + item_index_offset, ItemClassification.useful, 0, 0, 18), #Used by Armorer in Tall Tall Heights and Weaponsmith in Jidamba Tangle, Expert Regions
+
+ #Keys
+ GARDENERS_KEY: ItemData(KEY, 31 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Sequoia, Advanced Regions
+ COURTYARD_KEY: ItemData(KEY, 33 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Sequoia (Courtyard), Advanced Regions
+ LUXURY_KEY: ItemData(KEY, 36 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Sequoia, Advanced Regions
+ CELL_KEY: ItemData(KEY, 40 + item_index_offset, ItemClassification.progression, 0, 7), #Turn-in: Capital Jail, Advanced Regions
+ SOUTH_WING_KEY: ItemData(KEY, 41 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
+ EAST_WING_KEY: ItemData(KEY, 42 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
+ WEST_WING_KEY: ItemData(KEY, 43 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
+ DARK_WING_KEY: ItemData(KEY, 44 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Capital Jail, Advanced Regions
+ ROOM_ONE_KEY: ItemData(KEY, 32 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
+ PYRAMID_KEY: ItemData(KEY, 60 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Poko Poko Desert (unlocks Ancient Reservoir), Advanced Regions
+ TRAM_KEY: ItemData(KEY, 95 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Continental Tram (unlocks connection between Continental Tram and Sara Sara Bazaar), Expert Regions
+ SMALL_KEY: ItemData(KEY, 29 + item_index_offset, ItemClassification.progression, 0, 0, 4), #Turn-in: Beaurior Rock, Expert Regions
+ BEAURIOR_BOSS_KEY: ItemData(KEY, 30 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Beaurior Rock, Expert Regions
+ ICE_CELL_KEY: ItemData(KEY, 156 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Northern Cave, Expert Regions
+ RED_DOOR_KEY: ItemData(KEY, 169 + item_index_offset, ItemClassification.progression, 0, 0, 3), #Turn-in: Slip Glide Ride, Expert Regions
+ ICE_PUZZLE_KEY: ItemData(KEY, 160 + item_index_offset, ItemClassification.progression, 0, 0, 6), #Turn-in: Sequoia Athenaeum, Expert Regions
+ FOLIAGE_KEY: ItemData(KEY, 141 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Jidamba Tangle (unlocks Jidamba Eaclaneya), Expert Regions
+ CAVE_KEY: ItemData(KEY, 118 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Jidamba Tangle (unlocks Jidamba Eaclaneya), Expert Regions
+ CANOPY_KEY: ItemData(KEY, 116 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Jidamba Tangle (unlocks Jidamba Eaclaneya), Expert Regions
+ RAMPART_KEY: ItemData(KEY, 175 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Castle Ramparts, Expert Regions
+ FORGOTTEN_KEY: ItemData(KEY, 192 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: The Deep Sea, Expert Regions
+ SKELETON_KEY: ItemData(KEY, 147 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 0, 1), #Everyone's best friend
+ PRISON_KEY_RING: ItemData(KEY, 650 + item_index_offset, ItemClassification.progression, 0, 1),
+ BEAURIOR_KEY_RING: ItemData(KEY, 651 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ ICE_PUZZLE_KEY_RING: ItemData(KEY, 652 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ SLIP_GLIDE_RIDE_KEY_RING: ItemData(KEY, 653 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ JIDAMBA_KEY_RING: ItemData(KEY, 654 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+
+ #Passes
+ "Item - Quintar Pass": ItemData(ITEM, 7 + item_index_offset, ItemClassification.filler, 0), #We don't use this so it's filler to prevent it from being jsonified (now part of Progressive Quintar Flute)
+ PROGRESSIVE_LUXURY_PASS: ItemData(ITEM, 93 + item_index_offset, ItemClassification.progression, 0, 2), #Luxury Pass ID 93; Luxury Pass V2 148; Turn-in: Capital Sequoia, Advanced Regions
+ "Item - Luxury Pass V2": ItemData(ITEM, 148 + item_index_offset, ItemClassification.filler, 0), #We don't use this so it's filler to prevent it from being jsonified (now part of Progressive Luxury Pass)
+ FERRY_PASS: ItemData(ITEM, 37 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Sara Sara Bazaar (unlocks connection to Shoudu Province), Expert Regions
+
+ #Key Items
+ BLACK_SQUIRREL: ItemData(ITEM, 21 + item_index_offset, ItemClassification.progression, 4), #Turn-in: Spawning Meadows, Beginner Regions
+ DOG_BONE: ItemData(ITEM, 6 + item_index_offset, ItemClassification.progression, 3), #Turn-in: Delende, Beginner Regions
+ # Number of clamshells is set dynamically based on your Clamshells in pool variable
+ CLAMSHELL: ItemData(ITEM, 16 + item_index_offset, ItemClassification.progression | ItemClassification.deprioritized | ItemClassification.skip_balancing, 0), #Turn-in: Seaside Cliffs, Beginner Regions
+ DIGESTED_HEAD: ItemData(ITEM, 17 + item_index_offset, ItemClassification.progression, 0, 3), #Turn-in: Capital Sequoia, Advanced Regions
+ LOST_PENGUIN: ItemData(ITEM, 24 + item_index_offset, ItemClassification.progression, 0, 12), #Turn-in: Capital Sequoia, Advanced Regions
+ ELEVATOR_PART: ItemData(ITEM, 224 + item_index_offset, ItemClassification.progression | ItemClassification.deprioritized, 0, 0, 10), #Turn-in: Shoudu Province, Expert Regions
+ UNDERSEA_CRAB: ItemData(ITEM, 212 + item_index_offset, ItemClassification.progression | ItemClassification.deprioritized, 0, 0, 15), #Turn-in: The Deep Sea, Expert Regions
+ WEST_LOOKOUT_TOKEN: ItemData(ITEM, 81 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
+ CENTRAL_LOOKOUT_TOKEN: ItemData(ITEM, 88 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
+ NORTH_LOOKOUT_TOKEN: ItemData(ITEM, 131 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Sara Sara Bazaar, Advanced Regions
+ BABEL_QUINTAR: ItemData(ITEM, 167 + item_index_offset, ItemClassification.progression), #Quintar shop!
+ "Item - Quintar Shedding": ItemData(ITEM, 168 + item_index_offset, ItemClassification.filler, 0), #12
+ CRAG_DEMON_HORN: ItemData(ITEM, 197 + item_index_offset, ItemClassification.progression, 0, 1), #Turn-in: Jojo Sewers, Advanced Regions
+ VERMILLION_BOOK: ItemData(ITEM, 172 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Tall Tall Heights (unlocks Sequoia Athenaeum), Expert Regions
+ VIRIDIAN_BOOK: ItemData(ITEM, 173 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Tall Tall Heights (Sequoia Athenaeum), Expert Regions
+ CERULEAN_BOOK: ItemData(ITEM, 174 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Turn-in: Tall Tall Heights (Sequoia Athenaeum), Expert Regions
+ ANCIENT_TABLET_A: ItemData(ITEM, 161 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ ANCIENT_TABLET_B: ItemData(ITEM, 162 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ ANCIENT_TABLET_C: ItemData(ITEM, 163 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ TREASURE_FINDER: ItemData(ITEM, 196 + item_index_offset, ItemClassification.useful),
+ # Progressive Level is used for all level gating options: Progressive Level Pass, Progressive Level Cap, and Progressive Level Catch-Up
+ PROGRESSIVE_LEVEL: ItemData(ITEM, 500 + item_index_offset, ItemClassification.progression, 0),
+ HOMEPOINT_UNLOCK: ItemData(ITEM, 800 + item_index_offset, ItemClassification.filler, 0),
+ SPECIAL_SHOUDU_STEW: ItemData(ITEM, 229 + item_index_offset, ItemClassification.progression, 0, 1),
+ SPECIAL_MILK: ItemData(ITEM, 230 + item_index_offset, ItemClassification.progression, 0, 1),
+ SPECIAL_FRESH_SALMON: ItemData(ITEM, 231 + item_index_offset, ItemClassification.progression, 0, 1),
+ SPECIAL_ROTTEN_SALMON: ItemData(ITEM, 232 + item_index_offset, ItemClassification.progression, 0, 1),
+ DEITY_EYE: ItemData(ITEM, 176 + item_index_offset, ItemClassification.progression, 0, 0, 0, 4), #Used for true astley win con and fighting Gabriel in Kill Bosses mode
+ STEM_WARD: ItemData(ITEM, 177 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1), #Used for true astley win con and fighting Gabriel in Kill Bosses mode
+ PROOF_OF_MERIT: ItemData(ITEM, 191 + item_index_offset, ItemClassification.filler, 0), #We don't use this so it's filler to prevent it from being jsonified
+
+ #Passes for Regionsanity
+ SPAWNING_MEADOWS_PASS: ItemData(PASS, 801 + item_index_offset, ItemClassification.progression, 1),
+ DELENDE_PASS: ItemData(PASS, 802 + item_index_offset, ItemClassification.progression, 1),
+ SOILED_DEN_PASS: ItemData(PASS, 803 + item_index_offset, ItemClassification.progression, 1),
+ THE_PALE_GROTTO_PASS: ItemData(PASS, 804 + item_index_offset, ItemClassification.progression, 1),
+ SEASIDE_CLIFFS_PASS: ItemData(PASS, 805 + item_index_offset, ItemClassification.progression, 1),
+ DRAFT_SHAFT_CONDUIT_PASS: ItemData(PASS, 806 + item_index_offset, ItemClassification.progression, 1),
+ MERCURY_SHRINE_PASS: ItemData(PASS, 807 + item_index_offset, ItemClassification.progression, 1),
+ YAMAGAWA_MA_PASS: ItemData(PASS, 808 + item_index_offset, ItemClassification.progression, 1),
+ PROVING_MEADOWS_PASS: ItemData(PASS, 809 + item_index_offset, ItemClassification.progression, 1),
+ SKUMPARADISE_PASS: ItemData(PASS, 810 + item_index_offset, ItemClassification.progression, 1),
+ #Advanced
+ CAPITAL_SEQUOIA_PASS: ItemData(PASS, 811 + item_index_offset, ItemClassification.progression, 0, 1),
+ JOJO_SEWERS_PASS: ItemData(PASS, 812 + item_index_offset, ItemClassification.progression, 0, 1),
+ BOOMER_SOCIETY_PASS: ItemData(PASS, 813 + item_index_offset, ItemClassification.progression, 0, 1),
+ ROLLING_QUINTAR_FIELDS_PASS: ItemData(PASS, 814 + item_index_offset, ItemClassification.progression, 0, 1),
+ QUINTAR_NEST_PASS: ItemData(PASS, 815 + item_index_offset, ItemClassification.progression, 0, 1),
+ QUINTAR_SANCTUM_PASS: ItemData(PASS, 816 + item_index_offset, ItemClassification.progression, 0, 1),
+ CAPITAL_JAIL_PASS: ItemData(PASS, 817 + item_index_offset, ItemClassification.progression, 0, 1),
+ CAPITAL_PIPELINE_PASS: ItemData(PASS, 818 + item_index_offset, ItemClassification.progression, 0, 1),
+ COBBLESTONE_CRAG_PASS: ItemData(PASS, 819 + item_index_offset, ItemClassification.progression, 0, 1),
+ OKIMOTO_NS_PASS: ItemData(PASS, 820 + item_index_offset, ItemClassification.progression, 0, 1),
+ GREENSHIRE_REPRISE_PASS: ItemData(PASS, 821 + item_index_offset, ItemClassification.progression, 0, 1),
+ SALMON_PASS_PASS: ItemData(PASS, 822 + item_index_offset, ItemClassification.progression, 0, 1),
+ SALMON_RIVER_PASS: ItemData(PASS, 823 + item_index_offset, ItemClassification.progression, 0, 1),
+ SHOUDU_WATERFRONT_PASS: ItemData(PASS, 824 + item_index_offset, ItemClassification.progression, 0, 1),
+ POKO_POKO_DESERT_PASS: ItemData(PASS, 825 + item_index_offset, ItemClassification.progression, 0, 1),
+ SARA_SARA_BAZAAR_PASS: ItemData(PASS, 826 + item_index_offset, ItemClassification.progression, 0, 1),
+ SARA_SARA_BEACH_EAST_PASS: ItemData(PASS, 827 + item_index_offset, ItemClassification.progression, 0, 1),
+ SARA_SARA_BEACH_WEST_PASS: ItemData(PASS, 828 + item_index_offset, ItemClassification.progression, 0, 1),
+ ANCIENT_RESERVOIR_PASS: ItemData(PASS, 829 + item_index_offset, ItemClassification.progression, 0, 1),
+ #IBEK_CAVE_PASS: ItemData(PASS, 830 + item_index_offset, ItemClassification.progression, 0, 1),
+ SALMON_BAY_PASS: ItemData(PASS, 831 + item_index_offset, ItemClassification.progression, 0, 1),
+ #Expert
+ THE_OPEN_SEA_PASS: ItemData(PASS, 832 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ SHOUDU_PROVINCE_PASS: ItemData(PASS, 833 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ THE_UNDERCITY_PASS: ItemData(PASS, 834 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ GANYMEDE_SHRINE_PASS: ItemData(PASS, 835 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ BEAURIOR_VOLCANO_PASS: ItemData(PASS, 836 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ BEAURIOR_ROCK_PASS: ItemData(PASS, 837 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ LAKE_DELENDE_PASS: ItemData(PASS, 838 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ QUINTAR_RESERVE_PASS: ItemData(PASS, 839 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ DIONE_SHRINE_PASS: ItemData(PASS, 840 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ QUINTAR_MAUSOLEUM_PASS: ItemData(PASS, 841 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ EASTERN_CHASM_PASS: ItemData(PASS, 842 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ TALL_TALL_HEIGHTS_PASS: ItemData(PASS, 843 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ NORTHERN_CAVE_PASS: ItemData(PASS, 844 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ LANDS_END_PASS: ItemData(PASS, 845 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ SLIP_GLIDE_RIDE_PASS: ItemData(PASS, 846 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ SEQUOIA_ATHENAEUM_PASS: ItemData(PASS, 847 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ NORTHERN_STRETCH_PASS: ItemData(PASS, 848 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ CASTLE_RAMPARTS_PASS: ItemData(PASS, 849 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ THE_CHALICE_OF_TAR_PASS: ItemData(PASS, 850 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ FLYERS_CRAG_PASS: ItemData(PASS, 851 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ JIDAMBA_TANGLE_PASS: ItemData(PASS, 852 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ JIDAMBA_EACLANEYA_PASS: ItemData(PASS, 853 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ THE_DEEP_SEA_PASS: ItemData(PASS, 854 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ NEPTUNE_SHRINE_PASS: ItemData(PASS, 855 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ JADE_CAVERN_PASS: ItemData(PASS, 856 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ CONTINENTAL_TRAM_PASS: ItemData(PASS, 857 + item_index_offset, ItemClassification.progression, 0, 0, 1),
+ #End Game
+ ANCIENT_LABYRINTH_PASS: ItemData(PASS, 858 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ THE_SEQUOIA_PASS: ItemData(PASS, 859 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ THE_DEPTHS_PASS: ItemData(PASS, 860 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ CASTLE_SEQUOIA_PASS: ItemData(PASS, 861 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ THE_OLD_WORLD_PASS: ItemData(PASS, 862 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+ THE_NEW_WORLD_PASS: ItemData(PASS, 863 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1),
+
+ #Animal mount summons
+ PROGRESSIVE_QUINTAR_WOODWIND: ItemData(MOUNT, 39 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 3), #Quintar Pass ID 7 & Quintar Flute ID 39 & Quintar Ocarina 115
+ IBEK_BELL: ItemData(MOUNT, 50 + item_index_offset, ItemClassification.progression | ItemClassification.useful),
+ OWL_DRUM: ItemData(MOUNT, 49 + item_index_offset, ItemClassification.progression | ItemClassification.useful),
+ PROGRESSIVE_SALMON_VIOLA: ItemData(MOUNT, 48 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 2), #Salmon Violin ID 48 & Salmon Cello ID 114
+ PROGRESSIVE_MOUNT: ItemData(MOUNT, 700 + item_index_offset, ItemClassification.progression | ItemClassification.useful, 7),
+
+ #Teleport items (shards not included since they are stones but worse)
+ HOME_POINT_STONE: ItemData(TELEPORT_STONE, 19 + item_index_offset, ItemClassification.useful), #Starter pack
+ ARCHIPELAGO_STONE: ItemData(TELEPORT_STONE, 233 + item_index_offset, ItemClassification.useful), #Teleports you to your starting location
+ GAEA_STONE: ItemData(TELEPORT_STONE, 23 + item_index_offset, ItemClassification.progression, 0, 1), #Teleport to Capital Sequoia, Advanced Regions
+ MERCURY_STONE: ItemData(TELEPORT_STONE, 13 + item_index_offset, ItemClassification.progression), #Teleport to Beginner Regions
+ POSEIDON_STONE: ItemData(TELEPORT_STONE, 57 + item_index_offset, ItemClassification.progression, 0, 1), #Teleport to Salmon River, Advanced Regions
+ MARS_STONE: ItemData(TELEPORT_STONE, 59 + item_index_offset, ItemClassification.progression, 0, 1), #Teleport to Poko Poko Desert, Advanced Regions
+ GANYMEDE_STONE: ItemData(TELEPORT_STONE, 65 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to above Shoudu Province, Expert Regions
+ TRITON_STONE: ItemData(TELEPORT_STONE, 66 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to Tall Tall Heights, Expert Regions
+ CALLISTO_STONE: ItemData(TELEPORT_STONE, 155 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to Lands End, Expert Regions
+ EUROPA_STONE: ItemData(TELEPORT_STONE, 64 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to Jidamba Tangle, Expert Regions
+ DIONE_STONE: ItemData(TELEPORT_STONE, 166 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to above Quintar Reserve, Expert Regions
+ NEPTUNE_STONE: ItemData(TELEPORT_STONE, 208 + item_index_offset, ItemClassification.progression, 0, 0, 1), #Teleport to The Deep Sea, Expert Regions
+ NEW_WORLD_STONE: ItemData(TELEPORT_STONE, 140 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1), #End-Game Regions (Astley & True Astley goals hand it to the player directly instead of from the item pool)
+ OLD_WORLD_STONE: ItemData(TELEPORT_STONE, 253 + item_index_offset, ItemClassification.progression, 0, 0, 0, 1), #End-Game Regions (True Astley goal hands it to the player directly instead of from the item pool)
+
+ #Weapons
+ #Swords
+ "Equipment - Short Sword": ItemData(EQUIPMENT, 0 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 1H, Shop, Beginner Zones
+ "Equipment - Iron Sword": ItemData(EQUIPMENT, 11 + equipment_index_offset, ItemClassification.useful), #Tier 1 1H, Delende, Beginner Zones
+ "Equipment - Contract": ItemData(EQUIPMENT, 71 + equipment_index_offset, ItemClassification.useful), #Tier 1 1H, Mercury Shrine, Beginner Zones
+ "Equipment - Help the Prince": ItemData(EQUIPMENT, 89 + equipment_index_offset, ItemClassification.useful), #Tier 1 1H, Trial Caves/Skumparadise, Beginner Zones
+ "Equipment - Craftwork Sword": ItemData(EQUIPMENT, 93 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 1H, Capital Sequoia, Advanced Zones
+ "Equipment - Broadsword": ItemData(EQUIPMENT, 12 + equipment_index_offset, ItemClassification.useful), #Tier 1 2H, Yamagawa M.A., Beginner Zones
+ "Equipment - Sharp Sword": ItemData(EQUIPMENT, 200 + equipment_index_offset, ItemClassification.useful), #Tier 2 1H, Skumparadise, Beginner Zones
+ "Equipment - Razor Edge": ItemData(EQUIPMENT, 199 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 1H, Shop, Advanced Zones
+ "Equipment - Silver Sword": ItemData(EQUIPMENT, 112 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Sword": ItemData(EQUIPMENT, 157 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 1H, Shop, Advanced Zones
+ "Equipment - Longsword": ItemData(EQUIPMENT, 378 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
+ "Equipment - Boomer Sword": ItemData(EQUIPMENT, 177 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 2H, Boomer Society, Advanced Zones
+ "Equipment - Digested Sword": ItemData(EQUIPMENT, 227 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Scimitar": ItemData(EQUIPMENT, 379 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 1H, Shop, Advanced Zones
+ "Equipment - Cutlass": ItemData(EQUIPMENT, 377 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 1H, Shoudu Province, Expert Zones
+ "Equipment - Cold Touch": ItemData(EQUIPMENT, 375 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 1H, Beaurior Rock, Expert Zones
+ "Equipment - Burning Blade": ItemData(EQUIPMENT, 497 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gold Sword": ItemData(EQUIPMENT, 138 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - War Sword": ItemData(EQUIPMENT, 376 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Advanced Zones
+ "Equipment - Bloodbind": ItemData(EQUIPMENT, 197 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 2H, Salmon River, Advanced Zones
+ "Equipment - Temporal Blade": ItemData(EQUIPMENT, 525 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 2H, Beaurior Volcano, Expert Zones
+ "Equipment - Highland Blade": ItemData(EQUIPMENT, 370 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Expert Zones
+ "Equipment - Hydra Edge": ItemData(EQUIPMENT, 371 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Defender": ItemData(EQUIPMENT, 380 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 1H, Lands End, Expert Zones
+ "Equipment - Crystal Sword": ItemData(EQUIPMENT, 374 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 1H, Shop, Expert Zones
+ "Equipment - Conquest": ItemData(EQUIPMENT, 372 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 1H, Castle Ramparts, Expert Zones
+ "Equipment - Flame Sword": ItemData(EQUIPMENT, 381 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 1H, Jidamba Eaclaneya, Expert Zones
+ "Equipment - Master Sword": ItemData(EQUIPMENT, 248 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 1H, Shop, Advanced Zones
+ "Equipment - Rune Sword": ItemData(EQUIPMENT, 382 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 2H, Castle Ramparts, Expert Zones
+ "Equipment - Auduril": ItemData(EQUIPMENT, 270 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Master Bigsword": ItemData(EQUIPMENT, 249 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 2H, Shop, Advanced Zones
+ "Equipment - Training Sword": ItemData(EQUIPMENT, 532 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Life Line": ItemData(EQUIPMENT, 302 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Soul Keeper": ItemData(EQUIPMENT, 303 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 1H, The Deep Sea, Expert Zones
+ "Equipment - Crabs Claw": ItemData(EQUIPMENT, 411 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 1H, The Deep Sea, Expert Zones
+ "Equipment - Kings Guard": ItemData(EQUIPMENT, 316 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 1H, Castle Sequoia, End-Game Zones
+ "Equipment - Diamond Sword": ItemData(EQUIPMENT, 135 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Balrog": ItemData(EQUIPMENT, 369 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Oily Sword": ItemData(EQUIPMENT, 279 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 2H, Castle Sequoia, End-Game Zones
+
+ #Axes
+ "Equipment - Hand Axe": ItemData(EQUIPMENT, 55 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 1H, Shop, Beginner Zones
+ "Equipment - Craftwork Axe": ItemData(EQUIPMENT, 94 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 1H, Capital Sequoia, Advanced Zones
+ "Equipment - Cleaver": ItemData(EQUIPMENT, 2 + equipment_index_offset, ItemClassification.useful), #Tier 1 2H, Spawning Meadows, Beginner Zones
+ "Equipment - Chopper": ItemData(EQUIPMENT, 66 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 2H, Shop, Beginner Zones
+ "Equipment - Hunting Axe": ItemData(EQUIPMENT, 187 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 1H, Rolling Quintar Fields, Advanced Zones
+ "Equipment - Silver Axe": ItemData(EQUIPMENT, 104 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Stone Splitter": ItemData(EQUIPMENT, 201 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
+ "Equipment - Broadaxe": ItemData(EQUIPMENT, 383 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
+ "Equipment - Artisan Axe": ItemData(EQUIPMENT, 158 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 2H, Shop, Advanced Zones
+ "Equipment - Hatchet": ItemData(EQUIPMENT, 386 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 1H, Poko Poko Desert, Advanced Zones
+ "Equipment - Axe of Light": ItemData(EQUIPMENT, 387 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gold Axe": ItemData(EQUIPMENT, 139 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - War Axe": ItemData(EQUIPMENT, 384 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Advanced Zones
+ "Equipment - Berserker Axe": ItemData(EQUIPMENT, 390 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 2H, Shop, Expert Zones
+ "Equipment - Gaia Axe": ItemData(EQUIPMENT, 385 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 2H, Shoudu Province, Expert Zones
+ "Equipment - Master Axe": ItemData(EQUIPMENT, 251 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 1H, Shop, Advanced Zones
+ "Equipment - Ancient Axe": ItemData(EQUIPMENT, 391 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 2H, Shop, Expert Zones
+ "Equipment - Master Bigaxe": ItemData(EQUIPMENT, 250 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 2H, Shop, Advanced Zones
+ "Equipment - Aphotic Edge": ItemData(EQUIPMENT, 388 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 1H, The Sequoia, End-Game Zones
+ "Equipment - Diamond Axe": ItemData(EQUIPMENT, 136 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Decapitator": ItemData(EQUIPMENT, 280 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 2H, Castle Sequoia, End-Game Zones
+ "Equipment - Ragebringer": ItemData(EQUIPMENT, 274 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 2H, Capital Sequoia, Advanced Zones
+
+ #Daggers
+ "Equipment - Dirk": ItemData(EQUIPMENT, 3 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
+ "Equipment - Stabbers": ItemData(EQUIPMENT, 63 + equipment_index_offset, ItemClassification.useful), #Tier 1 Regular, Spawning Meadows, Beginner Zones
+ "Equipment - Fishgutter": ItemData(EQUIPMENT, 77 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
+ "Equipment - Poisonkiss": ItemData(EQUIPMENT, 40 + equipment_index_offset, ItemClassification.useful), #Tier 1 Regular, Pale Grotto, Beginner Zones
+ "Equipment - Shank": ItemData(EQUIPMENT, 60 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
+ "Equipment - Craftwork Dagger": ItemData(EQUIPMENT, 95 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 Regular, Capital Sequoia, Advanced Zones
+ "Equipment - Tanto": ItemData(EQUIPMENT, 192 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Ninja, Okimoto N.S., Advanced Zones
+ "Equipment - Butterfly": ItemData(EQUIPMENT, 203 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Ninja, Okimoto N.S., Advanced Zones
+ "Equipment - Kris": ItemData(EQUIPMENT, 202 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
+ "Equipment - Ambush Knife": ItemData(EQUIPMENT, 184 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Regular, Greenshire Reprise, Advanced Zones
+ "Equipment - Rondel": ItemData(EQUIPMENT, 204 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
+ "Equipment - Silver Dagger": ItemData(EQUIPMENT, 113 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Dagger": ItemData(EQUIPMENT, 159 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
+ "Equipment - Parry Knife": ItemData(EQUIPMENT, 397 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Ninja, Salmon River, Advanced Zones
+ "Equipment - Janbiya": ItemData(EQUIPMENT, 392 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 Ninja, Shop, Advanced Zones
+ "Equipment - Sai": ItemData(EQUIPMENT, 396 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Ninja, Shop, Expert Zones
+ "Equipment - Kodachi": ItemData(EQUIPMENT, 400 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Ninja, Shop, Expert Zones
+ "Equipment - Butter Cutter": ItemData(EQUIPMENT, 198 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Regular, Poko Poko Desert, Advanced Zones
+ "Equipment - Soul Kris": ItemData(EQUIPMENT, 305 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Regular, Shoudu Province, Expert Zones
+ "Equipment - Gouger": ItemData(EQUIPMENT, 61 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Fanged Knife": ItemData(EQUIPMENT, 526 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Regular, Shop, Expert Zones
+ "Equipment - Cinquedea": ItemData(EQUIPMENT, 393 + equipment_index_offset, ItemClassification.useful), #from Delende fisher; Tier 3 Regular, Delende, Beginner Zones
+ "Equipment - Gold Dagger": ItemData(EQUIPMENT, 140 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Kowakizashi": ItemData(EQUIPMENT, 398 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Bone Knife": ItemData(EQUIPMENT, 395 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Poignard": ItemData(EQUIPMENT, 394 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 Regular, Shop, Expert Zones
+ "Equipment - Flamespike": ItemData(EQUIPMENT, 72 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Regular, Jidamba Tangle, Expert Zones
+ "Equipment - Master Dagger": ItemData(EQUIPMENT, 269 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 Regular, Shop, Advanced Zones
+ "Equipment - Sange": ItemData(EQUIPMENT, 317 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Ninja, The Sequoia, End-Game Zones
+ "Equipment - Yasha": ItemData(EQUIPMENT, 318 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Ninja, Shoudu Province, Expert Zones
+ "Equipment - Legend Spike": ItemData(EQUIPMENT, 315 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Eclipse": ItemData(EQUIPMENT, 41 + equipment_index_offset, ItemClassification.useful, 0), #Capital Sequoia shady shop guy
+ "Equipment - Mage Masher": ItemData(EQUIPMENT, 282 + equipment_index_offset, ItemClassification.useful, 0), #Capital Sequoia shady shop guy
+ "Equipment - Mages Pike": ItemData(EQUIPMENT, 306 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Regular, The New World, End-Game Zones
+ "Equipment - Diamond Dagger": ItemData(EQUIPMENT, 137 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Rapiers
+ "Equipment - Rapier": ItemData(EQUIPMENT, 73 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Stinger": ItemData(EQUIPMENT, 1 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Toothpick": ItemData(EQUIPMENT, 42 + equipment_index_offset, ItemClassification.useful), #Tier 1, Pale Grotto, Beginner Zones
+ "Equipment - Craftwork Rapier": ItemData(EQUIPMENT, 96 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Estoc": ItemData(EQUIPMENT, 207 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Scarlette": ItemData(EQUIPMENT, 206 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Fish Skewer": ItemData(EQUIPMENT, 175 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Sara Sara Bazaar, Advanced Zones
+ "Equipment - Silver Rapier": ItemData(EQUIPMENT, 114 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Rapier": ItemData(EQUIPMENT, 160 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Dueller": ItemData(EQUIPMENT, 10 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Poko Poko Desert, Advanced Zones
+ "Equipment - Vulture": ItemData(EQUIPMENT, 402 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Falcon Dance": ItemData(EQUIPMENT, 408 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Fleuret": ItemData(EQUIPMENT, 404 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Gold Rapier": ItemData(EQUIPMENT, 141 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Epee": ItemData(EQUIPMENT, 405 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Windsong": ItemData(EQUIPMENT, 407 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, The Chalice of Tar, Expert Zones
+ "Equipment - Master Rapier": ItemData(EQUIPMENT, 252 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Nightingale": ItemData(EQUIPMENT, 401 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
+ "Equipment - Chartreuse": ItemData(EQUIPMENT, 403 + equipment_index_offset, ItemClassification.useful), #Tier 5, Delende, Beginner Zones
+ "Equipment - Murgleys": ItemData(EQUIPMENT, 406 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5, The Open Sea, Expert Zones
+ "Equipment - Diamond Rapier": ItemData(EQUIPMENT, 142 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Katanas
+ "Equipment - Craftwork Katana": ItemData(EQUIPMENT, 97 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a katana as a treat)
+ "Equipment - Tachi": ItemData(EQUIPMENT, 399 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Okimoto N.S., Advanced Zones
+ "Equipment - Silver Katana": ItemData(EQUIPMENT, 115 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Katana": ItemData(EQUIPMENT, 161 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Nansen": ItemData(EQUIPMENT, 363 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Mitsutada": ItemData(EQUIPMENT, 22 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Hitofuri": ItemData(EQUIPMENT, 364 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Kokaiji": ItemData(EQUIPMENT, 23 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Gold Katana": ItemData(EQUIPMENT, 143 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Hokuken": ItemData(EQUIPMENT, 588 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Tomokirimaru": ItemData(EQUIPMENT, 366 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Ichimonji": ItemData(EQUIPMENT, 362 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Master Katana": ItemData(EQUIPMENT, 253 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Muramasa": ItemData(EQUIPMENT, 367 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Shoudu Province, Expert Zones
+ "Equipment - Diamond Katana": ItemData(EQUIPMENT, 144 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Spears
+ "Equipment - Short Spear": ItemData(EQUIPMENT, 4 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 1, Shop, Advanced Zones
+ "Equipment - Craftwork Spear": ItemData(EQUIPMENT, 98 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a spear as a treat)
+ "Equipment - Javelin": ItemData(EQUIPMENT, 205 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Skewer": ItemData(EQUIPMENT, 190 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Cobblestone Crag, Advanced Zones
+ "Equipment - Prodder": ItemData(EQUIPMENT, 183 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Sequoia, Advanced Zones
+ "Equipment - Silver Spear": ItemData(EQUIPMENT, 116 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Spear": ItemData(EQUIPMENT, 162 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Trident": ItemData(EQUIPMENT, 409 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Salmon River, Advanced Zones
+ "Equipment - Wind Lance": ItemData(EQUIPMENT, 410 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Halberd": ItemData(EQUIPMENT, 418 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Rock, Expert Zones
+ "Equipment - Gold Spear": ItemData(EQUIPMENT, 145 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Voulge": ItemData(EQUIPMENT, 419 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Radiance": ItemData(EQUIPMENT, 417 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Northern Cave, Expert Zones
+ "Equipment - Partizan": ItemData(EQUIPMENT, 416 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Master Spear": ItemData(EQUIPMENT, 254 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Incursier": ItemData(EQUIPMENT, 563 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Royal Guard": ItemData(EQUIPMENT, 275 + equipment_index_offset, ItemClassification.useful,0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
+ "Equipment - Gungnir": ItemData(EQUIPMENT, 304 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Diamond Spear": ItemData(EQUIPMENT, 146 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Scythes
+ "Equipment - Battle Scythe": ItemData(EQUIPMENT, 6 + equipment_index_offset, ItemClassification.useful), #Tier 1, Proving Meadows, Beginner Zones
+ "Equipment - Craftwork Scythe": ItemData(EQUIPMENT, 99 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - War Scythe": ItemData(EQUIPMENT, 208 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Wind Sickle": ItemData(EQUIPMENT, 413 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Silver Scythe": ItemData(EQUIPMENT, 117 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Scythe": ItemData(EQUIPMENT, 163 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Thresher": ItemData(EQUIPMENT, 294 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Grim Scythe": ItemData(EQUIPMENT, 293 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Ancient Reservoir, Advanced Zones
+ "Equipment - Great Thresher": ItemData(EQUIPMENT, 295 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Frost Reaper": ItemData(EQUIPMENT, 414 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Tall Tall Heights, Expert Zones
+ "Equipment - Gold Scythe": ItemData(EQUIPMENT, 147 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Ember Scythe": ItemData(EQUIPMENT, 589 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gravedigger": ItemData(EQUIPMENT, 415 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
+ "Equipment - Wind Thresher": ItemData(EQUIPMENT, 590 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Quintar Mausoleum, Expert Zones
+ "Equipment - Master Scythe": ItemData(EQUIPMENT, 255 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Twilight": ItemData(EQUIPMENT, 412 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Adjudicator": ItemData(EQUIPMENT, 245 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Arctic Chill": ItemData(EQUIPMENT, 556 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Diamond Scythe": ItemData(EQUIPMENT, 148 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Bows
+ "Equipment - Craftwork Bow": ItemData(EQUIPMENT, 105 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a bow as a treat)
+ "Equipment - Short Bow": ItemData(EQUIPMENT, 7 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Hunting Bow": ItemData(EQUIPMENT, 181 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Rolling Quintar Fields, Advanced Zones
+ "Equipment - Long Bow": ItemData(EQUIPMENT, 209 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Silver Bow": ItemData(EQUIPMENT, 118 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Bow": ItemData(EQUIPMENT, 164 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Battle Bow": ItemData(EQUIPMENT, 297 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Composite Bow": ItemData(EQUIPMENT, 296 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Habins Bow": ItemData(EQUIPMENT, 222 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Razor Bow": ItemData(EQUIPMENT, 298 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Elven Bow": ItemData(EQUIPMENT, 421 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gold Bow": ItemData(EQUIPMENT, 149 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Spore Shooter": ItemData(EQUIPMENT, 180 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - War Bow": ItemData(EQUIPMENT, 300 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Siege Bow": ItemData(EQUIPMENT, 301 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Rune Bow": ItemData(EQUIPMENT, 299 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Master Bow": ItemData(EQUIPMENT, 256 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Panakeia": ItemData(EQUIPMENT, 530 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artemis": ItemData(EQUIPMENT, 281 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
+ "Equipment - Dream Hunter": ItemData(EQUIPMENT, 420 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
+ "Equipment - Diamond Bow": ItemData(EQUIPMENT, 150 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Staves
+ "Equipment - Short Staff": ItemData(EQUIPMENT, 5 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
+ "Equipment - Cedar Staff": ItemData(EQUIPMENT, 62 + equipment_index_offset, ItemClassification.useful), #Tier 1 Regular, Spawning Meadows, Beginner Zones
+ "Equipment - Gnarled Root": ItemData(EQUIPMENT, 15 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1 Regular, Shop, Beginner Zones
+ "Equipment - Craftwork Staff": ItemData(EQUIPMENT, 100 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1 Regular, Capital Sequoia, Advanced Zones
+ "Equipment - Bone Smasher": ItemData(EQUIPMENT, 14 + equipment_index_offset, ItemClassification.useful), #Tier 1 Beating, Delende, Beginner Zones
+ "Equipment - Iron Rod": ItemData(EQUIPMENT, 426 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Beating, Capital Jail, Advanced Zones
+ "Equipment - Quarterstaff": ItemData(EQUIPMENT, 210 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
+ "Equipment - Walking Stick": ItemData(EQUIPMENT, 188 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Regular, Cobblestone Crag, Advanced Zones
+ "Equipment - Maplewood": ItemData(EQUIPMENT, 211 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
+ "Equipment - Silver Staff": ItemData(EQUIPMENT, 119 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Staff": ItemData(EQUIPMENT, 165 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2 Regular, Shop, Advanced Zones
+ "Equipment - Knockout Stick": ItemData(EQUIPMENT, 335 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Beating, Sara Sara Bazaar, Advanced Zones
+ "Equipment - Skullbasher": ItemData(EQUIPMENT, 427 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 Beating, Shop, Advanced Zones
+ "Equipment - Future Sight": ItemData(EQUIPMENT, 561 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Battle Staff": ItemData(EQUIPMENT, 67 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3 Beating, Shop, Expert Zones
+ "Equipment - Digested Staff": ItemData(EQUIPMENT, 228 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Natures Gift": ItemData(EQUIPMENT, 423 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3 Regular, Shop, Advanced Zones
+ "Equipment - Life Jewel": ItemData(EQUIPMENT, 422 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Regular, Overpass (Dione Shrine), Advanced (Expert) Zones
+ "Equipment - Gold Staff": ItemData(EQUIPMENT, 151 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - War Staff": ItemData(EQUIPMENT, 428 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4 Beating, Shop, Expert Zones
+ "Equipment - Apprentice": ItemData(EQUIPMENT, 424 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Regular, Northern Cave, Expert Zones
+ "Equipment - Sages Walker": ItemData(EQUIPMENT, 425 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Regular, Slip Glide Ride, Expert Zones
+ "Equipment - Master Staff": ItemData(EQUIPMENT, 257 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4 Regular, Shop, Advanced Zones
+ "Equipment - Beats Stick": ItemData(EQUIPMENT, 289 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Staff of Balance": ItemData(EQUIPMENT, 290 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Regular, Jidamba Eaclaneya, Expert Zones
+ "Equipment - Judgement": ItemData(EQUIPMENT, 429 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Regular, Ancient Labyrinth, End-Game Zones
+ "Equipment - Diamond Staff": ItemData(EQUIPMENT, 152 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Wands
+ "Equipment - Ash Wand": ItemData(EQUIPMENT, 8 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Cedar Wand": ItemData(EQUIPMENT, 13 + equipment_index_offset, ItemClassification.useful), #Tier 1, Spawning Meadows, Beginner Zones
+ "Equipment - Oak Wand": ItemData(EQUIPMENT, 16 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Torch": ItemData(EQUIPMENT, 64 + equipment_index_offset, ItemClassification.useful), #Tier 1, Draft Shaft Conduit, Beginner Zones
+ "Equipment - Ink Stick": ItemData(EQUIPMENT, 80 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Craftwork Wand": ItemData(EQUIPMENT, 101 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Soul Wand": ItemData(EQUIPMENT, 213 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Static Rod": ItemData(EQUIPMENT, 189 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Quintar Nest, Advanced Zones
+ "Equipment - Maple Wand": ItemData(EQUIPMENT, 212 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Silver Wand": ItemData(EQUIPMENT, 120 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Wand": ItemData(EQUIPMENT, 166 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Storm Rod": ItemData(EQUIPMENT, 267 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Sara Sara Bazaar, Advanced Zones
+ "Equipment - Baton": ItemData(EQUIPMENT, 434 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Cursegiver": ItemData(EQUIPMENT, 432 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, The Undercity, Expert Zones
+ "Equipment - Effigy": ItemData(EQUIPMENT, 433 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Gold Wand": ItemData(EQUIPMENT, 153 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Rune Wand": ItemData(EQUIPMENT, 435 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Lands End, Expert Zones
+ "Equipment - Sentinel Rod": ItemData(EQUIPMENT, 431 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Stardust Wand": ItemData(EQUIPMENT, 430 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Eaclaneya, Expert Zones
+ "Equipment - Master Wand": ItemData(EQUIPMENT, 258 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Aura Focus": ItemData(EQUIPMENT, 278 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Paladin Wand": ItemData(EQUIPMENT, 276 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, The Deep Sea, Expert Zones
+ "Equipment - Obelisk": ItemData(EQUIPMENT, 307 + equipment_index_offset, ItemClassification.useful, 0), #black market shop (Z14_hobo shop)
+ "Equipment - Flameseeker": ItemData(EQUIPMENT, 358 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Jidamba Eaclaneya, Expert Zones
+ "Equipment - Diamond Wand": ItemData(EQUIPMENT, 154 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Books
+ "Equipment - Moby Dick": ItemData(EQUIPMENT, 51 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Orylei": ItemData(EQUIPMENT, 65 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Craftwork Pages": ItemData(EQUIPMENT, 102 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a book as a treat)
+ "Equipment - Encyclopedia": ItemData(EQUIPMENT, 214 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Gospel": ItemData(EQUIPMENT, 194 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Boomer Society, Advanced Zones
+ "Equipment - Paypirbak": ItemData(EQUIPMENT, 223 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Salmon Pass, Advanced Zones
+ "Equipment - Art of War": ItemData(EQUIPMENT, 224 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Okimoto N.S., Advanced Zones
+ "Equipment - Silver Pages": ItemData(EQUIPMENT, 121 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Pages": ItemData(EQUIPMENT, 167 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Grimoire": ItemData(EQUIPMENT, 438 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Blank Pages": ItemData(EQUIPMENT, 437 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Sara Sara Beach, Advanced Zones
+ "Equipment - Tome of Light": ItemData(EQUIPMENT, 439 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Volcano, Expert Zones
+ "Equipment - Hydrology": ItemData(EQUIPMENT, 441 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Gold Pages": ItemData(EQUIPMENT, 155 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Dark Gospel": ItemData(EQUIPMENT, 440 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Beaurior Volcano, Expert Zones
+ "Equipment - Divination": ItemData(EQUIPMENT, 442 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Malifice": ItemData(EQUIPMENT, 443 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
+ "Equipment - Master Pages": ItemData(EQUIPMENT, 259 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Codex": ItemData(EQUIPMENT, 436 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Diamond Pages": ItemData(EQUIPMENT, 156 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Armor
+ #Shields
+ "Equipment - Buckler": ItemData(EQUIPMENT, 44 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Stout Shield": ItemData(EQUIPMENT, 45 + equipment_index_offset, ItemClassification.useful), #Tier 1, Spawning Meadows, Beginner Zones
+ "Equipment - Iron Guard": ItemData(EQUIPMENT, 68 + equipment_index_offset, ItemClassification.useful), #Tier 1, Yamagawa M.A., Beginner Zones
+ "Equipment - Stalwart Shield": ItemData(EQUIPMENT, 88 + equipment_index_offset, ItemClassification.useful), #Tier 1, Trial Caves/Skumparadise, Beginner Zones
+ "Equipment - Craftwork Shield": ItemData(EQUIPMENT, 506 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Vanguard": ItemData(EQUIPMENT, 111 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Duelling Shield": ItemData(EQUIPMENT, 215 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Lucky Platter": ItemData(EQUIPMENT, 103 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Pipeline, Advanced Zones
+ "Equipment - Boomer Shield": ItemData(EQUIPMENT, 178 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Boomer Society, Advanced Zones
+ "Equipment - Silver Shield": ItemData(EQUIPMENT, 507 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Shield": ItemData(EQUIPMENT, 168 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Cross Shield": ItemData(EQUIPMENT, 444 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Brass Cross": ItemData(EQUIPMENT, 448 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Blood Shield": ItemData(EQUIPMENT, 560 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - The Immovable": ItemData(EQUIPMENT, 451 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Cross Guard": ItemData(EQUIPMENT, 446 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Mages Platter": ItemData(EQUIPMENT, 452 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Salmon River, Advanced Zones
+ "Equipment - Gold Shield": ItemData(EQUIPMENT, 447 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Bulkwark": ItemData(EQUIPMENT, 449 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Flame Guard": ItemData(EQUIPMENT, 450 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Wizards Wall": ItemData(EQUIPMENT, 453 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
+ "Equipment - Master Shield": ItemData(EQUIPMENT, 260 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Turtle Shell": ItemData(EQUIPMENT, 445 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
+ "Equipment - Tower Shield": ItemData(EQUIPMENT, 344 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Jidamba Tangle, Expert Zones
+ "Equipment - Nomads Guard": ItemData(EQUIPMENT, 288 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Continental Tram, Expert Zones
+ "Equipment - Ether Shield": ItemData(EQUIPMENT, 277 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Mirror Shield": ItemData(EQUIPMENT, 246 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Ancient Labyrinth, End-Game Zones
+ "Equipment - Diamond Shield": ItemData(EQUIPMENT, 237 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Heavy Head
+ "Equipment - Chain Helm": ItemData(EQUIPMENT, 25 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Sturdy Helm": ItemData(EQUIPMENT, 26 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Storm Helm": ItemData(EQUIPMENT, 74 + equipment_index_offset, ItemClassification.useful), #Tier 1, Pale Grotto, Beginner Zones
+ "Equipment - Copper Helm": ItemData(EQUIPMENT, 69 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Craftwork Helm": ItemData(EQUIPMENT, 508 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Bronze Helm": ItemData(EQUIPMENT, 106 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Scale Helm": ItemData(EQUIPMENT, 128 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Iron Helm": ItemData(EQUIPMENT, 125 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Jojo Sewers, Advanced Zones
+ "Equipment - Battle Helm": ItemData(EQUIPMENT, 132 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
+ "Equipment - Silver Helm": ItemData(EQUIPMENT, 509 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Helm": ItemData(EQUIPMENT, 169 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Orion Barbut": ItemData(EQUIPMENT, 465 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Iron Barbut": ItemData(EQUIPMENT, 468 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Horned Helm": ItemData(EQUIPMENT, 464 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Gold Helm": ItemData(EQUIPMENT, 469 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Cross Helm": ItemData(EQUIPMENT, 466 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Insignia Helm": ItemData(EQUIPMENT, 470 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Tall Tall Heights, Expert Zones
+ "Equipment - Demon Helm": ItemData(EQUIPMENT, 467 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Master Helm": ItemData(EQUIPMENT, 261 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Guts Busby": ItemData(EQUIPMENT, 308 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Raid Helm": ItemData(EQUIPMENT, 471 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Spellsword Helm": ItemData(EQUIPMENT, 292 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Sequoia Athenaeum, Expert Zones
+ "Equipment - Diamond Helm": ItemData(EQUIPMENT, 236 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Heavy Body
+ "Equipment - Breastplate": ItemData(EQUIPMENT, 18 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Ring Mail": ItemData(EQUIPMENT, 28 + equipment_index_offset, ItemClassification.useful), #Tier 1, Pale Grotto, Beginner Zones
+ "Equipment - Copper Suit": ItemData(EQUIPMENT, 29 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Plate of Wolf": ItemData(EQUIPMENT, 84 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Underpass, Advanced Zones
+ "Equipment - Craftwork Mail": ItemData(EQUIPMENT, 510 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Bronze Suit": ItemData(EQUIPMENT, 107 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Scale Mail": ItemData(EQUIPMENT, 127 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Iron Armor": ItemData(EQUIPMENT, 126 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Jojo Sewers, Advanced Zones
+ "Equipment - Battleplate": ItemData(EQUIPMENT, 43 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
+ "Equipment - Silver Mail": ItemData(EQUIPMENT, 511 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Mail": ItemData(EQUIPMENT, 170 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Orion Armor": ItemData(EQUIPMENT, 455 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Plate of Tiger": ItemData(EQUIPMENT, 129 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Plate Mail": ItemData(EQUIPMENT, 460 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Knights Plate": ItemData(EQUIPMENT, 456 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, The Undercity, Expert Zones
+ "Equipment - Bone Mail": ItemData(EQUIPMENT, 462 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Gold Mail": ItemData(EQUIPMENT, 459 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Sky Armor": ItemData(EQUIPMENT, 461 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Shoudu Province, Expert Zones
+ "Equipment - Plate of Lion": ItemData(EQUIPMENT, 130 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Slip Glide Ride, Expert Zones
+ "Equipment - Dragon Mail": ItemData(EQUIPMENT, 457 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Demon Plate": ItemData(EQUIPMENT, 458 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Master Mail": ItemData(EQUIPMENT, 262 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Construct Mail": ItemData(EQUIPMENT, 272 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Guardian Angel": ItemData(EQUIPMENT, 291 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Plate of Whale": ItemData(EQUIPMENT, 131 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5, The Open Sea, Expert Zones
+ "Equipment - Lunar Mail": ItemData(EQUIPMENT, 463 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The New World, End-Game Zones
+ "Equipment - Diamond Mail": ItemData(EQUIPMENT, 235 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Warrior Mail": ItemData(EQUIPMENT, 533 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Warlock Mail": ItemData(EQUIPMENT, 534 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Aegis Mail": ItemData(EQUIPMENT, 536 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Reaper Mail": ItemData(EQUIPMENT, 535 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Samurai Mail": ItemData(EQUIPMENT, 538 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Valkyrie Mail": ItemData(EQUIPMENT, 537 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Beastmaster Mail": ItemData(EQUIPMENT, 557 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Mimic Mail": ItemData(EQUIPMENT, 548 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+
+ #Medium Head
+ "Equipment - Leather Cap": ItemData(EQUIPMENT, 24 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Beret": ItemData(EQUIPMENT, 27 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Storm Cap": ItemData(EQUIPMENT, 75 + equipment_index_offset, ItemClassification.useful), #Tier 1, Seaside Cliffs, Beginner Zones
+ "Equipment - Headgear": ItemData(EQUIPMENT, 30 + equipment_index_offset, ItemClassification.useful), #Tier 1, Seaside Cliffs, Beginner Zones
+ "Equipment - Craftwork Cap": ItemData(EQUIPMENT, 512 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Rugged Hat": ItemData(EQUIPMENT, 219 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Spore Blocker": ItemData(EQUIPMENT, 195 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Rolling Quintar Fields, Advanced Zones
+ "Equipment - Vikings Hat": ItemData(EQUIPMENT, 220 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Silver Cap": ItemData(EQUIPMENT, 513 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Cap": ItemData(EQUIPMENT, 171 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Combat Band": ItemData(EQUIPMENT, 483 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Red Cap": ItemData(EQUIPMENT, 233 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Ancient Reservoir, Advanced Zones
+ "Equipment - Bandana": ItemData(EQUIPMENT, 484 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Suitor Hat": ItemData(EQUIPMENT, 485 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Gold Cap": ItemData(EQUIPMENT, 520 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Red Hat": ItemData(EQUIPMENT, 480 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Pirate Hat": ItemData(EQUIPMENT, 481 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Ice fisher in Tall Tall Heights; Tier 4, Tall Tall Heights, Expert Zones
+ "Equipment - Tall Tall Hat": ItemData(EQUIPMENT, 486 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Master Cap": ItemData(EQUIPMENT, 263 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Battle Band": ItemData(EQUIPMENT, 345 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The Sequoia, End-Game Zones
+ "Equipment - Captains Hat": ItemData(EQUIPMENT, 482 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5, Sara Sara Bazaar, Advanced Zones
+ "Equipment - Red Headgear": ItemData(EQUIPMENT, 487 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
+ "Equipment - Diamond Cap": ItemData(EQUIPMENT, 238 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Medium Body
+ "Equipment - Leather Outfit": ItemData(EQUIPMENT, 17 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Studded Armor": ItemData(EQUIPMENT, 35 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Leather Mail": ItemData(EQUIPMENT, 36 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Craftwork Vest": ItemData(EQUIPMENT, 514 + equipment_index_offset, ItemClassification.useful), #Tier 1, Capital Sequoia, Advanced Zones (beginners can have little a medium body as a treat)
+ "Equipment - Chain Vest": ItemData(EQUIPMENT, 217 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Combat Vest": ItemData(EQUIPMENT, 218 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Smelly Gi": ItemData(EQUIPMENT, 268 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Jojo Sewers, Advanced Zones
+ "Equipment - Training Gi": ItemData(EQUIPMENT, 229 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Okimoto N.S., Advanced Zones
+ "Equipment - Tuxedo": ItemData(EQUIPMENT, 176 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Sequoia, Advanced Zones
+ "Equipment - Silver Vest": ItemData(EQUIPMENT, 515 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Vest": ItemData(EQUIPMENT, 172 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Power Vest": ItemData(EQUIPMENT, 472 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Red Coat": ItemData(EQUIPMENT, 57 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3, Ancient Reservoir, Advanced Zones
+ "Equipment - Drifters Vest": ItemData(EQUIPMENT, 474 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Bandage Wrap": ItemData(EQUIPMENT, 558 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gaia Vest": ItemData(EQUIPMENT, 473 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Brigandine": ItemData(EQUIPMENT, 477 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, The Undercity, Expert Zones
+ "Equipment - Gold Vest": ItemData(EQUIPMENT, 521 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Onion Gi": ItemData(EQUIPMENT, 475 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Martial Vest": ItemData(EQUIPMENT, 478 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Judo Gi": ItemData(EQUIPMENT, 479 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Tall Tall Heights, Expert Zones
+ "Equipment - Master Vest": ItemData(EQUIPMENT, 264 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Quintar Pelt": ItemData(EQUIPMENT, 493 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 5, Shop, Expert Zones
+ "Equipment - Shadow Gi": ItemData(EQUIPMENT, 347 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, Shoudu Province, Expert Zones
+ "Equipment - Rex Vest": ItemData(EQUIPMENT, 476 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Slime Coat": ItemData(EQUIPMENT, 524 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Diamond Vest": ItemData(EQUIPMENT, 239 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Monk Vest": ItemData(EQUIPMENT, 539 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Rogue Vest": ItemData(EQUIPMENT, 540 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Fencer Vest": ItemData(EQUIPMENT, 541 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Hunter Vest": ItemData(EQUIPMENT, 542 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Ninja Vest": ItemData(EQUIPMENT, 543 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Nomad Vest": ItemData(EQUIPMENT, 544 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Beatsmith Vest": ItemData(EQUIPMENT, 545 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Assassin Vest": ItemData(EQUIPMENT, 546 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+
+ #Light Head
+ "Equipment - Hemp Hood": ItemData(EQUIPMENT, 31 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Cotton Hood": ItemData(EQUIPMENT, 32 + equipment_index_offset, ItemClassification.useful), #Tier 1, Delende, Beginner Zones
+ "Equipment - Storm Hood": ItemData(EQUIPMENT, 76 + equipment_index_offset, ItemClassification.useful), #Tier 1, Delende, Beginner Zones
+ "Equipment - Holy Hat": ItemData(EQUIPMENT, 33 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Craftwork Crown": ItemData(EQUIPMENT, 516 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Silk Hat": ItemData(EQUIPMENT, 110 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Circlet": ItemData(EQUIPMENT, 133 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Quintar Sanctum, Advanced Zones
+ "Equipment - Holy Miter": ItemData(EQUIPMENT, 109 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Woven Hood": ItemData(EQUIPMENT, 122 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
+ "Equipment - Silver Crown": ItemData(EQUIPMENT, 517 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Crown": ItemData(EQUIPMENT, 173 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Clerics Hood": ItemData(EQUIPMENT, 341 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Wizards Hat": ItemData(EQUIPMENT, 340 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Fairys Crown": ItemData(EQUIPMENT, 352 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Quilted Hat": ItemData(EQUIPMENT, 353 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Blood Hat": ItemData(EQUIPMENT, 559 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Plague Mask": ItemData(EQUIPMENT, 342 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Shoudu Province, Expert Zones
+ "Equipment - Guard Crown": ItemData(EQUIPMENT, 356 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Rock, Expert Zones
+ "Equipment - Gold Crown": ItemData(EQUIPMENT, 522 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Bronze Hairpin": ItemData(EQUIPMENT, 34 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tall Tall Heights fisher; Tier 4, Tall Tall Heights, Expert Zones
+ "Equipment - Regen Crown": ItemData(EQUIPMENT, 343 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Ravens Hood": ItemData(EQUIPMENT, 348 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Celestial Crown": ItemData(EQUIPMENT, 52 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Eaclaneya, Expert Zones
+ "Equipment - Master Crown": ItemData(EQUIPMENT, 265 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Advanced Zones
+ "Equipment - Pointy Hat": ItemData(EQUIPMENT, 531 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5, The Deep Sea, Expert Zones
+ "Equipment - Vita Crown": ItemData(EQUIPMENT, 271 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Ancient Labyrinth, End-Game Zones
+ "Equipment - Pact Crown": ItemData(EQUIPMENT, 350 + equipment_index_offset, ItemClassification.useful, 0), #black market shop (Z14_hobo shop)
+ "Equipment - Protector": ItemData(EQUIPMENT, 354 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Castle Sequoia, End-Game Zones
+ "Equipment - Diamond Crown": ItemData(EQUIPMENT, 240 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Light Body
+ "Equipment - Hemp Robe": ItemData(EQUIPMENT, 19 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Cotton Robe": ItemData(EQUIPMENT, 20 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 1), #Tier 1, Shop, Beginner Zones
+ "Equipment - Mages Robe": ItemData(EQUIPMENT, 21 + equipment_index_offset, ItemClassification.useful), #Tier 1, Delende, Beginner Zones
+ "Equipment - Swimmers Top": ItemData(EQUIPMENT, 81 + equipment_index_offset, ItemClassification.useful), #Tier 1, Seaside Cliffs, Beginner Zones
+ "Equipment - Craftwork Robe": ItemData(EQUIPMENT, 518 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 1, Capital Sequoia, Advanced Zones
+ "Equipment - Priest Garb": ItemData(EQUIPMENT, 216 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Dress": ItemData(EQUIPMENT, 134 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Quintar Sanctum, Advanced Zones
+ "Equipment - Silk Shirt": ItemData(EQUIPMENT, 108 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Woven Shirt": ItemData(EQUIPMENT, 230 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2, Capital Jail, Advanced Zones
+ "Equipment - Silver Cape": ItemData(EQUIPMENT, 519 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Artisan Shirt": ItemData(EQUIPMENT, 174 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Wizards Robe": ItemData(EQUIPMENT, 124 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Clerics Robe": ItemData(EQUIPMENT, 123 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Cosplay Garb": ItemData(EQUIPMENT, 336 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Sturdy Cape": ItemData(EQUIPMENT, 359 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Shelter Dress": ItemData(EQUIPMENT, 357 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3, Beaurior Rock, Expert Zones
+ "Equipment - Gold Robe": ItemData(EQUIPMENT, 523 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Winter Cape": ItemData(EQUIPMENT, 325 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Blue Cape": ItemData(EQUIPMENT, 360 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Lands End, Expert Zones
+ "Equipment - Seekers Garb": ItemData(EQUIPMENT, 324 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Slip Glide Ride, Expert Zones
+ "Equipment - Ravens Cloak": ItemData(EQUIPMENT, 349 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4, Jidamba Tangle, Expert Zones
+ "Equipment - Master Cape": ItemData(EQUIPMENT, 266 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Archmage Vest": ItemData(EQUIPMENT, 337 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, Ancient Labyrinth, End-Game Zones
+ "Equipment - Saviors Cape": ItemData(EQUIPMENT, 338 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Assassins Cloak": ItemData(EQUIPMENT, 273 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The Depths, End-Game Zones
+ "Equipment - Stealth Cape": ItemData(EQUIPMENT, 319 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5, The Sequoia, End-Game Zones
+ "Equipment - Shell Gown": ItemData(EQUIPMENT, 355 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Diamond Robe": ItemData(EQUIPMENT, 241 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Cleric Robe": ItemData(EQUIPMENT, 547 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Wizard Robe": ItemData(EQUIPMENT, 549 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Shaman Robe": ItemData(EQUIPMENT, 550 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Scholar Robe": ItemData(EQUIPMENT, 551 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Chemist Robe": ItemData(EQUIPMENT, 552 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Dervish Robe": ItemData(EQUIPMENT, 553 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Weaver Robe": ItemData(EQUIPMENT, 554 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+ "Equipment - Summoner Robe": ItemData(EQUIPMENT, 555 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier Class, Jade Cavern, Expert Zones
+
+ #Accessories
+ "Equipment - Fervor Charm": ItemData(EQUIPMENT, 48 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende, Beginner Zones
+ "Equipment - Dodge Charm": ItemData(EQUIPMENT, 39 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Soiled Den, Beginner Zones
+ "Equipment - Earring": ItemData(EQUIPMENT, 79 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende, Beginner Zones
+ "Equipment - Bracer": ItemData(EQUIPMENT, 70 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende and Seaside Cliffs, Beginner Zones
+ "Equipment - Jewel of Defense": ItemData(EQUIPMENT, 50 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Seaside Cliffs, Beginner Zones
+ "Equipment - Scope Bit": ItemData(EQUIPMENT, 226 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Seaside Cliffs and Quintar Nest, Beginner Zones
+ "Equipment - Protect Amulet": ItemData(EQUIPMENT, 49 + equipment_index_offset, ItemClassification.useful), #Tier 1 Shop, Delende, Beginner Zones
+ "Equipment - Wasps Stinger": ItemData(EQUIPMENT, 86 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gem Ring": ItemData(EQUIPMENT, 332 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Earth Bangle": ItemData(EQUIPMENT, 82 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Water Bangle": ItemData(EQUIPMENT, 83 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Looters Ring": ItemData(EQUIPMENT, 54 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Delende and Greenshire Reprise, Beginner Zones
+ "Equipment - Burglars Glove": ItemData(EQUIPMENT, 53 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Spawning Meadows and Proving Meadows, Beginner Zones
+ "Equipment - Torpid Cuffs": ItemData(EQUIPMENT, 331 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Yamagawa M.A., Beginner Zones
+ "Equipment - Squirrel Dung": ItemData(EQUIPMENT, 85 + equipment_index_offset, ItemClassification.useful), #Tier 1 Unique, Spawning Meadows, Beginner Zones
+ "Equipment - Fang Pendant": ItemData(EQUIPMENT, 196 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Shop (Capital), Capital Sequoia, Advanced Zones
+ "Equipment - Mana Ring": ItemData(EQUIPMENT, 38 + equipment_index_offset, ItemClassification.useful), #Tier 2 Shop (Capital), Skumparadise, Beginner Zones
+ "Equipment - Awake Ring": ItemData(EQUIPMENT, 87 + equipment_index_offset, ItemClassification.useful), #Tier 2 Shop (Capital), Skumparadise, Beginner Zones
+ "Equipment - Prayer Beads": ItemData(EQUIPMENT, 186 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Shell Amulet": ItemData(EQUIPMENT, 179 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Shop (Capital), Greenshire Reprise, Advanced Zones
+ "Equipment - Samurais Glove": ItemData(EQUIPMENT, 185 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 2, Shop, Advanced Zones
+ "Equipment - Magic Finder": ItemData(EQUIPMENT, 323 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Loot, Okimoto N.S., Advanced Zones
+ "Equipment - Learners Pin": ItemData(EQUIPMENT, 492 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Unique, Capital Sequoia, Advanced Zones
+ "Equipment - Givers Ring": ItemData(EQUIPMENT, 182 + equipment_index_offset, ItemClassification.useful, 0, 1),#Tier 2 Unique, Capital Sequoia, Advanced Zones
+ "Equipment - Aggro Band": ItemData(EQUIPMENT, 505 + equipment_index_offset, ItemClassification.useful, 0, 1), #Delende fisher; Tier 2 Unique, Delende, Advanced Zones
+ "Equipment - Hemoring": ItemData(EQUIPMENT, 56 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Float Shoes": ItemData(EQUIPMENT, 193 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 2 Unique, Okimoto N.S. and Lake Delende, Advanced Zones
+ "Equipment - Hope Cross": ItemData(EQUIPMENT, 225 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Defense Shifter": ItemData(EQUIPMENT, 191 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Shop (Sara Sara), Ancient Reservoir, Advanced Zones
+ "Equipment - Resist Shifter": ItemData(EQUIPMENT, 221 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Shop (Sara Sara), Ancient Reservoir, Advanced Zones
+ "Equipment - Casters Ring": ItemData(EQUIPMENT, 9 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Fearsome Ring": ItemData(EQUIPMENT, 504 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Crit Fang": ItemData(EQUIPMENT, 502 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Dancing Shoes": ItemData(EQUIPMENT, 333 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Advanced Zones
+ "Equipment - Bulk Belt": ItemData(EQUIPMENT, 243 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Poison Talon": ItemData(EQUIPMENT, 334 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - First Strike Mitt": ItemData(EQUIPMENT, 501 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Menders Ring": ItemData(EQUIPMENT, 529 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Kitsune Mask": ItemData(EQUIPMENT, 503 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Pact Ring": ItemData(EQUIPMENT, 496 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 3, Shop, Expert Zones
+ "Equipment - Scope Specs": ItemData(EQUIPMENT, 562 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 3 Loot, Poko Poko Desert, Advanced Zones
+ "Equipment - Loot Finder": ItemData(EQUIPMENT, 326 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Quiet Shoes": ItemData(EQUIPMENT, 495 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Knicked Knackers": ItemData(EQUIPMENT, 232 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Unique, Shoudu Province, Expert Zones
+ "Equipment - Looters Pin": ItemData(EQUIPMENT, 231 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Unique, Shoudu Province, Expert Zones
+ "Equipment - Acrobat Shoes": ItemData(EQUIPMENT, 320 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 3 Unique, Shoudu Province, Expert Zones
+ "Equipment - Bone Trophy": ItemData(EQUIPMENT, 37 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Gusto Fang": ItemData(EQUIPMENT, 47 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Beads of Defense": ItemData(EQUIPMENT, 328 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 4 Shop (Tall Tall), Castle Sequoia, End-Game Zones
+ "Equipment - Winter Mitten": ItemData(EQUIPMENT, 322 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 0, 0, 0, 1), #Tier 4, Shop, Expert Zones
+ "Equipment - Glasses": ItemData(EQUIPMENT, 327 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tall Tall Heights fisher; Tier 4 Unique, Tall Tall Heights, Expert Zones
+ "Equipment - Gusto Charm": ItemData(EQUIPMENT, 46 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tall Tall Heights fisher; Tier 4 Unique, Tall Tall Heights, Expert Zones
+ "Equipment - Muggers Glove": ItemData(EQUIPMENT, 309 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 4 Unique, Shoudu Province, Expert Zones
+ "Equipment - Fursuit": ItemData(EQUIPMENT, 490 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #crafted but i think its funny; Tier 5 Crafting, Quintar Reserve, Expert Zones
+ "Equipment - Snow Pendant": ItemData(EQUIPMENT, 528 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Ogre Ball": ItemData(EQUIPMENT, 78 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Sanity Ring": ItemData(EQUIPMENT, 330 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5 Unique, The Open Sea, Expert Zones
+ "Equipment - Undead Ring": ItemData(EQUIPMENT, 491 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, Quintar Reserve, Expert Zones
+ "Equipment - Fairys Ring": ItemData(EQUIPMENT, 329 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5 Unique, The Open Sea, Expert Zones
+ "Equipment - Oven Mitt": ItemData(EQUIPMENT, 321 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, The Deep Sea, Expert Zones
+ "Equipment - Autumns Oath": ItemData(EQUIPMENT, 234 + equipment_index_offset, ItemClassification.useful), #Tier 5 Unique, Yamagawa M.A., Beginner Zones
+ "Equipment - Springs Oath": ItemData(EQUIPMENT, 489 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Overpass (Okimoto N.S.), Advanced Zones
+ "Equipment - Lucky Socks": ItemData(EQUIPMENT, 498 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Capital Sequoia, Advanced Zones
+ "Equipment - Lucky Briefs": ItemData(EQUIPMENT, 499 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Capital Sequoia, Advanced Zones
+ "Equipment - Tall Stand Ring": ItemData(EQUIPMENT, 244 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, Shoudu Province, Expert Zones
+ "Equipment - Nomads Emblem": ItemData(EQUIPMENT, 287 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #The Open Sea fisher; Tier 5 Unique, The Open Sea, Expert Zones
+ "Equipment - Lockon Lense": ItemData(EQUIPMENT, 373 + equipment_index_offset, ItemClassification.useful, 0), #Quintar Enthusiast battle drop
+ "Equipment - Red Hairpin": ItemData(EQUIPMENT, 247 + equipment_index_offset, ItemClassification.useful), #Delende fisher; #Tier 5 Unique, Delende, Beginner Zones
+ "Equipment - Stone of Jodan": ItemData(EQUIPMENT, 286 + equipment_index_offset, ItemClassification.useful, 0, 1), #Tier 5 Unique, Jojo Sewers, Advanced Zones
+ "Equipment - Ribbon": ItemData(EQUIPMENT, 346 + equipment_index_offset, ItemClassification.useful, 0, 0, 1), #Tier 5 Unique, Shoudu Province, Expert Zones
+ "Equipment - Hand of Midas": ItemData(EQUIPMENT, 494 + equipment_index_offset, ItemClassification.useful, 0, 0, 0, 1), #Tier 5 Unique, The Sequoia, End-Game Zones
+ "Equipment - Ghendolfs Ring": ItemData(EQUIPMENT, 242 + equipment_index_offset, ItemClassification.useful, 0),
+ "Equipment - Master Material": ItemData(EQUIPMENT, 284 + equipment_index_offset, ItemClassification.useful, 0),
+
+ #Progressive Equipment
+ #the highest equipment id is 590 so these ids start at 600
+ #Shopsanity ids are in brackets in the lists at the end
+ "Equipment - Progressive 1H Sword": ItemData(EQUIPMENT, 900 + equipment_index_offset, ItemClassification.useful, 4, 1, 7, 1, 1, 4, 1, 0), #1H Sword IDs [0], 11, 71, 89, 93, 200, [199], [157], [379], 377, 375, 380, [374], 372, 381, [248], 303, 411, 316
+ "Equipment - Progressive 2H Sword": ItemData(EQUIPMENT, 901 + equipment_index_offset, ItemClassification.useful, 1, 2, 2, 1, 0, 3, 1, 0), # 2H Sword IDs 12, [378], 177, [376], 197, 525, [370], 382, [249], 279
+ "Equipment - Progressive 1H Axe": ItemData(EQUIPMENT, 902 + equipment_index_offset, ItemClassification.useful, 0, 3, 0, 1, 1, 1, 0, 0), # 1H Axe IDs [55], 94, 187, 386, [251], 388
+ "Equipment - Progressive 2H Axe": ItemData(EQUIPMENT, 903 + equipment_index_offset, ItemClassification.useful, 1, 1, 1, 1, 1, 5, 2, 0), # 2H Axe IDs 2, [66], [201], [383], [158], [384], [390], 385, [391], [250], 280, 274
+ "Equipment - Progressive Dagger": ItemData(EQUIPMENT, 904 + equipment_index_offset, ItemClassification.useful, 3, 3, 2, 1, 3, 4, 2, 0), # Dagger IDs [3], 63, [77], 40, [60], 95, [202], 184, [204], [159], 198, 305, [526], 393, [394], 72, [269], 306
+ "Equipment - Progressive Ninja Dagger": ItemData(EQUIPMENT, 905 + equipment_index_offset, ItemClassification.useful, 0, 3, 1, 1, 0, 1, 2, 0), # Ninja Dagger IDs 192, 203, 397, [392], [396], [400], 317, 318
+ "Equipment - Progressive Rapier": ItemData(EQUIPMENT, 906 + equipment_index_offset, ItemClassification.useful, 2, 3, 3, 1, 2, 5, 2, 0), # Rapier IDs [73], [1], 42, 96, [207], [206], 175, [160], 10, [402], [408], 404, [405], 407, [252], 401, 403, 406
+ "Equipment - Progressive Katana": ItemData(EQUIPMENT, 907 + equipment_index_offset, ItemClassification.useful, 1, 1, 1, 0, 0, 3, 4, 0), # Katana IDs 97, 399, [161], [363], [22], [364], [23], [366], [253], 367
+ "Equipment - Progressive Spear": ItemData(EQUIPMENT, 908 + equipment_index_offset, ItemClassification.useful, 1, 3, 3, 1, 0, 5, 1, 0), # Spear IDs [4], 98, [205], 190, 183, [162], 409, [410], 418, [419], 417, 416, [254], 275
+ "Equipment - Progressive Scythe": ItemData(EQUIPMENT, 909 + equipment_index_offset, ItemClassification.useful, 1, 2, 3, 0, 0, 4, 1, 0), # Scythe IDs 6, 99, [208], [163], [294], 293, [295], 414, 415, 590, [255]
+ "Equipment - Progressive Bow": ItemData(EQUIPMENT, 910 + equipment_index_offset, ItemClassification.useful, 1, 1, 2, 1, 0, 5, 4), # Bow IDs 105, [7], 181, [209], [164], [297], [296], [298], [300], 301, 299, [256], [281], 420
+ "Equipment - Progressive Staff": ItemData(EQUIPMENT, 911 + equipment_index_offset, ItemClassification.useful, 1, 2, 4, 1, 2, 5, 0, 0), # Staff IDs [5], 62, [15], 100, [210], 188, [211], [165], [423], 422, 424, 425, [257], 290, 429
+ "Equipment - Progressive Beating Staff": ItemData(EQUIPMENT, 912 + equipment_index_offset, ItemClassification.useful, 1, 2, 0, 0, 0, 1, 2, 0), # Beating Staff IDs 14, 426, 335, [427], [67], [428]
+ "Equipment - Progressive Wand": ItemData(EQUIPMENT, 913 + equipment_index_offset, ItemClassification.useful, 2, 3, 5, 0, 2, 5, 2, 0), # Wand IDs [8], 13, [16], 64, 101, [213], 189, [212], [166], 267, [434], 432, [433], 435, [431], 430, [258], 276, 358
+ "Equipment - Progressive Book": ItemData(EQUIPMENT, 914 + equipment_index_offset, ItemClassification.useful, 1, 4, 3, 0, 1, 5, 2, 0), # Book IDs [51], [65], 102, [214], 194, 223, 224, [167], [438], 437, 439, [441], 440, [442], 443, [259] (with shopsanity on i graduated craftwork to advanced)
+ "Equipment - Progressive Shield": ItemData(EQUIPMENT, 915 + equipment_index_offset, ItemClassification.useful, 3, 4, 5, 1, 1, 5, 4, 0), # Shield IDs [44], 45, 68, 88, 506, [111], [215], 103, 178, [168], [444], [448], 451, [446], 452, [449], 450, 453, [260], [445], 344, 288, 246
+ "Equipment - Progressive Heavy Head": ItemData(EQUIPMENT, 916 + equipment_index_offset, ItemClassification.useful, 1, 3, 4, 0, 3, 5, 2, 0), # Heavy Head IDs [25], [26], 74, [69], 508, [106], [128], 125, 132, [169], [465], [468], 464, [466], 470, 467, [261], 292
+ "Equipment - Progressive Heavy Body": ItemData(EQUIPMENT, 917 + equipment_index_offset, ItemClassification.useful, 1, 4, 14, 1, 2, 5, 2, 0), # Heavy Body IDs [18], 28, [29], 84, 510, [107], [127], 126, 43, [170], [455], [460], 456, 462, 461, 130, [457], 458, [262], 131, 463, 533, 534, 536, 535, 538, 537, 557, 548
+ "Equipment - Progressive Medium Head": ItemData(EQUIPMENT, 918 + equipment_index_offset, ItemClassification.useful, 2, 4, 2, 1, 2, 5, 3, 0), # Medium Head IDs [24], [27], 75, 30, 512, [219], 195, [220], [171], [483], 233, [484], 485, 481, [486], [263], 345, 482, [487]
+ "Equipment - Progressive Medium Body": ItemData(EQUIPMENT, 919 + equipment_index_offset, ItemClassification.useful, 1, 4, 12, 0, 2, 6, 3, 0), # Medium Body IDs [17], [35], [36], 514, [217], [218], 268, 229, 176, [172], [472], 57, [474], 473, 477, [478], 479, [264], [493], 347, 539, 540, 541, 542, 543, 544, 545, 546 (with shopsanity on i graduated craftwork to advanced)
+ "Equipment - Progressive Light Head": ItemData(EQUIPMENT, 920 + equipment_index_offset, ItemClassification.useful, 2, 3, 6, 2, 2, 5, 3, 0), # Light Head IDs [31], 32, 76, [33], 516, [110], 133, [109], 122, [173], [341], [340], [353], 342, 356, 34, [343], 348, 52, [265], 531, 271, 354
+ "Equipment - Progressive Light Body": ItemData(EQUIPMENT, 921 + equipment_index_offset, ItemClassification.useful, 2, 3, 12, 3, 2, 5, 3, 0), # Light Body IDs [19], [20], 21, 81, 518, [216], 134, [108], 230, [174], [124], [123], [359], 357, [325], 360, 324, 349, [266], 337, 273, 319, 547, 549, 550, 551, 552, 553, 554, 555
+
+ #Maps
+ #Beginner
+ SPAWNING_MEADOWS_MAP: ItemData(MAP, 73 + item_index_offset, ItemClassification.useful),
+ DELENDE_MAP: ItemData(MAP, 74 + item_index_offset, ItemClassification.useful),
+ BASEMENT_MAP: ItemData(MAP, 213 + item_index_offset, ItemClassification.useful),
+ MERCURY_SHRINE_MAP: ItemData(MAP, 87 + item_index_offset, ItemClassification.useful),
+ SOILED_DEN_MAP: ItemData(MAP, 79 + item_index_offset, ItemClassification.useful),
+ THE_PALE_GROTTO_MAP: ItemData(MAP, 75 + item_index_offset, ItemClassification.useful),
+ SEASIDE_CLIFFS_MAP: ItemData(MAP, 76 + item_index_offset, ItemClassification.useful),
+ DRAFT_SHAFT_CONDUIT_MAP: ItemData(MAP, 77 + item_index_offset, ItemClassification.useful),
+ YAMAGAWA_MA_MAP: ItemData(MAP, 80 + item_index_offset, ItemClassification.useful),
+ PROVING_MEADOWS_MAP: ItemData(MAP, 78 + item_index_offset, ItemClassification.useful),
+ TRIAL_CAVES_MAP: ItemData(MAP, 214 + item_index_offset, ItemClassification.useful),
+ SKUMPARADISE_MAP: ItemData(MAP, 82 + item_index_offset, ItemClassification.useful),
+ #Advanced
+ CAPITAL_COURTYARD_MAP: ItemData(MAP, 83 + item_index_offset, ItemClassification.useful, 0, 1),
+ CAPITAL_SEQUOIA_MAP: ItemData(MAP, 84 + item_index_offset, ItemClassification.useful, 0, 1),
+ JOJO_SEWERS_MAP: ItemData(MAP, 85 + item_index_offset, ItemClassification.useful, 0, 1),
+ BOOMER_SOCIETY_MAP: ItemData(MAP, 89 + item_index_offset, ItemClassification.useful, 0, 1),
+ ROLLING_QUINTAR_FIELDS_MAP: ItemData(MAP, 90 + item_index_offset, ItemClassification.useful, 0, 1),
+ QUINTAR_NEST_MAP: ItemData(MAP, 92 + item_index_offset, ItemClassification.useful, 0, 1),
+ QUINTAR_SANCTUM_MAP: ItemData(MAP, 120 + item_index_offset, ItemClassification.useful, 0, 1),
+ CAPITAL_JAIL_MAP: ItemData(MAP, 94 + item_index_offset, ItemClassification.useful, 0, 1),
+ CAPITAL_PIPELINE_MAP: ItemData(MAP, 170 + item_index_offset, ItemClassification.useful, 0, 1),
+ COBBLESTONE_CRAG_MAP: ItemData(MAP, 96 + item_index_offset, ItemClassification.useful, 0, 1),
+ OKIMOTO_NS_MAP: ItemData(MAP, 98 + item_index_offset, ItemClassification.useful, 0, 1),
+ GREENSHIRE_REPRISE_MAP: ItemData(MAP, 86 + item_index_offset, ItemClassification.useful, 0, 1),
+ SALMON_PASS_MAP: ItemData(MAP, 99 + item_index_offset, ItemClassification.useful, 0, 1),
+ SALMON_RIVER_MAP: ItemData(MAP, 100 + item_index_offset, ItemClassification.useful, 0, 1),
+ POSEIDON_SHRINE_MAP: ItemData(MAP, 101 + item_index_offset, ItemClassification.useful, 0, 1),
+ RIVER_CATS_EGO_MAP: ItemData(MAP, 217 + item_index_offset, ItemClassification.useful, 0, 1),
+ POKO_POKO_DESERT_MAP: ItemData(MAP, 103 + item_index_offset, ItemClassification.useful, 0, 1),
+ SARA_SARA_BAZAAR_MAP: ItemData(MAP, 104 + item_index_offset, ItemClassification.useful, 0, 1),
+ SARA_SARA_BEACH_MAP: ItemData(MAP, 105 + item_index_offset, ItemClassification.useful, 0, 1),
+ ANCIENT_RESERVOIR_MAP: ItemData(MAP, 106 + item_index_offset, ItemClassification.useful, 0, 1),
+ SALMON_BAY_MAP: ItemData(MAP, 128 + item_index_offset, ItemClassification.useful, 0, 1),
+ OVERPASS_MAP: ItemData(MAP, 215 + item_index_offset, ItemClassification.useful), #in Beginner bc of Yamagawa (and Regionsanity expects you to have it unlocked at the start)
+ UNDERPASS_MAP: ItemData(MAP, 216 + item_index_offset, ItemClassification.useful), #in Beginner bc of Delende (and Regionsanity expects you to have it unlocked at the start)
+ #Expert
+ THE_OPEN_SEA_MAP: ItemData(MAP, 198 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ SHOUDU_PROVINCE_MAP: ItemData(MAP, 107 + item_index_offset, ItemClassification.useful, 0, 1), #in Advanced for Shoudu Waterfront
+ THE_UNDERCITY_MAP: ItemData(MAP, 108 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ GANYMEDE_SHRINE_MAP: ItemData(MAP, 117 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ BEAURIOR_VOLCANO_MAP: ItemData(MAP, 109 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ BEAURIOR_ROCK_MAP: ItemData(MAP, 110 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ LAKE_DELENDE_MAP: ItemData(MAP, 121 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ QUINTAR_RESERVE_MAP: ItemData(MAP, 119 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ DIONE_SHRINE_MAP: ItemData(MAP, 199 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ FLYERS_LOOKOUT_MAP: ItemData(MAP, 223 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ QUINTAR_MAUSOLEUM_MAP: ItemData(MAP, 211 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ EASTERN_CHASM_MAP: ItemData(MAP, 219 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ TALL_TALL_HEIGHTS_MAP: ItemData(MAP, 112 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ NORTHERN_CAVE_MAP: ItemData(MAP, 194 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ LANDS_END_MAP: ItemData(MAP, 130 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ SLIP_GLIDE_RIDE_MAP: ItemData(MAP, 113 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ SEQUOIA_ATHENAEUM_MAP: ItemData(MAP, 220 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ NORTHERN_STRETCH_MAP: ItemData(MAP, 218 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ CASTLE_RAMPARTS_MAP: ItemData(MAP, 127 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ THE_CHALICE_OF_TAR_MAP: ItemData(MAP, 221 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ FLYERS_CRAG_MAP: ItemData(MAP, 222 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ JIDAMBA_TANGLE_MAP: ItemData(MAP, 122 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ JIDAMBA_EACLANEYA_MAP: ItemData(MAP, 123 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ THE_DEEP_SEA_MAP: ItemData(MAP, 124 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ NEPTUNE_SHRINE_MAP: ItemData(MAP, 206 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ JADE_CAVERN_MAP: ItemData(MAP, 228 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ CONTINENTAL_TRAM_MAP: ItemData(MAP, 126 + item_index_offset, ItemClassification.useful, 0, 0, 1),
+ #End-Game
+ ANCIENT_LABYRINTH_MAP: ItemData(MAP, 210 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
+ THE_SEQUOIA_MAP: ItemData(MAP, 111 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
+ THE_DEPTHS_MAP: ItemData(MAP, 195 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
+ CASTLE_SEQUOIA_MAP: ItemData(MAP, 209 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1),
+ THE_OLD_WORLD_MAP: ItemData(MAP, 254 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1), #if player is on True Astley goal, get_item_pool in init adds this if you didn't pick to include End-Game Regions
+ THE_NEW_WORLD_MAP: ItemData(MAP, 125 + item_index_offset, ItemClassification.useful, 0, 0, 0, 1), #if player is on Astley/True Astley goal, get_item_pool in init adds this if you didn't pick to include End-Game Regions
+
+ # Teleport Shards
+ "Item - Gaea Shard": ItemData(TELEPORT_SHARD, 22 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Mercury Shard": ItemData(TELEPORT_SHARD, 12 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Poseidon Shard": ItemData(TELEPORT_SHARD, 56 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Mars Shard": ItemData(TELEPORT_SHARD, 58 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Ganymede Shard": ItemData(TELEPORT_SHARD, 62 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Triton Shard": ItemData(TELEPORT_SHARD, 63 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Callisto Shard": ItemData(TELEPORT_SHARD, 154 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Europa Shard": ItemData(TELEPORT_SHARD, 61 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Dione Shard": ItemData(TELEPORT_SHARD, 165 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Neptune Shard": ItemData(TELEPORT_SHARD, 207 + item_index_offset, ItemClassification.useful, 0),
+ "Item - New World Shard": ItemData(TELEPORT_SHARD, 139 + item_index_offset, ItemClassification.useful, 0),
+ "Item - Old World Shard": ItemData(TELEPORT_SHARD, 252 + item_index_offset, ItemClassification.useful, 0),
+
+ #Seeds
+ "Item - Sketchy Seed": ItemData(ITEM, 178 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Tuber Seed": ItemData(ITEM, 179 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Spore Seed": ItemData(ITEM, 180 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Gourmet Seed": ItemData(ITEM, 181 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Fetish Seed": ItemData(ITEM, 182 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Tear Seed": ItemData(ITEM, 187 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Colony Seed": ItemData(ITEM, 188 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Watery Seed": ItemData(ITEM, 189 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Bloody Seed": ItemData(ITEM, 190 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Dark Seed": ItemData(ITEM, 183 + item_index_offset, ItemClassification.filler, 0),
+
+ #Seals
+ "Equipment - Warrior Seal": ItemData(EQUIPMENT, 564 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Monk Seal": ItemData(EQUIPMENT, 565 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Rogue Seal": ItemData(EQUIPMENT, 566 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Cleric Seal": ItemData(EQUIPMENT, 567 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Wizard Seal": ItemData(EQUIPMENT, 568 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Warlock Seal": ItemData(EQUIPMENT, 569 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Fencer Seal": ItemData(EQUIPMENT, 570 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Shaman Seal": ItemData(EQUIPMENT, 571 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Scholar Seal": ItemData(EQUIPMENT, 572 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Aegis Seal": ItemData(EQUIPMENT, 573 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Hunter Seal": ItemData(EQUIPMENT, 574 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Chemist Seal": ItemData(EQUIPMENT, 575 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Reaper Seal": ItemData(EQUIPMENT, 576 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Ninja Seal": ItemData(EQUIPMENT, 577 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Nomad Seal": ItemData(EQUIPMENT, 578 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Dervish Seal": ItemData(EQUIPMENT, 579 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Beatsmith Seal": ItemData(EQUIPMENT, 580 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Samurai Seal": ItemData(EQUIPMENT, 581 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Assassin Seal": ItemData(EQUIPMENT, 582 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Valkyrie Seal": ItemData(EQUIPMENT, 583 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Weaver Seal": ItemData(EQUIPMENT, 584 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Summoner Seal": ItemData(EQUIPMENT, 585 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Beastmaster Seal": ItemData(EQUIPMENT, 586 + equipment_index_offset, ItemClassification.filler, 0),
+ "Equipment - Mimic Seal": ItemData(EQUIPMENT, 587 + equipment_index_offset, ItemClassification.filler, 0),
+
+ #Quintar Raising
+ "Item - Quintar Grass": ItemData(ITEM, 157 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Quintar Wheat": ItemData(ITEM, 202 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Quintar Berries": ItemData(ITEM, 203 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Quintar Cheese": ItemData(ITEM, 204 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Quintar Cookie": ItemData(ITEM, 205 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Name Tag": ItemData(ITEM, 200 + item_index_offset, ItemClassification.filler, 0),
+ "Item - Incubator": ItemData(ITEM, 201 + item_index_offset, ItemClassification.filler, 0),
+
+ #Currency
+ #"Currency": ItemData("Currency", 0 + index_offset, ItemClassification.filler),
+
+ #Summons
+ SHAKU_SUMMON: ItemData(SUMMON, 223 + summon_index_offset, ItemClassification.useful),
+ PAMOA_SUMMON: ItemData(SUMMON, 224 + summon_index_offset, ItemClassification.useful),
+ GUABA_SUMMON: ItemData(SUMMON, 225 + summon_index_offset, ItemClassification.useful),
+ NILTSI_SUMMON: ItemData(SUMMON, 226 + summon_index_offset, ItemClassification.useful),
+ IOSKE_SUMMON: ItemData(SUMMON, 227 + summon_index_offset, ItemClassification.useful),
+ COYOTE_SUMMON: ItemData(SUMMON, 228 + summon_index_offset, ItemClassification.useful),
+ "Summon - Pinga": ItemData(SUMMON, 230 + summon_index_offset, ItemClassification.useful, 0), #(0 in pool bc you start with Pinga as a summoner)
+ TIRA_SUMMON: ItemData(SUMMON, 231 + summon_index_offset, ItemClassification.useful),
+ JUSES_SUMMON: ItemData(SUMMON, 232 + summon_index_offset, ItemClassification.useful),
+ PAH_SUMMON: ItemData(SUMMON, 234 + summon_index_offset, ItemClassification.useful),
+
+ #Monster Abilities for Scholar
+ ROOST: ItemData(SCHOLAR_ABILITY, 25 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ LUCKY_DICE: ItemData(SCHOLAR_ABILITY, 70 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ SUN_BATH: ItemData(SCHOLAR_ABILITY, 101 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ SLEEP_AURA: ItemData(SCHOLAR_ABILITY, 186 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ REGENERATE: ItemData(SCHOLAR_ABILITY, 197 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ REVERSE_POLARITY: ItemData(SCHOLAR_ABILITY, 198 + scholar_index_offset, ItemClassification.progression), #left in pool so you can merc Gran
+ BARRIER: ItemData(SCHOLAR_ABILITY, 199 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ MP_SICKLE: ItemData(SCHOLAR_ABILITY, 200 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ ADRENALINE: ItemData(SCHOLAR_ABILITY, 202 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ FIRE_BREATH: ItemData(SCHOLAR_ABILITY, 205 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ EXPLODE: ItemData(SCHOLAR_ABILITY, 206 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ WHIRLWIND: ItemData(SCHOLAR_ABILITY, 207 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ ATMOSHEAR: ItemData(SCHOLAR_ABILITY, 213 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ BUILD_LIFE: ItemData(SCHOLAR_ABILITY, 245 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ AERO: ItemData(SCHOLAR_ABILITY, 264 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ INSULT: ItemData(SCHOLAR_ABILITY, 363 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ INFUSION: ItemData(SCHOLAR_ABILITY, 364 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ OVERlOAD: ItemData(SCHOLAR_ABILITY, 365 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ REFLECTION: ItemData(SCHOLAR_ABILITY, 366 + scholar_index_offset, ItemClassification.useful, 0, 1),
+ LIFEGIVER: ItemData(SCHOLAR_ABILITY, 376 + scholar_index_offset, ItemClassification.useful, 0, 1),
+
+ #Traps
+ "Trap - Dialog Trap": ItemData(TRAP, 1 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Crag Demon Trap": ItemData(TRAP, 2 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Clothes Fall Off Trap": ItemData(TRAP, 3 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Oregon Trap": ItemData(TRAP, 4 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Teleport Trap": ItemData(TRAP, 5 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Moon Jump Trap": ItemData(TRAP, 6 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Snail Jump Trap": ItemData(TRAP, 7 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Dismount Trap": ItemData(TRAP, 8 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Dunk Tank Trap": ItemData(TRAP, 9 + trap_index_offset, ItemClassification.trap, 0),
+ "Trap - Trap Pack Trap": ItemData(TRAP, 10 + trap_index_offset, ItemClassification.trap, 0),
+}
+
+progressive_equipment: Tuple[str, ...] = (
+ "Equipment - Progressive 1H Sword",
+ "Equipment - Progressive 2H Sword",
+ "Equipment - Progressive 1H Axe",
+ "Equipment - Progressive 2H Axe",
+ "Equipment - Progressive Dagger",
+ "Equipment - Progressive Ninja Dagger",
+ "Equipment - Progressive Rapier",
+ "Equipment - Progressive Katana",
+ "Equipment - Progressive Spear",
+ "Equipment - Progressive Scythe",
+ "Equipment - Progressive Bow",
+ "Equipment - Progressive Staff",
+ "Equipment - Progressive Beating Staff",
+ "Equipment - Progressive Wand",
+ "Equipment - Progressive Book",
+ "Equipment - Progressive Shield",
+ "Equipment - Progressive Heavy Head",
+ "Equipment - Progressive Heavy Body",
+ "Equipment - Progressive Medium Head",
+ "Equipment - Progressive Medium Body",
+ "Equipment - Progressive Light Head",
+ "Equipment - Progressive Light Body",
+ )
+
+non_progressive_equipment: Tuple[str, ...] = (
+ #Weapons
+ #Swords
+ "Equipment - Iron Sword",
+ "Equipment - Contract",
+ "Equipment - Help the Prince",
+ "Equipment - Craftwork Sword",
+ "Equipment - Broadsword",
+ "Equipment - Sharp Sword",
+ #"Equipment - Silver Sword",
+ "Equipment - Boomer Sword",
+ #"Equipment - Digested Sword",
+ "Equipment - Cutlass",
+ "Equipment - Cold Touch",
+ #"Equipment - Burning Blade",
+ #"Equipment - Gold Sword",
+ "Equipment - Bloodbind",
+ "Equipment - Temporal Blade",
+ #"Equipment - Hydra Edge",
+ "Equipment - Defender",
+ "Equipment - Conquest",
+ "Equipment - Flame Sword",
+ "Equipment - Rune Sword",
+ #"Equipment - Auduril",
+ #"Equipment - Training Sword",
+ #"Equipment - Life Line",
+ "Equipment - Soul Keeper",
+ "Equipment - Crabs Claw",
+ "Equipment - Kings Guard",
+ #"Equipment - Diamond Sword",
+ #"Equipment - Balrog",
+ "Equipment - Oily Sword",
+
+ #Axes
+ "Equipment - Craftwork Axe",
+ "Equipment - Cleaver",
+ "Equipment - Hunting Axe",
+ #"Equipment - Silver Axe",
+ "Equipment - Hatchet",
+ #"Equipment - Axe of Light",
+ #"Equipment - Gold Axe",
+ "Equipment - Gaia Axe",
+ #"Equipment - Ancient Axe",
+ #"Equipment - Master Bigaxe",
+ "Equipment - Aphotic Edge",
+ #"Equipment - Diamond Axe",
+ "Equipment - Decapitator",
+ "Equipment - Ragebringer",
+
+ #Daggers
+ "Equipment - Stabbers",
+ "Equipment - Poisonkiss",
+ "Equipment - Craftwork Dagger",
+ "Equipment - Tanto",
+ "Equipment - Butterfly",
+ "Equipment - Ambush Knife",
+ #"Equipment - Silver Dagger",
+ "Equipment - Parry Knife",
+ "Equipment - Butter Cutter",
+ "Equipment - Soul Kris",
+ #"Equipment - Gouger",
+ "Equipment - Cinquedea",
+ #"Equipment - Gold Dagger",
+ #"Equipment - Kowakizashi",
+ #"Equipment - Bone Knife",
+ "Equipment - Flamespike",
+ "Equipment - Sange",
+ "Equipment - Yasha",
+ #"Equipment - Legend Spike",
+ #"Equipment - Eclipse",
+ #"Equipment - Mage Masher",
+ "Equipment - Mages Pike",
+ #"Equipment - Diamond Dagger",
+
+ #Rapiers
+ "Equipment - Toothpick",
+ "Equipment - Craftwork Rapier",
+ "Equipment - Fish Skewer",
+ #"Equipment - Silver Rapier",
+ "Equipment - Dueller",
+ "Equipment - Fleuret",
+ #"Equipment - Gold Rapier",
+ "Equipment - Windsong",
+ "Equipment - Nightingale",
+ "Equipment - Chartreuse",
+ "Equipment - Murgleys",
+ #"Equipment - Diamond Rapier",
+
+ #Katanas
+ "Equipment - Craftwork Katana",
+ "Equipment - Tachi",
+ #"Equipment - Silver Katana",
+ #"Equipment - Gold Katana",
+ #"Equipment - Hokuken",
+ #"Equipment - Ichimonji",
+ "Equipment - Muramasa",
+ #"Equipment - Diamond Katana",
+
+ #Spears
+ "Equipment - Craftwork Spear",
+ "Equipment - Skewer",
+ "Equipment - Prodder",
+ #"Equipment - Silver Spear",
+ "Equipment - Trident",
+ "Equipment - Halberd",
+ #"Equipment - Gold Spear",
+ "Equipment - Radiance",
+ "Equipment - Partizan",
+ #"Equipment - Incursier",
+ "Equipment - Royal Guard",
+ #"Equipment - Gungnir",
+ #"Equipment - Diamond Spear",
+
+ #Scythes
+ "Equipment - Battle Scythe",
+ "Equipment - Craftwork Scythe",
+ #"Equipment - Wind Sickle",
+ #"Equipment - Silver Scythe",
+ "Equipment - Grim Scythe",
+ "Equipment - Frost Reaper",
+ #"Equipment - Gold Scythe",
+ #"Equipment - Ember Scythe",
+ "Equipment - Gravedigger",
+ "Equipment - Wind Thresher",
+ #"Equipment - Adjudicator",
+ #"Equipment - Twilight",
+ #"Equipment - Arctic Chill",
+ #"Equipment - Diamond Scythe",
+
+ #Bows
+ "Equipment - Craftwork Bow",
+ "Equipment - Hunting Bow",
+ #"Equipment - Silver Bow",
+ #"Equipment - Habins Bow",
+ #"Equipment - Elven Bow",
+ #"Equipment - Gold Bow",
+ #"Equipment - Spore Shooter",
+ "Equipment - Siege Bow",
+ "Equipment - Rune Bow",
+ #"Equipment - Panakeia",
+ "Equipment - Dream Hunter",
+ #"Equipment - Diamond Bow",
+
+ #Staves
+ "Equipment - Cedar Staff",
+ "Equipment - Craftwork Staff",
+ "Equipment - Bone Smasher",
+ "Equipment - Iron Rod",
+ "Equipment - Walking Stick",
+ #"Equipment - Silver Staff",
+ "Equipment - Knockout Stick",
+ #"Equipment - Future Sight",
+ #"Equipment - Digested Staff",
+ "Equipment - Life Jewel",
+ #"Equipment - Gold Staff",
+ "Equipment - Apprentice",
+ "Equipment - Sages Walker",
+ #"Equipment - Beats Stick",
+ "Equipment - Staff of Balance",
+ "Equipment - Judgement",
+ #"Equipment - Diamond Staff",
+
+ #Wands
+ "Equipment - Cedar Wand",
+ "Equipment - Torch",
+ #"Equipment - Ink Stick",
+ "Equipment - Craftwork Wand",
+ "Equipment - Static Rod",
+ #"Equipment - Silver Wand",
+ "Equipment - Storm Rod",
+ "Equipment - Cursegiver",
+ #"Equipment - Gold Wand",
+ "Equipment - Rune Wand",
+ "Equipment - Stardust Wand",
+ #"Equipment - Aura Focus",
+ "Equipment - Paladin Wand",
+ #"Equipment - Obelisk",
+ "Equipment - Flameseeker",
+ #"Equipment - Diamond Wand",
+
+ #Books
+ "Equipment - Craftwork Pages",
+ "Equipment - Gospel",
+ "Equipment - Paypirbak",
+ "Equipment - Art of War",
+ #"Equipment - Silver Pages",
+ "Equipment - Blank Pages",
+ "Equipment - Tome of Light",
+ #"Equipment - Gold Pages",
+ "Equipment - Dark Gospel",
+ "Equipment - Malifice",
+ #"Equipment - Codex",
+ #"Equipment - Diamond Pages",
+
+ #Armor
+ #Shields
+ "Equipment - Stout Shield",
+ "Equipment - Iron Guard",
+ "Equipment - Stalwart Shield",
+ "Equipment - Craftwork Shield",
+ "Equipment - Lucky Platter",
+ "Equipment - Boomer Shield",
+ #"Equipment - Silver Shield",
+ #"Equipment - Blood Shield",
+ "Equipment - The Immovable",
+ "Equipment - Mages Platter",
+ #"Equipment - Gold Shield",
+ "Equipment - Flame Guard",
+ "Equipment - Wizards Wall",
+ "Equipment - Tower Shield",
+ "Equipment - Nomads Guard",
+ #"Equipment - Ether Shield",
+ "Equipment - Mirror Shield",
+ #"Equipment - Diamond Shield",
+
+ #Heavy Head
+ "Equipment - Storm Helm",
+ "Equipment - Craftwork Helm",
+ "Equipment - Iron Helm",
+ "Equipment - Battle Helm",
+ #"Equipment - Silver Helm",
+ "Equipment - Horned Helm",
+ #"Equipment - Gold Helm",
+ "Equipment - Insignia Helm",
+ "Equipment - Demon Helm",
+ #"Equipment - Guts Busby",
+ #"Equipment - Raid Helm",
+ "Equipment - Spellsword Helm",
+ #"Equipment - Diamond Helm",
+
+ #Heavy Body
+ "Equipment - Ring Mail",
+ "Equipment - Plate of Wolf",
+ "Equipment - Craftwork Mail",
+ "Equipment - Iron Armor",
+ "Equipment - Battleplate",
+ #"Equipment - Silver Mail",
+ #"Equipment - Plate of Tiger",
+ "Equipment - Knights Plate",
+ "Equipment - Bone Mail",
+ #"Equipment - Gold Mail",
+ "Equipment - Sky Armor",
+ "Equipment - Plate of Lion",
+ "Equipment - Demon Plate",
+ #"Equipment - Construct Mail",
+ #"Equipment - Guardian Angel",
+ "Equipment - Plate of Whale",
+ "Equipment - Lunar Mail",
+ #"Equipment - Diamond Mail",
+ "Equipment - Warrior Mail",
+ "Equipment - Warlock Mail",
+ "Equipment - Aegis Mail",
+ "Equipment - Reaper Mail",
+ "Equipment - Samurai Mail",
+ "Equipment - Valkyrie Mail",
+ "Equipment - Beastmaster Mail",
+ "Equipment - Mimic Mail",
+
+ #Medium Head
+ "Equipment - Storm Cap",
+ "Equipment - Headgear",
+ "Equipment - Craftwork Cap",
+ "Equipment - Spore Blocker",
+ #"Equipment - Silver Cap",
+ "Equipment - Red Cap",
+ "Equipment - Suitor Hat",
+ #"Equipment - Gold Cap",
+ #"Equipment - Red Hat",
+ "Equipment - Pirate Hat",
+ "Equipment - Battle Band",
+ "Equipment - Captains Hat",
+ #"Equipment - Diamond Cap",
+
+ #Medium Body
+ "Equipment - Craftwork Vest",
+ "Equipment - Smelly Gi",
+ "Equipment - Training Gi",
+ "Equipment - Tuxedo",
+ #"Equipment - Silver Vest",
+ "Equipment - Red Coat",
+ #"Equipment - Bandage Wrap",
+ "Equipment - Gaia Vest",
+ "Equipment - Brigandine",
+ #"Equipment - Gold Vest",
+ #"Equipment - Onion Gi",
+ "Equipment - Judo Gi",
+ "Equipment - Shadow Gi",
+ #"Equipment - Rex Vest",
+ #"Equipment - Slime Coat",
+ #"Equipment - Diamond Vest",
+ "Equipment - Monk Vest",
+ "Equipment - Rogue Vest",
+ "Equipment - Fencer Vest",
+ "Equipment - Hunter Vest",
+ "Equipment - Ninja Vest",
+ "Equipment - Nomad Vest",
+ "Equipment - Beatsmith Vest",
+ "Equipment - Assassin Vest",
+
+ #Light Head
+ "Equipment - Cotton Hood",
+ "Equipment - Storm Hood",
+ "Equipment - Craftwork Crown",
+ "Equipment - Circlet",
+ "Equipment - Woven Hood",
+ #"Equipment - Silver Crown",
+ #"Equipment - Fairys Crown",
+ #"Equipment - Blood Hat",
+ "Equipment - Plague Mask",
+ "Equipment - Guard Crown",
+ #"Equipment - Gold Crown",
+ "Equipment - Bronze Hairpin",
+ "Equipment - Ravens Hood",
+ "Equipment - Celestial Crown",
+ "Equipment - Pointy Hat",
+ "Equipment - Vita Crown",
+ #"Equipment - Pact Crown",
+ "Equipment - Protector",
+ #"Equipment - Diamond Crown",
+
+ #Light Body
+ "Equipment - Mages Robe",
+ "Equipment - Swimmers Top",
+ "Equipment - Craftwork Robe",
+ "Equipment - Dress",
+ "Equipment - Woven Shirt",
+ #"Equipment - Silver Cape",
+ #"Equipment - Cosplay Garb",
+ "Equipment - Shelter Dress",
+ #"Equipment - Gold Robe",
+ "Equipment - Blue Cape",
+ "Equipment - Seekers Garb",
+ "Equipment - Ravens Cloak",
+ "Equipment - Archmage Vest",
+ #"Equipment - Saviors Cape",
+ "Equipment - Assassins Cloak",
+ "Equipment - Stealth Cape",
+ #"Equipment - Shell Gown",
+ #"Equipment - Diamond Robe",
+ "Equipment - Cleric Robe",
+ "Equipment - Wizard Robe",
+ "Equipment - Shaman Robe",
+ "Equipment - Scholar Robe",
+ "Equipment - Chemist Robe",
+ "Equipment - Dervish Robe",
+ "Equipment - Weaver Robe",
+ "Equipment - Summoner Robe",
+
+ #Accessories aren't replaced by progressive items
+
+ #Shop Equipment
+ # Weapons
+ # Swords
+ "Equipment - Short Sword",
+ "Equipment - Razor Edge",
+ "Equipment - Artisan Sword",
+ "Equipment - Longsword",
+ "Equipment - Scimitar",
+ "Equipment - War Sword",
+ "Equipment - Highland Blade",
+ "Equipment - Crystal Sword",
+ "Equipment - Master Sword",
+ "Equipment - Master Bigsword",
+
+ # Axes
+ "Equipment - Hand Axe",
+ "Equipment - Chopper",
+ "Equipment - Stone Splitter",
+ "Equipment - Broadaxe",
+ "Equipment - Artisan Axe",
+ "Equipment - War Axe",
+ "Equipment - Berserker Axe",
+ "Equipment - Master Axe",
+ "Equipment - Ancient Axe",
+ "Equipment - Master Bigaxe",
+
+ # Daggers
+ "Equipment - Dirk",
+ "Equipment - Fishgutter",
+ "Equipment - Shank",
+ "Equipment - Kris",
+ "Equipment - Rondel",
+ "Equipment - Artisan Dagger",
+ "Equipment - Janbiya",
+ "Equipment - Sai",
+ "Equipment - Kodachi",
+ "Equipment - Fanged Knife",
+ "Equipment - Poignard",
+ "Equipment - Master Dagger",
+
+ # Rapiers
+ "Equipment - Rapier",
+ "Equipment - Stinger",
+ "Equipment - Estoc",
+ "Equipment - Scarlette",
+ "Equipment - Artisan Rapier",
+ "Equipment - Vulture",
+ "Equipment - Falcon Dance",
+ "Equipment - Epee",
+ "Equipment - Master Rapier",
+
+ # Katanas
+ "Equipment - Artisan Katana",
+ "Equipment - Nansen",
+ "Equipment - Mitsutada",
+ "Equipment - Hitofuri",
+ "Equipment - Kokaiji",
+ "Equipment - Tomokirimaru",
+ "Equipment - Master Katana",
+
+ # Spears
+ "Equipment - Short Spear",
+ "Equipment - Javelin",
+ "Equipment - Artisan Spear",
+ "Equipment - Wind Lance",
+ "Equipment - Voulge",
+ "Equipment - Master Spear",
+
+ # Scythes
+ "Equipment - War Scythe",
+ "Equipment - Artisan Scythe",
+ "Equipment - Thresher",
+ "Equipment - Great Thresher",
+ "Equipment - Master Scythe",
+
+ # Bows
+ "Equipment - Short Bow",
+ "Equipment - Long Bow",
+ "Equipment - Artisan Bow",
+ "Equipment - Battle Bow",
+ "Equipment - Composite Bow",
+ "Equipment - Razor Bow",
+ "Equipment - War Bow",
+ "Equipment - Master Bow",
+ "Equipment - Artemis",
+
+ # Staves
+ "Equipment - Short Staff",
+ "Equipment - Gnarled Root",
+ "Equipment - Quarterstaff",
+ "Equipment - Maplewood",
+ "Equipment - Artisan Staff",
+ "Equipment - Skullbasher",
+ "Equipment - Battle Staff",
+ "Equipment - Natures Gift",
+ "Equipment - War Staff",
+ "Equipment - Master Staff",
+
+ # Wands
+ "Equipment - Ash Wand",
+ "Equipment - Oak Wand",
+ "Equipment - Soul Wand",
+ "Equipment - Maple Wand",
+ "Equipment - Artisan Wand",
+ "Equipment - Baton",
+ "Equipment - Effigy",
+ "Equipment - Sentinel Rod",
+ "Equipment - Master Wand",
+
+ # Books
+ "Equipment - Moby Dick",
+ "Equipment - Orylei",
+ "Equipment - Encyclopedia",
+ "Equipment - Artisan Pages",
+ "Equipment - Grimoire",
+ "Equipment - Hydrology",
+ "Equipment - Divination",
+ "Equipment - Master Pages",
+
+ # Armor
+ # Shields
+ "Equipment - Buckler",
+ "Equipment - Vanguard",
+ "Equipment - Duelling Shield",
+ "Equipment - Artisan Shield",
+ "Equipment - Cross Shield",
+ "Equipment - Brass Cross",
+ "Equipment - Cross Guard",
+ "Equipment - Bulkwark",
+ "Equipment - Master Shield",
+ "Equipment - Turtle Shell",
+
+ # Heavy Head
+ "Equipment - Chain Helm",
+ "Equipment - Sturdy Helm",
+ "Equipment - Copper Helm",
+ "Equipment - Bronze Helm",
+ "Equipment - Scale Helm",
+ "Equipment - Artisan Helm",
+ "Equipment - Orion Barbut",
+ "Equipment - Iron Barbut",
+ "Equipment - Cross Helm",
+ "Equipment - Master Helm",
+
+ # Heavy Body
+ "Equipment - Breastplate",
+ "Equipment - Copper Suit",
+ "Equipment - Bronze Suit",
+ "Equipment - Scale Mail",
+ "Equipment - Artisan Mail",
+ "Equipment - Orion Armor",
+ "Equipment - Plate Mail",
+ "Equipment - Dragon Mail",
+ "Equipment - Master Mail",
+
+ # Medium Head
+ "Equipment - Leather Cap",
+ "Equipment - Beret",
+ "Equipment - Rugged Hat",
+ "Equipment - Vikings Hat",
+ "Equipment - Artisan Cap",
+ "Equipment - Combat Band",
+ "Equipment - Bandana",
+ "Equipment - Tall Tall Hat",
+ "Equipment - Master Cap",
+ "Equipment - Red Headgear",
+
+ # Medium Body
+ "Equipment - Leather Outfit",
+ "Equipment - Studded Armor",
+ "Equipment - Leather Mail",
+ "Equipment - Chain Vest",
+ "Equipment - Combat Vest",
+ "Equipment - Artisan Vest",
+ "Equipment - Power Vest",
+ "Equipment - Drifters Vest",
+ "Equipment - Martial Vest",
+ "Equipment - Master Vest",
+ "Equipment - Quintar Pelt",
+
+ # Light Head
+ "Equipment - Hemp Hood",
+ "Equipment - Holy Hat",
+ "Equipment - Silk Hat",
+ "Equipment - Holy Miter",
+ "Equipment - Artisan Crown",
+ "Equipment - Clerics Hood",
+ "Equipment - Wizards Hat",
+ "Equipment - Quilted Hat",
+ "Equipment - Regen Crown",
+ "Equipment - Master Crown",
+
+ # Light Body
+ "Equipment - Hemp Robe",
+ "Equipment - Cotton Robe",
+ "Equipment - Priest Garb",
+ "Equipment - Silk Shirt",
+ "Equipment - Artisan Shirt",
+ "Equipment - Wizards Robe",
+ "Equipment - Clerics Robe",
+ "Equipment - Sturdy Cape",
+ "Equipment - Winter Cape",
+ "Equipment - Master Cape",
+
+ #Accessories aren't replaced by progressive items
+)
+
+# shop_accessories: Tuple[str, ...] = (
+# "Equipment - Prayer Beads",
+# "Equipment - Samurais Glove",
+# "Equipment - Casters Ring",
+# "Equipment - Fearsome Ring",
+# "Equipment - Crit Fang",
+# "Equipment - Dancing Shoes",
+# # "Equipment - Bulk Belt",
+# # "Equipment - Poison Talon",
+# # "Equipment - First Strike Mitt",
+# # "Equipment - Menders Ring",
+# # "Equipment - Kitsune Mask",
+# # "Equipment - Pact Ring",
+# # "Equipment - Gusto Fang",
+# # "Equipment - Winter Mitten"
+# )
+
+optional_scholar_abilities: Tuple[str, ...] = (
+ ROOST,
+ LUCKY_DICE,
+ SUN_BATH,
+ SLEEP_AURA,
+ REGENERATE,
+ #"Scholar - Reverse Polarity" leaving this one always in the pool so you can merc Gran
+ BARRIER,
+ MP_SICKLE,
+ ADRENALINE,
+ FIRE_BREATH,
+ EXPLODE,
+ WHIRLWIND,
+ ATMOSHEAR,
+ BUILD_LIFE,
+ AERO,
+ INSULT,
+ INFUSION,
+ OVERlOAD,
+ REFLECTION,
+ LIFEGIVER
+)
+
+default_starting_job_list: List[str] = [
+ WARRIOR_JOB,
+ MONK_JOB,
+ ROGUE_JOB,
+ CLERIC_JOB,
+ WIZARD_JOB,
+ WARLOCK_JOB,
+]
+
+job_crystal_beginner_dictionary: Dict[str, str] = {
+ FENCER_JOB: THE_PALE_GROTTO_DISPLAY_NAME + FENCER_JOB_CRYSTAL_LOCATION,
+ SHAMAN_JOB: DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + SHAMAN_JOB_CRYSTAL_LOCATION,
+ SCHOLAR_JOB: YAMAGAWA_MA_DISPLAY_NAME + SCHOLAR_JOB_CRYSTAL_LOCATION,
+ AEGIS_JOB: SKUMPARADISE_DISPLAY_NAME + AEGIS_JOB_CRYSTAL_LOCATION,
+}
+
+job_crystal_advanced_dictionary: Dict[str, str] = {
+ HUNTER_JOB: QUINTAR_NEST_DISPLAY_NAME + HUNTER_JOB_CRYSTAL_LOCATION,
+ CHEMIST_JOB: QUINTAR_SANCTUM_DISPLAY_NAME + CHEMIST_JOB_CRYSTAL_LOCATION,
+ REAPER_JOB: CAPITAL_JAIL_DISPLAY_NAME + REAPER_JOB_CRYSTAL_LOCATION,
+ NINJA_JOB: OKIMOTO_NS_DISPLAY_NAME + NINJA_JOB_CRYSTAL_LOCATION,
+ NOMAD_JOB: RIVER_CATS_EGO_AP_REGION + NOMAD_JOB_CRYSTAL_LOCATION,
+ DERVISH_JOB: ANCIENT_RESERVOIR_DISPLAY_NAME + DERVISH_JOB_CRYSTAL_LOCATION,
+ BEATSMITH_JOB: CAPITAL_SEQUOIA_DISPLAY_NAME + BEATSMITH_JOB_CRYSTAL_LOCATION,
+}
+
+job_crystal_expert_dictionary: Dict[str, str] = {
+ SAMURAI_JOB: SHOUDU_PROVINCE_DISPLAY_NAME + SAMURAI_JOB_CRYSTAL_LOCATION,
+ ASSASSIN_JOB: THE_UNDERCITY_DISPLAY_NAME + ASSASSIN_JOB_CRYSTAL_LOCATION,
+ VALKYRIE_JOB: BEAURIOR_VOLCANO_DISPLAY_NAME + VALKYRIE_JOB_CRYSTAL_LOCATION,
+ SUMMONER_JOB: SLIP_GLIDE_RIDE_DISPLAY_NAME + SUMMONER_JOB_CRYSTAL_LOCATION,
+ BEASTMASTER_JOB: CASTLE_RAMPARTS_DISPLAY_NAME + BEASTMASTER_JOB_CRYSTAL_LOCATION,
+ WEAVER_JOB: JIDAMBA_EACLANEYA_DISPLAY_NAME + WEAVER_JOB_CRYSTAL_LOCATION,
+ MIMIC_JOB: THE_CHALICE_OF_TAR_DISPLAY_NAME + MIMIC_JOB_CRYSTAL_LOCATION,
+}
+
+key_rings: Tuple[str, ...] = (
+ PRISON_KEY_RING,
+ BEAURIOR_KEY_RING,
+ SLIP_GLIDE_RIDE_KEY_RING,
+ ICE_PUZZLE_KEY_RING,
+ JIDAMBA_KEY_RING,
+)
+
+dungeon_keys: Tuple[str, ...] = (
+ CELL_KEY,
+ SOUTH_WING_KEY,
+ EAST_WING_KEY,
+ WEST_WING_KEY,
+ DARK_WING_KEY,
+ SMALL_KEY,
+ BEAURIOR_BOSS_KEY,
+ RED_DOOR_KEY,
+ ICE_PUZZLE_KEY,
+ FOLIAGE_KEY,
+ CAVE_KEY,
+ CANOPY_KEY
+)
+
+singleton_keys: Tuple[str, ...] = (
+ GARDENERS_KEY,
+ COURTYARD_KEY,
+ LUXURY_KEY,
+ ROOM_ONE_KEY,
+ PYRAMID_KEY,
+ TRAM_KEY,
+ ICE_CELL_KEY,
+ RAMPART_KEY,
+ FORGOTTEN_KEY
+)
+
+display_region_name_to_pass_dict: Dict[str, str] = {
+ #Beginner
+ SPAWNING_MEADOWS_DISPLAY_NAME: SPAWNING_MEADOWS_PASS,
+ DELENDE_DISPLAY_NAME: DELENDE_PASS,
+ SOILED_DEN_DISPLAY_NAME: SOILED_DEN_PASS,
+ THE_PALE_GROTTO_DISPLAY_NAME: THE_PALE_GROTTO_PASS,
+ SEASIDE_CLIFFS_DISPLAY_NAME: SEASIDE_CLIFFS_PASS,
+ DRAFT_SHAFT_CONDUIT_DISPLAY_NAME: DRAFT_SHAFT_CONDUIT_PASS,
+ MERCURY_SHRINE_DISPLAY_NAME: MERCURY_SHRINE_PASS,
+ YAMAGAWA_MA_DISPLAY_NAME: YAMAGAWA_MA_PASS,
+ PROVING_MEADOWS_DISPLAY_NAME: PROVING_MEADOWS_PASS,
+ SKUMPARADISE_DISPLAY_NAME: SKUMPARADISE_PASS,
+ #Advanced
+ CAPITAL_SEQUOIA_DISPLAY_NAME: CAPITAL_SEQUOIA_PASS,
+ JOJO_SEWERS_DISPLAY_NAME: JOJO_SEWERS_PASS,
+ BOOMER_SOCIETY_DISPLAY_NAME: BOOMER_SOCIETY_PASS,
+ ROLLING_QUINTAR_FIELDS_DISPLAY_NAME: ROLLING_QUINTAR_FIELDS_PASS,
+ QUINTAR_NEST_DISPLAY_NAME: QUINTAR_NEST_PASS,
+ QUINTAR_SANCTUM_DISPLAY_NAME: QUINTAR_SANCTUM_PASS,
+ CAPITAL_JAIL_DISPLAY_NAME: CAPITAL_JAIL_PASS,
+ CAPITAL_PIPELINE_DISPLAY_NAME: CAPITAL_PIPELINE_PASS,
+ COBBLESTONE_CRAG_DISPLAY_NAME: COBBLESTONE_CRAG_PASS,
+ OKIMOTO_NS_DISPLAY_NAME: OKIMOTO_NS_PASS,
+ GREENSHIRE_REPRISE_DISPLAY_NAME: GREENSHIRE_REPRISE_PASS,
+ SALMON_PASS_DISPLAY_NAME: SALMON_PASS_PASS,
+ SALMON_RIVER_DISPLAY_NAME: SALMON_RIVER_PASS,
+ SHOUDU_WATERFRONT_DISPLAY_NAME: SHOUDU_WATERFRONT_PASS,
+ POKO_POKO_DESERT_DISPLAY_NAME: POKO_POKO_DESERT_PASS,
+ SARA_SARA_BAZAAR_DISPLAY_NAME: SARA_SARA_BAZAAR_PASS,
+ SARA_SARA_BEACH_EAST_DISPLAY_NAME: SARA_SARA_BEACH_EAST_PASS,
+ SARA_SARA_BEACH_WEST_DISPLAY_NAME: SARA_SARA_BEACH_WEST_PASS,
+ ANCIENT_RESERVOIR_DISPLAY_NAME: ANCIENT_RESERVOIR_PASS,
+ SALMON_BAY_DISPLAY_NAME: SALMON_BAY_PASS,
+ #Expert
+ THE_OPEN_SEA_DISPLAY_NAME: THE_OPEN_SEA_PASS,
+ SHOUDU_PROVINCE_DISPLAY_NAME: SHOUDU_PROVINCE_PASS,
+ THE_UNDERCITY_DISPLAY_NAME: THE_UNDERCITY_PASS,
+ GANYMEDE_SHRINE_DISPLAY_NAME: GANYMEDE_SHRINE_PASS,
+ BEAURIOR_VOLCANO_DISPLAY_NAME: BEAURIOR_VOLCANO_PASS,
+ BEAURIOR_ROCK_DISPLAY_NAME: BEAURIOR_ROCK_PASS,
+ LAKE_DELENDE_DISPLAY_NAME: LAKE_DELENDE_PASS,
+ QUINTAR_RESERVE_DISPLAY_NAME: QUINTAR_RESERVE_PASS,
+ DIONE_SHRINE_DISPLAY_NAME: DIONE_SHRINE_PASS,
+ QUINTAR_MAUSOLEUM_DISPLAY_NAME: QUINTAR_MAUSOLEUM_PASS,
+ EASTERN_CHASM_DISPLAY_NAME: EASTERN_CHASM_PASS,
+ TALL_TALL_HEIGHTS_DISPLAY_NAME: TALL_TALL_HEIGHTS_PASS,
+ NORTHERN_CAVE_DISPLAY_NAME: NORTHERN_CAVE_PASS,
+ LANDS_END_DISPLAY_NAME: LANDS_END_PASS,
+ SLIP_GLIDE_RIDE_DISPLAY_NAME: SLIP_GLIDE_RIDE_PASS,
+ SEQUOIA_ATHENAEUM_DISPLAY_NAME: SEQUOIA_ATHENAEUM_PASS,
+ NORTHERN_STRETCH_DISPLAY_NAME: NORTHERN_STRETCH_PASS,
+ CASTLE_RAMPARTS_DISPLAY_NAME: CASTLE_RAMPARTS_PASS,
+ THE_CHALICE_OF_TAR_DISPLAY_NAME: THE_CHALICE_OF_TAR_PASS,
+ FLYERS_CRAG_DISPLAY_NAME: FLYERS_CRAG_PASS,
+ JIDAMBA_TANGLE_DISPLAY_NAME: JIDAMBA_TANGLE_PASS,
+ JIDAMBA_EACLANEYA_DISPLAY_NAME: JIDAMBA_EACLANEYA_PASS,
+ THE_DEEP_SEA_DISPLAY_NAME: THE_DEEP_SEA_PASS,
+ NEPTUNE_SHRINE_DISPLAY_NAME: NEPTUNE_SHRINE_PASS,
+ JADE_CAVERN_DISPLAY_NAME: JADE_CAVERN_PASS,
+ CONTINENTAL_TRAM_DISPLAY_NAME: CONTINENTAL_TRAM_PASS,
+ #End Game
+ ANCIENT_LABYRINTH_DISPLAY_NAME: ANCIENT_LABYRINTH_PASS,
+ THE_SEQUOIA_DISPLAY_NAME: THE_SEQUOIA_PASS,
+ THE_DEPTHS_DISPLAY_NAME: THE_DEPTHS_PASS,
+ CASTLE_SEQUOIA_DISPLAY_NAME: CASTLE_SEQUOIA_PASS,
+ THE_OLD_WORLD_DISPLAY_NAME: THE_OLD_WORLD_PASS,
+ THE_NEW_WORLD_DISPLAY_NAME: THE_NEW_WORLD_PASS,
+}
+
+filler_items: Tuple[str, ...] = (
+ "Item - Tonic",
+ "Item - Potion",
+ "Item - Z-Potion",
+ "Item - Tincture",
+ "Item - Ether",
+ "Item - Zether",
+ "Item - Fenix Juice",
+ "Item - Fenix Syrup",
+ "Item - Nans Stew",
+ "Item - Nans Cocoa",
+ "Item - Nans Secret Recipe",
+ "Item - Nuts",
+ "Item - Milk",
+ "Item - Sweet Pop Candy",
+ "Item - Sour Pop Candy",
+ "Item - Bitter Pop Candy",
+ "Item - Decent Cod",
+ "Item - Fresh Salmon",
+ "Item - Scroll",
+ # add currency?
+)
+
+def get_item_names_per_category() -> Dict[str, Set[str]]:
+ categories: Dict[str, Set[str]] = {}
+
+ for name, data in item_table.items():
+ if data.category != "Events":
+ categories.setdefault(data.category, set()).add(name)
+
+ return categories
+
+def get_starting_jobs(world: "CrystalProjectWorld") -> List[str]:
+ if world.options.job_rando.value == world.options.job_rando.option_full:
+ return get_random_starting_jobs(world, world.options.starting_job_quantity.value)
+ else:
+ return default_starting_job_list
+
+def get_random_starting_jobs(self, count:int) -> List[str]:
+ if self.options.use_mods.value == self.options.use_mods.option_true:
+ return self.random.sample(list(self.item_name_groups[JOB]), count)
+ else:
+ return self.random.sample(list(self.base_game_jobs), count)
+
+def set_jobs_at_default_locations(world: "CrystalProjectWorld", player_name:str) -> Tuple[int, List[str]]:
+ job_crystal_dictionary: Dict[str, str] = job_crystal_beginner_dictionary.copy() #if we don't use copy it means updating job_crystal_dictionary messes with the beginner dict too
+ jobs_not_to_exclude: List[str] = []
+
+ if world.options.included_regions.value == world.options.included_regions.option_advanced:
+ job_crystal_dictionary.update(job_crystal_advanced_dictionary)
+
+ if (world.options.included_regions.value == world.options.included_regions.option_expert
+ or world.options.included_regions.value == world.options.included_regions.option_all):
+ job_crystal_dictionary.update(job_crystal_advanced_dictionary)
+ job_crystal_dictionary.update(job_crystal_expert_dictionary)
+
+ for job_name in job_crystal_dictionary:
+ try:
+ world.get_location(job_crystal_dictionary[job_name]).place_locked_item(world.create_item(job_name))
+ #message = "Placing" + job_name + " at " + job_crystal_dictionary[job_name]
+ #world.logger.info(message)
+ except KeyError:
+ jobs_not_to_exclude.append(job_name)
+ message = f"For player {player_name}: the crystal where {job_name} was placed was updated by a mod. It has been forced to be randomized instead of in its default location."
+ logging.getLogger().info(message)
+
+
return len(job_crystal_dictionary), jobs_not_to_exclude
\ No newline at end of file
diff --git a/worlds/crystal_project/locations.py b/worlds/crystal_project/locations.py
index 9377c0629f4a..e5bbbbe5942e 100644
--- a/worlds/crystal_project/locations.py
+++ b/worlds/crystal_project/locations.py
@@ -1,1990 +1,1990 @@
-from typing import List, Optional, Callable, NamedTuple, Dict, Set
-from BaseClasses import CollectionState
-from .options import CrystalProjectOptions
-from .rules import CrystalProjectLogic
-from .constants.jobs import *
-from .constants.keys import *
-from .constants.key_items import *
-from .constants.ap_regions import *
-from .constants.region_passes import *
-from .constants.display_regions import *
-from .constants.teleport_stones import *
-from .constants.crystal_locations import *
-from .constants.level_requirements import *
-
-class LocationData(NamedTuple):
- ap_region: str
- name: str
- code: int
- rule: Optional[Callable[[CollectionState], bool]] = None
- regionsanity: bool = False
-
-treasure_index_offset = 1
-npc_index_offset = 10000
-crystal_index_offset = 100000
-boss_index_offset = 1000000
-shop_index_offset = 10000000
-regionsanity_index_offset = 100000000
-
-def get_location_name_to_id() -> dict[str, int]:
- location_name_to_id = {location.name: location.code for location in get_treasure_and_npc_locations(-1, None)}
- crystal_name_to_id = {crystal.name: crystal.code for crystal in get_crystal_locations(-1, None)}
- boss_name_to_id = {boss.name: boss.code for boss in get_boss_locations(-1, None)}
- shop_name_to_id = {shop.name: shop.code for shop in get_shop_locations(-1, None)}
- region_completion_name_to_id = {region_completion.name: region_completion.code for region_completion in get_region_completion_locations(-1, None)}
- location_name_to_id.update(crystal_name_to_id)
- location_name_to_id.update(boss_name_to_id)
- location_name_to_id.update(shop_name_to_id)
- location_name_to_id.update(region_completion_name_to_id)
-
- return location_name_to_id
-
-def get_treasure_and_npc_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
- logic = CrystalProjectLogic(player, options)
-
- location_table: List[LocationData] = [
- #Zones (Beginner)
- #Spawning Meadows
- #Treasure chests
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - On cliff north of spawn", 101 + treasure_index_offset), #Money chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Under overpass", 292 + treasure_index_offset), #Money chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Jump on Nan", 41 + treasure_index_offset), #Burglars Glove chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Above waterfall", 17 + treasure_index_offset), #Cedar Staff chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Behind Nan house", 61 + treasure_index_offset), #Cedar Wand chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Promontory south of waterfall", 54 + treasure_index_offset), #Cleaver chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Jump on secret tunnel chest", 5 + treasure_index_offset), #Fenix Juice chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - On path to Delende", 49 + treasure_index_offset), #Fenix Juice chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Secret tunnel", 47 + treasure_index_offset), #Stabbers chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - On ledge jump from tree", 50 + treasure_index_offset), #Stout Shield chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Cross trees and jump down", 38 + treasure_index_offset), #Tincture chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - West of spawn", 1 + treasure_index_offset), #Tonic chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - In cave NW of spawn", 2 + treasure_index_offset), #Tonic chest
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Mountain summit jump on Nan", 1142 + treasure_index_offset), #Tonic Pouch chest
-
- #NPCs
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Pouch Nan", 53 + npc_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Secret Herb near Shaku", 627 + npc_index_offset), #Secret Herb 0 Fixed Missable
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Promontory south of waterfall Secret Herb", 297 + npc_index_offset), #(48, 112, -36) Secret Herb 1 Fixed Missable
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Secret Herb past mountain summit chest", 545 + npc_index_offset), #(79, 112, -30) Secret Herb 2 Fixed Missable
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Cross trees and jump down for Secret Herb", 546 + npc_index_offset), #(43, 104, -8) Secret Herb 3 Fixed Missable
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Map Nan", 84 + npc_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Nan Stew", 14 + npc_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo", 194 + npc_index_offset, lambda state: state.has(BLACK_SQUIRREL, player, 3)), #Tree Fairy NPC seems to have the dialogue for this (ID 194)
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on tree SW of spawn", 264 + npc_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on tree NW of spawn", 296 + npc_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on tree near lampposts", 110 + npc_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on Mario jump tree", 3085 + npc_index_offset),
-
- #Delende
- #Treasure chests
- #Delende Plains
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Fallen log parkour", 208 + treasure_index_offset), # Earring chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - On west mountainside", 209 + treasure_index_offset), # Iron Sword chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Off north path", 259 + treasure_index_offset), # Tonic chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Fish hatchery approach", 79 + treasure_index_offset), # Tincture chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In front of fish hatchery lower level", 210 + treasure_index_offset), # Money chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Up near hatchery", 169 + treasure_index_offset), # Protect Amulet chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In front of fish hatchery below tree", 2997 + treasure_index_offset), # Tonic Pouch chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In fish hatchery", 39 + treasure_index_offset), # Tincture chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Across river from fish hatchery", 123 + treasure_index_offset), # Looters Ring chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Return from fish hatchery", 34 + treasure_index_offset), # Bracer chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Under tree", 261 + treasure_index_offset), # Tincture chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Mushroom underpass", 262 + treasure_index_offset), # Cotton Hood chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In front of camp", 263 + treasure_index_offset), # Money chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Under ambush tree", 212 + treasure_index_offset), # Fenix Juice chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Outside spooky cave", 27 + treasure_index_offset), # Storm Hood chest
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Across river", 213 + treasure_index_offset), # Earring chest
- #Eastern Delende (High Bridges)
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Hollow riverbank", 43 + treasure_index_offset), # Underground Ether chest
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Before Proving Meadows", 216 + treasure_index_offset), # Tonic Pouch chest
- #Delende High Bridges
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - High up west mountainside", 33 + treasure_index_offset), #Mages Robe chest
- # Gran's House (Delende High Bridges)
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 1", 87 + treasure_index_offset), # (126, 128, -58) style: blank
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 2", 100 + treasure_index_offset), # (127, 128, -58) style: weapon
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 3", 177 + treasure_index_offset), # (137, 128, -57) style: consumable
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 4", 178 + treasure_index_offset), # (137, 128, -56) style: consumable
- # Basement (Delende High Bridges)
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Basement Chest - Gran...?", 179 + treasure_index_offset), # Empty chest
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Basement Chest - Gran......?", 180 + treasure_index_offset), # Digested Head chest
- # Below Gran
- LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Cracks in Gran's foundation", 3653 + treasure_index_offset), # (126, 115, -102) Basement map chest
- LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Gran's subbasement pair 1", 181 + treasure_index_offset), # (129, 98, -111) Fenix Juice Pouch chest
- LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Gran's subbasement pair 2", 182 + treasure_index_offset), # (128, 98, -111) Plate of Wolf chest
- LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Gran's subbasement loner", 3671 + treasure_index_offset), # (119, 98, -110) Underpass Scrap
- #Mesa Above Spooky Cave
- LocationData(DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Top of spooky cave", 73 + treasure_index_offset), #Tincture Pouch chest
- #Delende Peak
- LocationData(DELENDE_PEAK_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Troll", 451 + treasure_index_offset), #Tincture Pouch chest
- #Heart Tarn
- LocationData(HEART_TARN_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Looking for love in all the high places (glide to the heart tarn)", 1554 + treasure_index_offset, lambda state: logic.has_swimming(state)), # Chartreuse chest
-
- #NPCs
- #NPCs Shortcuts: shortcut girl (Z2_Collector Sister ID 3769 (169, 132, -89))
- #NPCs Shortcuts: Rabbit Claws shortcut guy (Z2_RoosterFeetGuy ID 74(281, 128, -159))
- #Delende Plains
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Astley gives you a home point stone", 28 + npc_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Not-at-all shady guy", 124 + npc_index_offset), # (181, 132, -200); Rotten Salmon
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Fish at the hatchery", 121 + npc_index_offset, lambda state: state.has("Item - Flimsy Rod", player) and state.has("Item - Plug Lure", player)), # Fisher (Z2_FisherOnDock ID 121 (166, 133, -208))
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dizzy noob chucks something at your face", 831 + npc_index_offset), # (276, 116, -204); Fervor Charm
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dog Bone Guy", 31 + npc_index_offset, lambda state: state.has(DOG_BONE, player, 3)),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dog Bone south of Soiled Den", 184 + npc_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dog Bone in spooky cave", 1915 + npc_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Cartographer", 1153 + npc_index_offset), # guy who gives you a map of Delende if you don't have one (Z2_MapMan (198, 131, -74)) Fixed Missable
-
- #Soiled Den
- #Treasure chests
- #Soiled Denlende
- LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - Riverside", 1155 + treasure_index_offset), # (249, 116, -156) Tonic Pouch chest
- #The Bangler
- LocationData(THE_BANGLER_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - Lurking in the shadows by the Bangler", 218 + treasure_index_offset), #(311, 111, -96) Clamshell chest
- LocationData(THE_BANGLER_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - By the Bangler", 271 + treasure_index_offset), #(322, 111, -101) Clamshell chest
- LocationData(THE_BANGLER_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - Long river jump", 448 + treasure_index_offset), #(326, 111, -116) Dodge Charm chest
-
- #NPCs
- #Soiled Denlende
- LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " NPC - Dog Bone among the bones and flowers", 176 + npc_index_offset), #(296, 112, -155)
-
- #Pale Grotto
- #Treasure chests
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Across from fish island", 228 + treasure_index_offset), #(#316, 120, -262) Fenix Juice chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - North from save point", 144 + treasure_index_offset), #(307, 124, -345) Poisonkiss chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Entrance river hop", 229 + treasure_index_offset), #Tonic chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - On promontory", 2979 + treasure_index_offset), #Tincture Pouch chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Island 1", 3622 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_glide(state)), #Underpass Scrap chest; somehow this is actually in the pale grotto and not the underpass
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Island 2", 3077 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_glide(state)), #Z-Potion Pouch chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Tucked behind path to temple", 267 + treasure_index_offset), #Tincture chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Jumping puzzle", 226 + treasure_index_offset), #Storm Helm chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - South of temple", 136 + treasure_index_offset), #Money chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Temple antechamber", 222 + treasure_index_offset), #Toothpick chest
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Temple sanctuary", 1154 + treasure_index_offset), #Pale Grotto map chest
- LocationData(THE_PALE_GROTTO_AP_REGION, "Underpass Chest - Blue flower ledge between Pale Grotto & Soiled Den", 3621 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(245, 116, -199) Underpass Scrap chest
-
- #NPCs
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " NPC - Reid gives you gently worn armor", 1166 + npc_index_offset), #Pale Grotto Temple map (Z2_ReidCamp (273, 122, -327)) gives you Ring Mail
-
- #Seaside Cliffs
- #Treasure chests
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - North across river from double giant box", 282 + treasure_index_offset), #Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Stonehenge", 150 + treasure_index_offset), #Bracer chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - ClamHaters Mulan jumping puzzle", 268 + treasure_index_offset), #Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - After ClamHater made a man out of you", 2981 + treasure_index_offset), #Tincture Pouch chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - South of ClamHater", 281 + treasure_index_offset), #Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - South of chest south of ClamHater", 286 + treasure_index_offset), #Tonic chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Climbing the seaside cliffs", 42 + treasure_index_offset), #Potion chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Three Amigos Uno", 1161 + treasure_index_offset), #Tonic chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Three Amigos Dos", 447 + treasure_index_offset), #Scope Bit chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Three Amigos Tres", 270 + treasure_index_offset), #Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Beneath encampment ledge", 217 + treasure_index_offset), #(310, 116, -68) Money chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - On jigsaw mountain 1", 449 + treasure_index_offset), #(213, 107, 27) Money chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - In cliffs nook south of encampment", 80 + treasure_index_offset), #(307, 113, -22) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Below west Delende entrance", 273 + treasure_index_offset), #(275,108,-28) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Above the eastern beach standing stones", 274 + treasure_index_offset), #(312, 95, 12) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Below jigsaw mountain", 275 + treasure_index_offset), #(223, 94, 26) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Island by the waterfalls", 277 + treasure_index_offset), #(259, 107, -18) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - East of the river above the beach", 278 + treasure_index_offset), #(281, 98, -3) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Along the eastern beach up the cliffs", 279 + treasure_index_offset), #(302, 101, 4) Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Parkour by the island waterfalls", 289 + treasure_index_offset), #(250, 104, -13) Fenix Juice chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - On jigsaw mountain 2", 157 + treasure_index_offset), #(218, 107, 23) Headgear chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - South of encampment on the canyon mountainside", 272 + treasure_index_offset), #(289, 110, -18) Jewel of Defense chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Downstream of the island waterfalls", 288 + treasure_index_offset), #(250, 98, -4) Tincture chest
- #Seaside Cliffs Beach
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - The little mermaid", 276 + treasure_index_offset), #Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Hop along the sea stacks south of the peninsula", 280 + treasure_index_offset), #Clamshell chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Peninsula past the standing stones jump puzzle", 205 + treasure_index_offset), #Storm Cap chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - MR SNIPS", 287 + treasure_index_offset), #Fenix Juice chest
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Atop sea stack east of the bay", 450 + treasure_index_offset), #Swimmers Top chest
- #Seaside Cliffs Valley
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Rocky cove down the lazy river", 269 + treasure_index_offset), #Clamshell chest
-
- #NPCs
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - ClamHater above the mist", 283 + npc_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - If you give a Manana Man a clam... (he will ask you for more)", 284 + npc_index_offset, lambda state: logic.has_enough_clamshells(state)),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - Diamond below the bay", 2896 + npc_index_offset, lambda state: logic.has_swimming(state)), #(343, 81, 0) Ore
- #Todo NPCs Job Masters: Seaside Cliffs Outpost map has Master Shaman ID 3572 (387, 155, -104); gives you Shaman Seal in exchange for job mastery
-
- #Draft Shaft Conduit
- #Treasure chests
- LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Chest - Straight shot", 82 + treasure_index_offset), #Torch chest
- LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Chest - Ring around the rosy", 81 + treasure_index_offset), #Tonic Pouch chest
-
- #Mercury Shrine
- #Treasure chests
- LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Chest - Pinnacle", 155 + treasure_index_offset, lambda state: state.has(MERCURY_STONE, player) or logic.has_vertical_movement(state)), #Contract chest
-
- #Yamagawa M.A.
- #Treasure chests
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Up first cliff", 2995 + treasure_index_offset), #Money chest
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Sneaky behind tree", 91 + treasure_index_offset), #Broadsword chest
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Tucked next to waterfall", 95 + treasure_index_offset), #Iron Guard chest
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Dead end", 3056 + treasure_index_offset), #Tonic chest
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Hidden stairway", 757 + treasure_index_offset), #Tonic Pouch chest
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Drop down to mountain balcony", 290 + treasure_index_offset), #Torpid Cuffs chest
- #Fencer's Keep Chest
- LocationData(FENCERS_KEEP_CHEST_AP_REGION, "Overpass Chest - Hop over from Yamagawa to dead tree by Fencers Keep", 3537 + treasure_index_offset), #(148, 151, -114) 6th Overpass Scrap on Overpass main map
-
- #NPCs
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " NPC - Hidden inside waterfall source", 628 + npc_index_offset, lambda state: logic.has_swimming(state)), #Autumns Oath
- #Todo NPCs Job Masters: Yamagawa M.A. Temple map has Master Scholar ID 3574 (59, 151, -98); gives you Scholar Seal in exchange for job mastery
-
- #Proving Meadows
- #Treasure chests
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Next to trial guard", 207 + treasure_index_offset), #Money chest
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Hop on shops to sneak behind waterfall", 258 + treasure_index_offset), #Battle Scythe chest
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Hidden behind the inn", 118 + treasure_index_offset), #Burglars Glove chest
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Tucked into waterfall nook", 2980 + treasure_index_offset), #Tincture Pouch chest
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Tarzan", 256 + treasure_index_offset), #Tonic chest
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - On the climb up outside Skumparadise", 193 + treasure_index_offset), #Tonic Pouch chest
-
- #NPCs
- #NPCs Blocker: this guy checks whether you have enough crystals to pass; this is a blocker guy not a location check guy
- #LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " NPC - Crystal Checker", 128 + npc_index_offset),
-
- #Skumparadise (we're smushing Trial Caves into there)
- #Treasure chests
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Stairs are lava", 126 + treasure_index_offset), #Stalwart Shield chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Shroom dodging", 120 + treasure_index_offset), #Help the Prince chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Ride the shroom", 670 + treasure_index_offset), #Awake Ring chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Wall niche", 671 + treasure_index_offset), #Awake Ring chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Smaller wall niche", 669 + treasure_index_offset), #Tincture Pouch chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Lava-loving shrooms", 684 + treasure_index_offset), #Tonic Pouch chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Behind the lava shroom colonnade", 685 + treasure_index_offset), #Mana Ring chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - There and back again", 683 + treasure_index_offset), #Sharp Sword chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Accompanied by yellow flower in tunnel", 1110 + treasure_index_offset), #Fenix Juice chest
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Behind boss", 332 + treasure_index_offset), #Money chest
-
- #Zones (Advanced)
- #Capital Sequoia (smushed Capital Courtyard in)
- #Treasure chests
- #Moat Shallows
- LocationData(MOAT_SHALLOWS_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Beyond the courtyard wall", 2671 + treasure_index_offset), #Tonic Pouch chest
- #Capital Sequoia
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Inn room", 1388 + treasure_index_offset), #Craftwork Staff chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Second story by Master Rogue", 158 + treasure_index_offset), #Craftwork Dagger chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Magic store attic", 1389 + treasure_index_offset), #Craftwork Scythe chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Training ground parkour", 1390 + treasure_index_offset), #Craftwork Katana chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Behind Luxury store", 2651 + treasure_index_offset), #Craftwork Cap chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Locked in Luxury Store storage 1", 1533 + treasure_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #Fenix Syrup Pouch chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Locked in Luxury Store storage 2", 1532 + treasure_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #Lucky Briefs chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Locked in Luxury Store storage 3", 1531 + treasure_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #Lucky Socks chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Inn attic by Master Monk", 2656 + treasure_index_offset), #Craftwork Vest chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - By Master Warrior atop the Luxury Store", 2655 + treasure_index_offset), #Craftwork Shield chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Atop library bookcases", 1392 + treasure_index_offset), #Craftwork Sword chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Penguin sanctuary", 2654 + treasure_index_offset), #Craftwork Robe chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 1", 137 + treasure_index_offset), #Gaea Shard chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 2", 227 + treasure_index_offset), #Gaea Shard chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 3", 381 + treasure_index_offset), #Gaea Shard chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 4", 548 + treasure_index_offset), #Gaea Shard chest
- #Next check can be acquired with either Owl, Ibek, Quintar, or Gaea Stone; vanilla expects Gaea Stone so that's the logic were using
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Cleric's Lounge", 1391 + treasure_index_offset, lambda state: state.has(GAEA_STONE, player) or (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Craftwork Bow chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Instrducktor classroom", 1387 + treasure_index_offset), #Craftwork Axe chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Master Warlock's chambers atop Weapons R Us", 2732 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Watering Can chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Master Wizard's Library atop Weapons R Us", 168 + treasure_index_offset), #Craftwork Pages chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Fenced off in Armor Merchant alley", 2653 + treasure_index_offset), #Craftwork Helm chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Beneath grand staircase", 1393 + treasure_index_offset), #Craftwork Rapier chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Tucked into maze entrance hedge", 389 + treasure_index_offset), #Fang Pendant chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Down left maze path", 452 + treasure_index_offset), #Craftwork Wand chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Hop moat to maze", 863 + treasure_index_offset), #Craftwork Spear chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Accompanied by blue flower pair in maze", 390 + treasure_index_offset), #Craftwork Crown chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Below maze-cheating Lost Penguin", 388 + treasure_index_offset), #Gardeners Key chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Above maze fountain", 387 + treasure_index_offset), #Givers Ring chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gardener's Shed 1", 2652 + treasure_index_offset, lambda state: logic.has_key(state, GARDENERS_KEY)), #Craftwork Mail chest
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gardener's Shed 2", 2663 + treasure_index_offset, lambda state: logic.has_key(state, GARDENERS_KEY)), #Tuber Seed
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gardener's Shed 3", 2664 + treasure_index_offset, lambda state: logic.has_key(state, GARDENERS_KEY)), #Tuber Seed
-
- #NPCs
- #Todo NPCs Job Masters: Master Beatsmith ID 3560 (361, 170, -268); gives you Beatsmith Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Cleric ID 3568 (363, 166, -266); gives you Cleric Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Monk ID 3567 (394, 179, -295); gives you Monk Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Rogue ID 3571 (444, 167, -264); gives you Rogue Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Warlock ID 3570 (400, 171, -267); gives you Warlock Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Warrior ID 3566 (424, 182, -293); gives you Warrior Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Wizard ID 3569 (391, 168, -266); gives you Wizard Seal in exchange for job mastery
- #NPCs Blocker: Z14_ProgressionGate ID 3823 (403, 180, -367) requires 18 crystals; we think it"s an original-randomizer-only NPC blocking the way to the castle
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Home Point Stone duck", 560 + npc_index_offset), #Home Point Stone (403, 161, -265) Fixed Missable
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Impress the Luxury Equipment Shop Bouncer with 6 jobs collected", 51162 + npc_index_offset, lambda state: logic.has_jobs(state, 6)), #(419, 171, -289) Blocker-No-Longer, Fixed Missable, and Multichecks
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Impress the Luxury Equipment Shop Bouncer further with 11 jobs collected", 1162 + npc_index_offset, lambda state: logic.has_jobs(state, 11)), #(419, 171, -289)
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Plunder the Luxury storage and skip town to meet a very slow thief", 1529 + npc_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #(417, 171, -299) Fixed Missable
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Courtyard Chloe", 1661 + npc_index_offset, lambda state: (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Fly Lure (399, 155, -219) Fixed Missable
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Glinting Courtyard Key", 2486 + npc_index_offset), #Courtyard Key sparkle that appears if you miss Courtyard Reid in Salmon River (424, 150, -222) Fixed Missable
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Sparkling in the fountain", 2584 + npc_index_offset), #Plug Lure
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin on a tent", 605 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Speedy Lost Penguin on patrol", 584 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin kiosk keeper", 508 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin skulking in store alley", 565 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin on gender change bench porch", 1095 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin enjoying inn hospitality", 946 + npc_index_offset),
- # Progressive Location: 4 checks on the Penguin Keeper, must add a progressive location in the C# app every time you use one of these.
- # The original check that corresponds to the npc id should be last so that when it completes it stops showing up on your minimap.
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring 3 Lost Penguins to Penguin Keeper", 50531 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 3)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring 6 Lost Penguins to Penguin Keeper", 50532 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 6)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring 9 Lost Penguins to Penguin Keeper", 50533 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 9)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring all 12 Lost Penguins to Penguin Keeper", 531 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 12)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin trampling Cleric's flowers", 564 + npc_index_offset, lambda state: state.has(GAEA_STONE, player) or (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Sadist Sam gives you pain, you give Sadist Sam head(s)", 536 + npc_index_offset, lambda state: state.has(DIGESTED_HEAD, player, 3)), #name is ca69011a in Crystal Edit why lmao
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin wandering Magic Store rooftop garden", 573 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin atop sewer exit rooftop", 567 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin cheating at Garden Maze", 421 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - How did you climb that tree, Lost Penguin?", 422 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Library roof Lost Penguin", 594 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Library Morii of the East!", 1948 + npc_index_offset), #(440, 171, -296) Z14_Library Scholar
-
- #Jojo Sewers
- #Treasure chests
- #Sewer Main (lol)
- LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Hiding in the guarded grass", 743 + treasure_index_offset), #Tonic Pouch chest
- LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Eastside sewer green room", 2658 + treasure_index_offset), #Iron Helm chest
- LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Invisible maze", 744 + treasure_index_offset), #Iron Armor chest
- LocationData(JOJO_SEWERS_AP_REGION, "Underpass Chest - Walking the plank above Pale Grotto waterfall", 3670 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(337, 155, -319) Underpass Scrap chest
- #Past Secret Password
- LocationData(SEWERS_TO_BOOMER_SOCIETY_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Drowned passage to Boomer Society", 634 + treasure_index_offset), # Money chest
- LocationData(SEWERS_TO_BOOMER_SOCIETY_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - In the shadow of the waterfall", 1126 + treasure_index_offset), # Tincture Pouch
- LocationData(SEWERS_TO_BOOMER_SOCIETY_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Leap of faith", 887 + treasure_index_offset), # Smelly Gi chest
-
- #NPCs
- #Sewer Main (lol)
- LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " NPC - Who even wants Stone of Jordan these days?", 2759 + npc_index_offset, lambda state: state.has(CRAG_DEMON_HORN, player)),
-
- #Boomer Society
- #Treasure chests
- LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " Chest - Log cabin", 2667 + treasure_index_offset), #Gospel chest
- LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " Chest - 2nd floor of log cabin", 2909 + treasure_index_offset), #Boomer Society map chest
-
- #NPCs
- LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " NPC - Nice Allowance Lady", 476 + npc_index_offset),
- LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " NPC - Treasury Grandpa", 547 + npc_index_offset),
-
- #Rolling Quintar Fields
- #Treasure chests
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - South of east gate", 826 + treasure_index_offset), #Potion chest
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Chevy divot south of east gate", 828 + treasure_index_offset), #Fenix Juice chest
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Deep in Quintar cave", 817 + treasure_index_offset), #Hunting Axe chest
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Sneaky chest behind tree", 829 + treasure_index_offset), #Potion chest
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Deep in eastern Quintar cave", 745 + treasure_index_offset), #Hunting Bow chest
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - At the end of the road", 825 + treasure_index_offset), #Money chest
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Hidden beneath end of the road", 2674 + treasure_index_offset), #Tonic Pouch chest
- #Rolling Treetop Highway
- LocationData(ROLLING_TREETOP_HIGHWAY_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - West of and above sneaky chest", 338 + treasure_index_offset), #Money chest
- #Quintar Sanctum Entrance
- LocationData(SANCTUM_ENTRANCE_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Pinnacle by short and tall box friends", 471 + treasure_index_offset), #Tincture Pouch chest
- LocationData(SANCTUM_ENTRANCE_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Treetop west of Quintar Sanctum", 365 + treasure_index_offset), #Spore Blocker chest
- #Hunter's Tower
- LocationData(HUNTERS_TOWER_AP_REGION, "Overpass Chest - Climb the mountain west of Quintar Sanctum entrance", 3532 + treasure_index_offset), #1st Overpass Scrap chest on main Overpass map
-
- #NPCs
- #NPCs CheckOrNot: two Quintar Eggs (decided against)
- #Hunter Master is in Hunter's Tower region, but you need quintar to climb the tower
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Quintar Stable Owner by Capital Sequoia's eastern gate", 375 + npc_index_offset, lambda state: logic.has_jobs(state, 7)), #Quintar Pass, Fixed Missable
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Silver beneath overhang in eastern Quintar cave crevasse", 2678 + npc_index_offset), #Dust
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Quintar Enthusiast (always pet Buttermint)", 464 + npc_index_offset), #Fixed Missable
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Silver in Quintar cave beneath the end of the road", 454 + npc_index_offset), #Ingot
- #Rolling Treetop Highway
- LocationData(ROLLING_TREETOP_HIGHWAY_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Silver behind Quintar Nest befriending a stack of boxes", 323 + npc_index_offset), #Ore
-
- #Quintar Nest
- #Treasure chests
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - West Donut Lake sprinkle", 883 + treasure_index_offset), #Money chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - East Donut Lake sprinkle", 884 + treasure_index_offset), #Ether chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Jumping puzzle above the donut", 756 + treasure_index_offset), #Fenix Juice chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Northwest Donut Lake sprinkle", 432 + treasure_index_offset), #Potion chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Welcome", 3078 + treasure_index_offset), #Potion chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Mighty jump along east side wall", 746 + treasure_index_offset), #Scope Bit chest
- #You can't detour through the sewers if you can't reach the sewers (relevant for regionsanity)
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Detour through the sewers", 638 + treasure_index_offset, lambda state: state.can_reach(JOJO_SEWERS_AP_REGION, player=player) or logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state)), #Static Rod chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - North Donut Lake sprinkle", 852 + treasure_index_offset), #Tincture chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Hop along west side wall", 2982 + treasure_index_offset), #Tincture Pouch chest
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Donut Lake crown sprinkle", 851 + treasure_index_offset), #Tonic chest
- LocationData(QUINTAR_NEST_AP_REGION, "Underpass Chest - Up north Quintar Nest waterfall", 3620 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(524, 146, -368) Underpass Scrap chest
-
- #NPCs
- #Todo NPCs CheckOrNot: two Quintar Eggs here
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " NPC - Eastside Silver come on down to the water", 711 + npc_index_offset), #Dust
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " NPC - South of sewers Silver", 850 + npc_index_offset), #Ingot
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " NPC - Silver on the way out", 755 + npc_index_offset), #Ore
-
- #Quintar Sanctum
- #Treasure chests
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - West wall big bounce", 810 + treasure_index_offset), #Money chest
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - Bounce field", 969 + treasure_index_offset), #Fenix Juice chest
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - In front of the shrine", 2910 + treasure_index_offset), #Quintar Sanctum map chest
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - West at ground level", 2983 + treasure_index_offset), #Tincture Pouch chest
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - North at ground level", 593 + treasure_index_offset), #Tonic Pouch chest
- #Sanctum Exit Clifftop
- LocationData(SANCTUM_EXIT_CLIFFTOP_AP_REGION, "Overpass Chest - Lonely chest above Quintar Sanctum", 3533 + treasure_index_offset), #2nd Overpass Scrap chest on main map
-
- #NPCs
- #Todo NPCs CheckOrNot: Quintar Egg here (on Quintar Sanctum Mushroom map)
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Silver going back down", 802 + npc_index_offset), #Dust
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Silver almost to the top", 965 + npc_index_offset), #Dust
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Mushroom bounce Silver", 411 + npc_index_offset), #Ingot
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Silver beneath the shroom", 801 + npc_index_offset), #Ingot
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - East side Silver (Do not look down)", 737 + npc_index_offset), #Ore
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Big bounce Silver", 754 + npc_index_offset), #Ore
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Two Toads bestow Princess Toadstool", 963 + npc_index_offset),
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Two Toads crown Bowsette", 964 + npc_index_offset),
-
- #Capital Jail
- #Treasure chests
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Touchdown", 640 + treasure_index_offset), # South Wing Key chest
- #South Wing
- LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - South Wing jail cell across from busted wall", 930 + treasure_index_offset), # West Wing Key chest
- LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Haunted jail cell in South Wing dead end", 931 + treasure_index_offset), # East Wing Key chest
- #South Wing Rubble
- LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Fiercely guarded and locked in South Wing rubble 1", 990 + treasure_index_offset), # Cell Key chest
- LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Fiercely guarded and locked in South Wing rubble 2", 2668 + treasure_index_offset), # Iron Rod chest
- LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked in South Wing rubble", 991 + treasure_index_offset), # Battleplate chest
- LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, "Underpass Chest - Drop down behind Capital Jail South Wing rubble", 3675 + treasure_index_offset), # 7th Underpass Scrap on main map
- #West Wing
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - West Wing jail cell among the glowy plants", 925 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY)), #Cell Key chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - West Wing arrow plants", 923 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY)), #Battle Helm chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked among the foliage in West Wing", 916 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, CELL_KEY, 4)), #Cell Key chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked beyond overgrown West Wing hallway", 909 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), # Dark Wing Key chest
- #East Wing
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - East Wing bedroom closet twinsies the 1st", 2999 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #empty chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - East Wing bedroom closet twinsies the 2nd", 906 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #Potion chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Waterlogged East Wing hallway twinsies the 1st", 676 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #Cell Key top chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Waterlogged East Wing hallway twinsies the 2nd", 707 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #Cell Key bottom chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked in broken East Wing jail cell", 708 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Cell Key chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked in East Wing bedroom", 763 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Cell Key chest
- #Dark Wing
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Dark Wing entry left cell", 2911 + treasure_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Capital Jail map chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Sneaky chest in Dark Wing", 929 + treasure_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Woven Hood chest
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Corner lava jump in Dark Wing", 920 + treasure_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Woven Shirt chest
-
- #NPCs
- #South Wing
- LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver in haunted South Wing jail cell", 972 + npc_index_offset), #Ingot
- LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver in zombified South Wing jail cell", 989 + npc_index_offset), #Ingot
- #West Wing
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver locked in overgrown West Wing hallway", 759 + npc_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), # Ore
- #East Wing
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver locked in broken East Wing jail cell accompanied by blue flower", 760 + npc_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Ore
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver locked in East Wing bedroom", 782 + npc_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Dust
- #Dark Wing
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver in Dark Wing entry right cell", 472 + npc_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Dust
-
- #Capital Pipeline
- #Treasure chests
- #Pipeline South
- LocationData(PIPELINE_SOUTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " Chest - I wanna go home", 2912 + treasure_index_offset), #Capital Pipeline map chest
- #Pipeline North
- LocationData(PIPELINE_NORTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " Chest - Do not anger the fungus", 1294 + treasure_index_offset), #Lucky Platter chest
-
- #NPCs
- #Pipeline North
- LocationData(PIPELINE_NORTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " NPC - Silver in corrupted tunnel 1", 2660 + npc_index_offset), #Ingot
- LocationData(PIPELINE_NORTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " NPC - Silver in corrupted tunnel 2", 1295 + npc_index_offset), #Ore
- # Pipeline Jidamba Connector
- LocationData(PIPELINE_JIDAMBA_CONNECTOR_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " NPC - Ride the Pipeline elevator for Jidamba diamond heist", 2897 + npc_index_offset), # Dust
-
- #Cobblestone Crag
- #Treasure chests
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Behind sluice gate", 479 + treasure_index_offset), #Ether Pouch chest
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Long jump", 382 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Potion chest
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Tucked in cranny between two tall spikes", 1119 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Potion Pouch chest
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - I totally meant to miss that jump", 2670 + treasure_index_offset), #Skewer chest
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Upon exiting from Quintar Nest", 478 + treasure_index_offset), #Tonic Pouch chest
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Could really use a Walking Stick (chest) right about now...", 2669 + treasure_index_offset),
- LocationData(COBBLESTONE_CRAG_AP_REGION, "Underpass Chest - On the way to village hidden among leaves", 3669 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Underpass Scrap (Okimoto)
-
- #NPCs
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " NPC - Westernmost Silver", 1120 + npc_index_offset), #Dust
-
- #Okimoto N.S.
- #Treasure chests
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Moth love lamp", 364 + treasure_index_offset), #Butterfly chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Dont bump your head", 2661 + treasure_index_offset), #Ether Pouch chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Parkour to the west", 337 + treasure_index_offset), #Float Shoes chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Just kinda in there, its not special", 356 + treasure_index_offset), #Potion chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - East of save point", 344 + treasure_index_offset), #Tanto chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - On yashiki balcony", 690 + treasure_index_offset), #Money chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Down hidden stairs in library", 686 + treasure_index_offset), #Art of War chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - East ground floor room", 2673 + treasure_index_offset), #Magic Finder chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Lurking behind bookcase", 434 + treasure_index_offset), #Potion Pouch chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Past hidden staircase", 694 + treasure_index_offset), #Tachi chest
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Dance above the koi pond", 1103 + treasure_index_offset), #Training Gi chest
- LocationData(OKIMOTO_NS_AP_REGION, "Overpass Chest - Mountain lake north of the yashiki", 3534 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or logic.has_swimming(state)), #(605, 228, -270) 3rd Overpass Scrap in Overpass main map
-
- #NPCs
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Silver on the way up", 359 + npc_index_offset), #Dust
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Why does a room like this exist? (Silver)", 692 + npc_index_offset), #Silver Dust
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Eastern Silver atop pond box", 689 + npc_index_offset), #Ingot
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Silver behind room that shall not be named", 691 + npc_index_offset), #Ingot
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Silver atop yashiki", 2659 + npc_index_offset), #Ore
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Lets get down to business western Silver", 429 + npc_index_offset), #Ore
- LocationData(OKIMOTO_NS_AP_REGION, "Overpass NPC - Swim up koi pond waterfall into cherry tree", 1583 + npc_index_offset, lambda state: logic.has_swimming(state)), #Springs Oath (632, 243, -261) Overpass main map
-
- #Greenshire Reprise
- #Treasure chests
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Jump off bridge 4", 483 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Ambush Knife chest
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Atop the waterfalls", 490 + treasure_index_offset), #Ether chest
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Jump off bridge 3", 482 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Looters Ring chest
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Tall taunter", 373 + treasure_index_offset), #Shell Amulet chest
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - In the valley of trees", 487 + treasure_index_offset), #Tincture Pouch chest
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Tip of peninsula south of 2nd bridge", 491 + treasure_index_offset), #Tonic Pouch chest
-
- #NPCs
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " NPC - Jump down from 2nd bridge to Silver fallen in north crack", 485 + npc_index_offset), #Ore
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " NPC - Silver across 1st bridge hiding in a crack", 486 + npc_index_offset), #Dust
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " NPC - The furthest southern edge Silver", 474 + npc_index_offset), #Ingot
-
- #Salmon Pass
- #Treasure chests
- #Salmon Pass East: 2 chests on the east side that either require the ability to get to Greenshire Reprise and cross it or hop the bridge (also you need the 5 jobs bc the rental quintar comes from Capital Sequoia)
- LocationData(SALMON_PASS_EAST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Chest - Riverbank among yellow flowers", 2700 + treasure_index_offset), #Paypirbak chest
- LocationData(SALMON_PASS_EAST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Chest - Admiring the hidden waterfall", 419 + treasure_index_offset), #Fenix Juice chest
- #Salmon Pass West: 1 chest on the west side that either requires crossing the bridge or hoofing it from Salmon River
- LocationData(SALMON_PASS_WEST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Chest - Across a bridge and around through a tunnel", 2420 + treasure_index_offset), # Fenix Juice chest
-
- #Salmon River
- #Treasure chests
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - Hop on chest once you have become frogger", 1264 + treasure_index_offset), #Money chest
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - Atop river island crown", 1297 + treasure_index_offset), #Bloodbind chest
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - It also wishes to be frogger", 325 + treasure_index_offset), #Money chest
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - In the stands of Salmon race finish line", 2976 + treasure_index_offset, lambda state: logic.has_rental_salmon(state)), #Ether Pouch chest
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - Inside Salmon Shack", 2913 + treasure_index_offset), #Salmon River map chest
- #Mushroom Mountain
- LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Hop west from shrine to shroom-studded mountainside", 3539 + treasure_index_offset), #(32, 181, -373) 2nd Overpass scrap on (Cloudy Wind)
- LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Frigid dip high behind River Cat", 3654 + treasure_index_offset, lambda state: logic.has_glide(state) or logic.has_swimming(state)), #(60, 225, -435) Overpass (Snow) River Cats Ego map
- LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Chilling by Nomad's Outpost", 3676 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state)), # (45, 215, -465) Overpass (Outpost) Scrap
- LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Ultimate Mulan challenge past mushroom mountain", 1401 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #(-35, 166, -387) Overpass (Cloudy Wind) Zether Pouch chest
-
- #NPCs
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Reid chilling by the Fish Hatchery", 2410 + npc_index_offset), #(113, 172, -372) Courtyard Key; Fixed Missable
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race Participation Prize", 50639 + npc_index_offset, lambda state: logic.has_rental_salmon(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 14th place prize", 50640 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 12th place prize", 50641 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 10th place prize", 50642 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 8th place prize", 50643 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 6th place prize", 50644 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 4th place prize", 50645 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 3rd place prize", 50646 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 2nd place prize", 50647 + npc_index_offset, lambda state: logic.has_swimming(state)),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Win the Salmon Race", 639 + npc_index_offset, lambda state: logic.has_swimming(state)),
- #Mushroom Mountain
- LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass NPC - Fall off mushroom mountain onto Gold", 2739 + npc_index_offset), #(63, 191, -399) 2nd Gold Dust on Overpass (Cloudy Wind)
-
- #Poko Poko Desert
- #Treasure chests
- #Poko Poko Desert main
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - West Lookout Tower", 1170 + treasure_index_offset), # West Lookout Token chest
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Quintar leapfrog", 1080 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Butter Cutter chest
- #if you're Good At Jumping you can get to this chest south of rocky outcropping gold with no mounts
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - South of tricky Quintar Gold", 1082 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Hatchet chest
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - This chests (on) a butte", 1169 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME)), # Dueller
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - North Lookout Tower", 1190 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME) or logic.has_vertical_movement(state)), #North Lookout Token chest
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Stormy first floor of ruins", 2676 + treasure_index_offset), #Fenix Juice chest
- #Poko Poko Desert Eastern Plateau
- LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Potion chest to fortify you for jumping puzzle from hell", 2708 + treasure_index_offset),
- LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Central Lookout Tower (ok maybe that jumping puzzle wasn't that bad)", 1189 + treasure_index_offset), # Central Lookout Token chest
- LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Balance beam", 97 + treasure_index_offset), # Scope Specs chest
- LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Past Lost Son", 1667 + treasure_index_offset), # Ether Pouch chest
- #Tower of Zot
- LocationData(TOWER_OF_ZOT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Cooling off in the tent before the Tower of Zot", 2914 + treasure_index_offset), #Salmon Bay map chest
-
- #NPCs
- #NPCs CheckOrNot: three Quintar Eggs in Poko Poko Desert (Nest) map - not
- #Poko Poko Desert main
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver beneath overhang in ruins south of shrine", 2675 + npc_index_offset), #Dust
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver slumbering in broken house NE of shrine", 1081 + npc_index_offset), #Ingot
- # if you're Good At Jumping you can get to this rocky outcropping gold with no mounts
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Rocky outcropping Gold will put your Quintar to the test", 2817 + npc_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Dust
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Stormy Silver atop ruins", 2677 + npc_index_offset), # Ore
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold Ingot atop ridge south of North Lookout Tower", 2818 + npc_index_offset),
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Thirsty Lad", 1201 + npc_index_offset, lambda state: state.has(SPECIAL_MILK, player)),
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver in desert arch shade", 2682 + npc_index_offset), # Ingot
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver in the sandstorm on ruins 2nd floor", 2680 + npc_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Dust
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Stormy Silver on ruined building floor", 2681 + npc_index_offset), #Ore
- #Poko Poko Eastern Plateau
- LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold on an outcropping by long loop-around chest", 2706 + npc_index_offset), # Ore
- LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold overlooking Sara Sara Bazaar", 2707 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Ingot
- #Poko Poko Sara Sara Beach West Mountain Pass
- LocationData(POKO_POKO_BEACH_WEST_PASS_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - High western gold (approach from the Lookout Tower or the Beach Nest)", 2711 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), #Dust
- #Tower of Zot
- LocationData(TOWER_OF_ZOT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Diamond bedazzling the Tower of Zot", 2879 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #Dust
- LocationData(TOWER_OF_ZOT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold bedazzling the Tower of Zot", 2816 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #Ore
-
- #Sara Sara Bazaar
- #Treasure chests
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Someone took the St James and left a...", 408 + treasure_index_offset, lambda state: logic.has_key(state, ROOM_ONE_KEY)), #Knockout Stick chest
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Darkened upper storeroom", 414 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME) or logic.has_horizontal_movement(state)), #Potion chest
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Wedge yourself through the crates in the darkened upper storeroom", 513 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME) or logic.has_horizontal_movement(state)), #Storm Rod chest
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Potion Mixer", 1194 + treasure_index_offset), #Beaurior Volcano map chest
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Spilled booty", 2936 + treasure_index_offset, lambda state: logic.has_swimming(state)), #Captains Hat chest
-
- #NPCs
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Quintar West Stable Owner", 1852 + npc_index_offset, lambda state: logic.has_jobs(state, 7)), #Quintar Pass; Fixed Missable
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Quintar East Stable Owner", 2234 + npc_index_offset, lambda state: logic.has_jobs(state, 7)), #Quintar Pass; Fixed Missable
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Ferry Ticket Agent forgot what Ferry Passes are but she found something in her desk", 940 + npc_index_offset, lambda state: logic.has_jobs(state, 11)), #(-166,93,56) Fixed Missable
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Three tokens makes a Pyramid Key something something triangles", 949 + npc_index_offset, lambda state: state.has(WEST_LOOKOUT_TOKEN, player) and state.has(CENTRAL_LOOKOUT_TOKEN, player) and state.has(NORTH_LOOKOUT_TOKEN, player)),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - The One and Only Room 1 Key", 385 + npc_index_offset, lambda state: logic.can_earn_money(state, SARA_SARA_BAZAAR_AP_REGION)),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Circle the eastern desert wall for Worried Moms Lost Son", 1196 + npc_index_offset, lambda state: (state.has(POKO_POKO_DESERT_PASS, player) or logic.options.regionsanity.value != options.regionsanity.option_extreme) and logic.is_area_in_level_range(state, POKO_POKO_ENEMY_LEVEL)), #Ferry Pass, if regionsanity extreme is enabled, you'll need the pass for the desert
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Pelt this Fish Merchant with Rotten Salmon", 942 + npc_index_offset, lambda state: state.has(SPECIAL_ROTTEN_SALMON, player) and state.has(SPECIAL_FRESH_SALMON, player)),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - No Shoudu Stew for you!", 1200 + npc_index_offset, lambda state: state.has(SPECIAL_SHOUDU_STEW, player)),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silver", 2905 + npc_index_offset, lambda state: logic.has_swimming(state)), #Dust
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silverer", 2906 + npc_index_offset, lambda state: logic.has_swimming(state)), #Dust
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silvererer", 2903 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ingot
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silverererer", 2904 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ingot
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silvererererer", 2901 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ore
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silverererererer", 2902 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ore
-
- #Sara Sara Beach East
- # Treasure chests
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Chest - Glittering in the sun at Ibek Cave exit 1", 1083 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Tincture Pouch chest
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Chest - Glittering in the sun at Ibek Cave exit 2", 1085 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Tonic Pouch chest
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Chest - How dare you stand where he stood?", 1084 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Money chest
-
- # NPCs
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 1", 2683 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Dust
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 2", 2684 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Dust
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 3", 2686 + npc_index_offset), # Dust
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 4", 2688 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Silver
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 5", 2689 + npc_index_offset), # Ore
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 6", 2690 + npc_index_offset), # Ore
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Jaunt along cliff past Dr Cool Aids perch to Silver", 2685 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Ingot
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver on the beach rocks at eastern edge", 2687 + npc_index_offset), # Ingot
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver beheld by Dr Cool Aids", 2691 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Ore
-
- #Sara Sara Beach West
- #Treasure chests
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - South of Beach Birds Nest", 154 + treasure_index_offset), #Ether chest
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - Across the palms above the dust", 1509 + treasure_index_offset), #Potion chest
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - Beach cave", 2718 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Blank Pages chest
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - Tightrope walk below Beach Birds Nest", 1546 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state)), #Potion chest; possible with rental if masochists play our game/Good At Jumping option
-
- #NPCs
- #Todo NPCs Job Masters: Master Dervish ID 3575 (-255, 103, -237); gives you Dervish Seal in exchange for job mastery
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Cross my palms with Silver", 2693 + npc_index_offset), #Dust
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver past angry birds", 2697 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_glide(state)), #Dust
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver south of Beach Birds Nest", 2694 + npc_index_offset), #Ingot
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver at the foot of the Tower of Zot", 2699 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Ingot
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Lonely Islet Silver", 2878 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Ingot
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Southern silver along the cliffside", 2692 + npc_index_offset), #Ore
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver chilling in beach cave", 2698 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Ore
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver further along beach", 2877 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ore
-
- #Ancient Reservoir
- #Treasure chests
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Really elaborate crystal rafters", 1123 + treasure_index_offset), #Red Coat chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Crystal gutters", 1122 + treasure_index_offset), #Red Cap chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Tucked on ledge by aqueduct", 1982 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state)), #Resist Shifter chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - East Switch Room", 2977 + treasure_index_offset), #Ether Pouch chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Eastern nyoom room", 2056 + treasure_index_offset), #Money chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Hiding behind aqueduct grate", 2703 + treasure_index_offset), #Potion Pouch chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Hiding behind western aqueduct grate", 2702 + treasure_index_offset), #Money chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Twinsies the 1st at west waterfall base", 2704 + treasure_index_offset), #Defense Shifter chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Twinsies the 2nd at west waterfall base", 1145 + treasure_index_offset), #Money chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - I saw Red vent in the eastern stairwell", 2701 + treasure_index_offset), #Grim Scythe chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Goat snack for later", 2915 + treasure_index_offset), #Ancient Reservoir map chest
- LocationData(ANCIENT_RESERVOIR_AP_REGION, "Underpass Chest - Waterway nook between Gran & Ancient Reservoir", 3541 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(64, 98, -111) 1st Underpass Scrap on main map
-
- #NPCs
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Silver in odd flooded room 1", 2695 + npc_index_offset), #Ingot
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Silver in odd flooded room 2", 1675 + npc_index_offset), #Ore
-
- #Ibek Cave
- #Treasure chests
- LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Celebrate your new hops", 2517 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Fenix Juice Pouch chest
-
- #NPCs
- LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Goat victory Ibek Bell", 1676 + npc_index_offset), # Z30_PostBossEvent;
- LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Silver in the goat digs", 2696 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Dust
-
- #Salmon Bay
- #Treasure chests
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Chest - Cliff diving", 2975 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), #Ether Pouch chest
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Chest - Across the bridge", 2974 + treasure_index_offset), #Potion Pouch chest
- LocationData(SALMON_RIVER_MOUTH_AP_REGION, "Overpass Chest - Lonely scrap among half-dead pines above Salmon Bay", 3677 + treasure_index_offset), #8th Scrap in Overpass main map
-
- #NPCs
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " NPC - Ancient Tablet B on moodlit shore behind waterfall", 2438 + npc_index_offset),
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " NPC - West cliff diving Ancient Tablet C", 1271 + npc_index_offset, lambda state: logic.has_vertical_movement(state)),
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " NPC - Quintar splish splash Ancient Tablet A", 1272 + npc_index_offset),
-
- #Overpass
- #Treasure chests
- #Life Jewel on main map has been categorized under Dione Shrine
- #1st Scrap on main Overpass map has been categorized under Rolling Quintar Fields
- #2nd Scrap on main Overpass map has been categorized under the Quintar Sanctum
- #3rd Scrap on main Overpass map has been categorized under the Okimoto N.S.
- #4th Scrap on main Overpass map has been categorized under Dione Shrine
- #5th Scrap on main Overpass map has been categorized under Quintar Reserve
- #6th Scrap on main Overpass map has been categorized under Yamagawa M.A.
- #7th Scrap on main Overpass map has been categorized under Beaurior Rock
- #8th Scrap on main Overpass map has been categorized under Salmon Bay
- #9th Scrap on main Overpass map has been categorized under Lands End
- #1st Overpass Scrap (Cloudy Wind) has been categorized under Tall Tall Heights
- #Zether Pouch (Cloudy Wind) has been categorized under Salmon River
- #River Cats Ego map (Snow) recategorized under Salmon River
- #Overpass (Outpost) Northern Stretch map has been categorized under Northern Stretch
- #Overpass (Outpost) Scrap has been categorized under Tall Tall Heights
-
- #NPCs
- #Todo NPCs Job Masters: Overpass main map has Master Hunter ID 3558 (496, 198, -346); gives you Hunter Seal in exchange for job mastery
- #Gold Ingot on Overpass main map has been categorized under Dione Shrine
- #Z20_WaterOrigin ID 1583 (gives Springs Oath) has been categorized under Okimoto N.S. (632, 243, -261)
- #1st Gold Dust (Cloudy Wind) has been categorized under Tall Tall Heights
- #2nd Gold Dust (Cloudy Wind) has been categorized under Salmon River
- #Todo NPCs CheckOrNot: Overpass 4 Quintar Eggs (Dirt Nest), 1 Quintar Egg (Stone Nest), 1 Quintar Egg (Cave Nest), 1 Quintar Egg (Stone Nest) submap
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Aegis ID 3610 (501, 194, -210); gives you Aegis Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Beastmaster ID 3608 (312, 236, -344); gives you Beastmaster Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Ninja ID 3550 (561, 238, -209); gives you Ninja Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Nomad ID 3548 (53, 227, -465); gives you Nomad Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Reaper ID 3611 (232, 220, -383); gives you Reaper Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Summoner ID 3557 (379, 246, -575); gives you Summoner Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost) has Master Valkyrie ID 3554 (24, 137, 71); gives you Valkyrie Seal in exchange for job mastery
- #Todo NPCs Job Masters: Overpass (Outpost 3H) has Master Fencer ID 3573 (166, 154, -124); gives you Fencer Seal in exchange for job mastery
-
- #Underpass
- #Treasure chests
- #3 treasures categorized under Delende
- #1st Scrap main Underpass map has been categorized under Ancient Reservoir
- #1st Scrap main Underpass map has been categorized under Quintar Nest
- #3rd Scrap main Underpass map has been categorized under Pale Grotto
- #4th Scrap main Underpass map has been categorized under Jojo Sewers
- #5th Scrap main Underpass map categorized under Delende
- #6th Scrap main Underpass map has been categorized under the Quintar Mausoleum
- #7th Scrap main Underpass map has been categorized under the Capital Jail
- #Underpass (Okimoto) Scrap has been categorized under Cobblestone Crag
- #Underpass (Summon Pah) Scrap has been categorized under The Undercity
- #Underpass (Ice Pass) Scrap chest has been categorized under Tall Tall Heights
- #Underpass (Ice Pass) Potion chest has been categorized under Tall Tall Heights
- #Underpass (Underwater) Scrap chest has been categorized under Tall Tall Heights
-
- #Zones (Expert)
- #The Open Sea
- #Treasure chests
- #Atolls
- LocationData(JIDAMBA_ATOLLS_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " Chest - Atoll southeast of Jidamba Tangle", 3767 + treasure_index_offset), # Fenix Syrup chest
- LocationData(JIDAMBA_ATOLLS_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " Chest - Atoll southwest of southeast Jidamba Tangle atoll", 3765 + treasure_index_offset), # Z-Potion chest
- #NPCs
- LocationData(THE_OPEN_SEA_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " NPC - I'm on a raft!", 2804 + npc_index_offset, lambda state: state.has("Item - Super Rod", player) and state.has("Item - Jigging Lure", player)),
- #CheckOrNot: (930, 91, 253) do we put a check on the guy who gives you a Gaea Shard if you get there with no Salmon lol: no
- #LocationData(THE_OPEN_SEA_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " NPC - Z34_SinisterSailor", 2520 + npc_index_offset),
-
- #Shoudu Waterfront
- #Treasure chests
- LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Chest - Along the water", 2419 + treasure_index_offset), #Money chest
- LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Chest - Hop around 1", 3690 + treasure_index_offset), #Empty chest
- LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Chest - Hop around 2", 1114 + treasure_index_offset), #Mars Stone chest
-
- #Shoudu Province
- #Treasure chests
- #Port area
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Jump through a window", 1507 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Ether Pouch chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Lurking above spike ball pit by goldsmith", 2984 + treasure_index_offset), # (753, 105, -176) Tincture Pouch chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Weaponsmith", 1505 + treasure_index_offset), # Plague Mask chest
-
- #Shanty Inn Home Point area
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Outside the inn", 2985 + treasure_index_offset), # Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Sneaky back door of cramped storage room", 1519 + treasure_index_offset), # Looters Pin chest
-
- #Moving up past here requires Ibek/Owl
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Below fast boi spark", 3504 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Through rooftop window south of fast boi spark 1", 3506 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Through rooftop window south of fast boi spark 2", 2763 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Reservoir above the water", 3508 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_swimming(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above accessory store", 3509 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Building near all the grates", 3510 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above Samurai Lounge 1", 3511 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above Samurai Lounge 2", 1541 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Fleuret chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Samurai Lounge", 3512 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Assassin Lounge", 3513 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Among crates across from Assassin Lounge", 3514 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Its in a room and there is a bed", 3515 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Granary", 3520 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Below the flower house", 3521 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - White hut", 3522 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Across the reservoir", 2978 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_swimming(state)) or logic.has_glide(state)), #Ether Pouch chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Crawl along the attic", 1536 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Knicked Knackers chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Sneak behind crates near Assassin Lounge", 2760 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Muggers Glove chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Go in the back door", 1506 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Near the Assassin Lounge", 2762 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Jump along the lamppost", 2752 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Suitor Hat chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above the armor store", 1517 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Enter building next to white hut to balance above The Undercity 1", 2717 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ether chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Enter building next to white hut to balance above The Undercity 2", 2716 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Tincture Pouch chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Cross the balance beam east of Fields save point", 3040 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Cross the balance beam on the way to Sky Arena", 2754 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Acrobat Shoes chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Atop the roofs near the grates", 1369 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - In the flower room", 2789 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Hidden in a house by the elevator 1", 3505 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Hidden in a house by the elevator 2", 2790 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Near sky fishing 1", 3507 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Near sky fishing 2", 2986 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Under the dry kid pit", 1365 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #The Immovable chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Fall through broken grate in building west of Sky Arena Prize Counter", 2951 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins Room 1", 2794 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), # Money chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins Room 2", 2751 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), # Bone Mail chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins room 3", 2747 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), # Cutlass chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins room 4", 2796 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), #Tonic Pouch
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins room 5", 2748 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), #Soul Kris chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 1", 2812 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Money chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 2", 2723 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Gaia Axe chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 3", 2813 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Money chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 4", 2753 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Gaia Vest chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 8 Sky Arena Wins room 1", 2665 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Gravedigger chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 8 Sky Arena Wins room 2", 2805 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Malifice chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 8 Sky Arena Wins room 3", 2800 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Wizards Wall chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 10 Sky Arena Wins room 1", 2756 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(753, 134, -263) Yasha chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 10 Sky Arena Wins room 2", 2928 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(754, 134, -264) Muramasa chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 10 Sky Arena Wins room 3", 2929 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(755, 134, -263) Shadow Gi chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Fall through floorboards of 10 Sky Arena Wins room 1", 3763 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(754, 130, -264) Zether chest
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Fall through floorboards of 10 Sky Arena Wins room 2", 3764 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(755, 130, -263) Z-Potion chest
-
- #NPCs
- #Todo NPCs Job Masters: Master Assassin ID 3605 (769, 123, -201); gives you Assassin Seal in exchange for job mastery
- #Todo NPCs Job Masters: Master Samurai ID 3576 (800, 115, -221); gives you Samurai Seal in exchange for job mastery
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Chloe and Talon sky fishing", 3702 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #(765, 125, -248) Fixed Missable; removed post-sparkle
- #NPCs Multichecks: Shoudu Province (Sky Arena) map Z38_SkyArenaPrizes ID 1921 (765, 125, -248) gives 5 prizes in exchange for winning fights
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 1 Sky Arena Win Prize", 51921 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIRST_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 4 Sky Arena Wins Prize", 51922 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FOURTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 6 Sky Arena Wins Prize", 51923 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SIXTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 7 Sky Arena Wins Prize", 51924 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SEVENTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 9 Sky Arena Wins Prize", 1921 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, NINTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 10 Sky Arena Wins room Diamond 1", 2833 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(752, 133, -262) Dust
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 10 Sky Arena Wins room Diamond 2", 2811 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(756, 133, -261) Ingot
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Diamond through a hole in the 10 Sky Arena Wins room floor", 2832 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(753, 130, -264) Ore
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold at back reservoir wall", 2827 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.has_swimming(state)), #Ingot
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in farmland on way to shrine", 2821 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ingot
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold near sky fishing", 2834 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ore
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 2 Sky Arena Wins room", 2829 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), #Dust
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 5 Sky Arena Wins room 1", 2720 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Ore
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 5 Sky Arena Wins room 2", 2722 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Ingot
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 5 Sky Arena Wins room 3", 2721 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Dust
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 8 Sky Arena Wins room 1", 2830 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Ingot
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 8 Sky Arena Wins room 2", 2831 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Ore
-
- #The Undercity
- #Treasures
- # can get without mounts from Shoudu
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 1", 2988 + treasure_index_offset), # Fenix Juice chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 2", 2987 + treasure_index_offset), # Ether chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 3", 1147 + treasure_index_offset), # Potion chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 4", 3517 + treasure_index_offset), # (778, 94, -254) Elevator Part chest
- #except these up high
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Climb up lampposts and run across the fence", 1925 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Cursegiver chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - North wall climb (or fall through broken grate by Sky Arena Prize Counter)", 3516 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Elevator Part chest
- #Home Point Stone area - can walk from Ganymede Shrine -> Undercity entrance next to white hut -> fall off rafter; can also swim from the sea
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Hiding in the rafters", 2989 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Potion Pouch chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Atop awning east of the waterfall", 3518 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Elevator Part chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Hidden in a nook in the wall", 2793 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Knights Plate chest
- #ibek from Home Point Stone area
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Up the rafters against a pillar", 2990 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Ether chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Even further up the rafters", 2991 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Ether Pouch chest
- #Undercity Inn area - can walk from Home Point Stone area if you hop up on guy who wants you to defeat the Undercity masters, go north, and hop west across lamps
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Hiding in a building in the north area", 2826 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Potion chest
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Undercity Inn", 3519 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Elevator Part
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - South of the Undercity Inn", 1695 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Brigandine chest
- #swimmy swimmy
- LocationData(THE_UNDERCITY_AP_REGION, "Underpass Chest - Lovely bounce tree W of The Undercity", 3673 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_glide(state)), #(608, 91, -215) (Summon Pah) Underpass Scrap chest
-
- #NPCs
- #can get without mounts from Shoudu
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Gold hiding from the bats under the awning", 2835 + npc_index_offset), #Dust
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Gated-off room Gold", 2825 + npc_index_offset), #Ore
- #other side of canal from Home Point Stone area
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Gold in the sewer offshoot", 1696 + npc_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), #Dust
- #Undercity Inn area
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Storage room Gold of the Undercity Inns", 1694 + npc_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), #Ingot
-
- #Ganymede Shrine
- #Treasure chests
- LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Chest - drop down from the top", 1594 + treasure_index_offset, lambda state: state.has(GANYMEDE_STONE, player)),
-
- #Beaurior Volcano
- #Treasure chests
- LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Chest - Beaurior Rock entrance", 3770 + treasure_index_offset), #Fenix Syrup chest
- LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Chest - Outcropping above the fog", 1168 + treasure_index_offset), #Temporal Blade chest
- LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Chest - Tricky jumps past Rock entrance", 2750 + treasure_index_offset), #Tome of Light chest
-
- #Beaurior Rock
- #Treasure chests
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B4 big lava room with optional boss", 1796 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Ether chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Caged in far corner of B4 big lava room with optional boss", 481 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Guard Crown chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Caged through secret tunnel in B4 big lava room with optional boss", 724 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Halberd chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Island in B4 big lava room with optional boss", 1682 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Small Key chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Elevator down from entrance to happy spike land", 894 + treasure_index_offset), #Small Key chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B3 balcony above the lava", 1337 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY)), #Small Key chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B2 with the Lets Make a Deal doors", 2973 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Potion Pouch chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 overlooking the catwalks room", 818 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Small Key chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 square pokeball room", 2916 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #map chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B3 behind wrought-iron fence", 899 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Shelter Dress chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B3 ominous green dumplings room", 1797 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Fenix Juice chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B2 danger hops back to purple", 2044 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Ether Pouch chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 cheeky lava platforming 1", 2041 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Potion
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 cheeky lava platforming 2", 1799 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Ether
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 jump to odd ice block", 2040 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Cold Touch chest
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Top floor ominous lamps room", 1683 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Boss Key chest
- LocationData(BEAURIOR_ROCK_AP_REGION, "Overpass Chest - West of Valkyrie Watchtower", 3540 + treasure_index_offset), # (1, 129, 62) 7th Overpass Scrap on main map
-
- #NPCs
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " NPC - Gold in B4 big lava room with optional boss", 2822 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY) and logic.has_horizontal_movement(state)), #Gold Ingot
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " NPC - B1 cheeky lava platforming Gold", 2823 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Dust
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " NPC - B1 Magic Well friendos hiding in the pillars", 2824 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_horizontal_movement(state)), #Ore
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " NPC - Summit Gold", 2836 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)), #Ore Beaurior Volcano but it's at the top so Rock it is
-
- #Lake Delende
- #Treasure chests
- LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Chest - North edge 1", 1263 + treasure_index_offset), #Float Shoes chest
- LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Chest - North edge 2", 2917 + treasure_index_offset), #Lake Delende map chest
-
- #NPCs
- #Atop Salmon River Dam
- LocationData(ATOP_DAM_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " NPC - Panning for Gold down Salmon Creek without a paddle", 2854 + npc_index_offset), #Dust
-
- #Quintar Reserve
- #Treasure chests
- LocationData(QUINTAR_RESERVE_AP_REGION, "Overpass Chest - Climbing the boughs up from the elevator", 3536 + treasure_index_offset), #5th Scrap on Overpass main map
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Chest - Race start hut", 1591 + treasure_index_offset), #Quintar Grass chest
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Chest - Hollowed-out wall of Mausoleum", 1320 + treasure_index_offset, lambda state: logic.has_glide(state)), #Undead Ring chest
-
- #NPCs
- #Reserve main
- #Todo NPCs CheckOrNot: 3 Quintar Eggs here
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Eastern Quintar overlooking the sea", 427 + npc_index_offset, lambda state: state.has(BABEL_QUINTAR, player)), #The Sequoia map (789, 191, -338); Fixed Missable
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding in hidden Quintar nest down in the ravine SE of Dione Shrine", 2255 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state) and logic.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)), #Shedding 1
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding in sneaky Quintar nest in eastern wall of the canyon north of the Mausoleum", 2256 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state) and logic.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)), #Shedding 2
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding in super sneaky Quintar nest in canyon north of the Mausoleum", 2257 + npc_index_offset, lambda state: logic.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)), #Shedding 3
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding east of shrine", 2259 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 4
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Long jog along the east mountain to shedding", 2260 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 5
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding overlooking the east ocean", 2261 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 6
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Treetop shedding north of Mausoleum", 2263 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 8
- #shedding 9 is in the Dione Shrine because why not I guess
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding overlooking the race start point", 2265 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 10
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding north of Mausoleum", 2266 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 11
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding just north of Quintar cosplayer", 2267 + npc_index_offset), #Shedding 12
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Gold on east side of map", 2837 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Ore
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Climb the center mountain for Gold", 2839 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Ore
- #Reserve Treetops
- LocationData(RESERVE_TREETOPS_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding atop the Mausoleum", 2262 + npc_index_offset, lambda state: logic.has_glide(state)), #Shedding 7
- LocationData(RESERVE_TREETOPS_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Jump across the treetops for Gold", 2840 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), #Dust
-
- #Dione Shrine
- #Treasure chests
- #Shrine proper
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - Lobby", 2791 + treasure_index_offset), #Dione Shard chest
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - 2nd floor", 2792 + treasure_index_offset), #Dione Shard chest
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - 2nd floor balcony", 1146 + treasure_index_offset), #Dione Shard chest
- #Shrine Roof
- LocationData(DIONE_ROOF_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - Roof", 2154 + treasure_index_offset), # Dione Shard chest
- LocationData(DIONE_ROOF_AP_REGION, "Overpass Chest - Glide SW from top of shrine 1", 3535 + treasure_index_offset, lambda state: logic.has_glide(state)), #4th Scrap on main Overpass map
- LocationData(DIONE_ROOF_AP_REGION, "Overpass Chest - Glide SW from top of shrine 2", 2749 + treasure_index_offset, lambda state: logic.has_glide(state)), #Life Jewel Overpass main map
-
- #NPCs
- #Shrine Roof
- LocationData(DIONE_ROOF_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " NPC - Shedding on roof", 2264 + npc_index_offset), #Shedding 9
- LocationData(DIONE_ROOF_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " NPC - Glide SW from top of shrine to Gold", 2838 + npc_index_offset, lambda state: logic.has_glide(state)), #Ingot on Overpass main map
-
- #Quintar Mausoleum
- #Treasure chests
- LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Chest - Past the switches race", 2153 + treasure_index_offset, lambda state: logic.has_fast(state)), #(688, 114, -464) Babel Quintar chest
- LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Chest - Rocky room", 3401 + treasure_index_offset), #(664, 129, -425) Quintar Mausoleum map chest
- LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Chest - Glowing grass room", 3768 + treasure_index_offset), #(709, 129, -442) Wind Thresher chest
- LocationData(QUINTAR_MAUSOLEUM_AP_REGION, "Underpass Chest - Up the waterfall inside Quintar Mausoleum", 3674 + treasure_index_offset), #(614, 146, -410) 6th Scrap chest on main Underpass map
-
- #Eastern Chasm
- #Treasure chests
- LocationData(EASTERN_CHASM_AP_REGION, EASTERN_CHASM_DISPLAY_NAME + " Chest - Overgrown opposite of chasm", 3543 + treasure_index_offset), #Eastern Chasm map chest
-
- #Tall Tall Heights
- #Treasure chests
- #Boomer Overlook
- LocationData(BOOMER_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Above the Boomer Society", 2844 + treasure_index_offset), # Z-Potion Pouch chest
- #Note: this chest is not technically connected to this region but it has the exact same requirements to get to it from Boomer Society/Tall, Tall Heights Save Point
- LocationData(BOOMER_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Lonely chest over the ridge from Boomer Society", 2428 + treasure_index_offset), # Ether
- #Greenshire Overlook
- LocationData(GREENSHIRE_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the icy Chips Challenge", 2786 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), #Tear Seed chest
- #Tall, Tall Heights Save Point
- LocationData(TALL_TALL_SAVE_POINT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the 2nd icy Chips Challenge", 2788 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), # Tear Seed chest
- LocationData(TALL_TALL_SAVE_POINT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the 3rd icy Chips Challenge", 1254 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), # Potion chest
- #Lower Ice Lakes
- LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the Chips Challenge fishing hut", 1578 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), # Frost Reaper chest
- LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Tall stones and blue flowers", 2992 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Potion Pouch chest
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Underpass Chest - Ice swimming instead of ice fishing", 3623 + treasure_index_offset, lambda state: logic.has_swimming(state)), # (191, 172, -437) (Underwater) Underpass Scrap chest
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Overpass Chest - Past Tall Tall Heights spiky tunnel to Salmon River", 3538 + treasure_index_offset), # 1st Overpass (Cloudy Wind) Scrap
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Underpass Chest - Tall Tall Heights spiky tunnel to Salmon River 1", 3672 + treasure_index_offset), # Underpass (Ice Pass) Scrap
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Underpass Chest - Tall Tall Heights spiky tunnel to Salmon River 2", 1601 + treasure_index_offset), # Underpass (Ice Pass) Potion
- #Upper Ice Lakes
- LocationData(UPPER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Above the Triton Shrine", 2795 + treasure_index_offset), # Ether chest
- #Tall, Tall Diamondsmith
- LocationData(TALL_TALL_DIAMONDSMITH_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Parkour off the diamondsmith beneath the dead tree", 2810 + treasure_index_offset), # Judo Gi chest
- #Slip Glide Ride Exit
- LocationData(SLIP_GLIDE_RIDE_EXIT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Break the ice", 2744 + treasure_index_offset), # Radiance Northern Cave
- #Sequoia Athenaeum Entrance
- LocationData(SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - East of the souvenir store", 2993 + treasure_index_offset), # Money chest
- #Sequoia Athenaeum Balcony
- LocationData(SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - A funny chest happened on the way to the Athenaeum", 2994 + treasure_index_offset), # Z-Potion chest
- LocationData(SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - East of the Athenaeum", 2565 + treasure_index_offset), # Potion Pouch chest
- #Tall, Tall Tall Chest
- LocationData(TALL_TALL_TALL_CHEST_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Athenaeum outdoors Chip's Challenge (or be a bird)", 2785 + treasure_index_offset), #Insignia Helm chest
-
- #NPCs
- #Todo NPCs Job Masters: Tall Tall Heights (Outpost) map has Master Chemist ID 3707 (491, 221, -389); gives you Chemist Seal in exchange for job mastery
- #Todo NPCs Player Options: (197, 192, -441) do we want a filter option to add the guys who fish things up for you
- #Boomer Overlook
- LocationData(BOOMER_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold above the Boomer Society", 1600 + npc_index_offset), #Ingot
- #Tall, Tall Ramparts Crag Chest
- LocationData(TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold tucked in melted snow past the Chip's Challenge east of shrine", 2846 + npc_index_offset), # Ore
- #Greenshire Overlook
- LocationData(GREENSHIRE_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - I bek you can get this one (or come back with the bird) for Gold", 2845 + npc_index_offset, lambda state: logic.has_glide(state) or logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME)), # Ingot
- #Lower Ice Lakes
- LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Melted snow Gold past the chest east of the Athenaeum", 2847 + npc_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Ingot
- LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Fish in the hut", 1549 + npc_index_offset, lambda state: state.has("Item - Tough Rod", player) and state.has("Item - Fly Lure", player)), # Z8_FisherInHut (197, 192, -441)
- LocationData(LOWER_ICE_LAKES_AP_REGION, "Overpass NPC - Gold past Tall Tall Heights spiky tunnel to Salmon River", 2710 + npc_index_offset), # 1st Gold Dust Overpass (Cloudy Wind)
- #Upper Ice Lakes
- LocationData(UPPER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Chip Challenge himself", 2388 + npc_index_offset),
- #Land's End Cottage Ridge
- LocationData(LANDS_END_COTTAGE_RIDGE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Hop along spike mountain to Gold", 2853 + npc_index_offset), #Dust
- #Slip Glide Ride Exit
- LocationData(SLIP_GLIDE_RIDE_EXIT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold by the breakable ice wall", 2814 + npc_index_offset), #Ingot
- #Sequoia Athenaeum Balcony
- LocationData(SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold past the Athenaeum Chips Challenge", 1602 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), # Dust
- #Icy Spikes Madness
- LocationData(ICY_SPIKES_MADNESS_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Treacherous landing Gold above the spikes", 1584 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ore
-
- #Northern Cave
- #Treasure chests
- #Lower Northern Cave
- LocationData(LOWER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Island in the ice", 2787 + treasure_index_offset), #Tear Seed chest
- #Ice Cell
- LocationData(ICE_CELL_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Ominous Chips Challenge cave", 1579 + treasure_index_offset), #Ice Cell Key chest
- #Note: ibek here requires ice block; check can be made with quintar and not owl but the owl is required from Slip Glide Ride save point or Ice Cell start
- LocationData(ICE_CELL_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Hop along the igloos or Quintar skip the Chip", 1552 + treasure_index_offset, lambda state: logic.has_glide(state) or logic.can_push_ice_block_and_goat(state, NORTHERN_CAVE_DISPLAY_NAME)), #Apprentice chest
- #Upper Northern Cave
- LocationData(UPPER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Past the wiggly block spike pit", 3001 + treasure_index_offset), #Money chest
-
- #NPCs
- #Upper Northern Cave
- LocationData(UPPER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " NPC - Gold past the wiggly block spike pit", 2815 + npc_index_offset), #Ore
-
- #Lands End
- #Treasure chests
- #Land's End Northern Peak
- LocationData(LANDS_END_NORTHERN_PEAK_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Brave the spikes to climb the northern peak", 3002 + treasure_index_offset), # Money chest
- #Land's End
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Definitely requires Quintar *wink* among the first spikes 1", 2849 + treasure_index_offset), #Ether chest
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Definitely requires Quintar *wink* among the first spikes 2", 3003 + treasure_index_offset), #Potion chest
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - To defeat the Huns", 2740 + treasure_index_offset, lambda state: logic.has_glide(state) or logic.has_vertical_movement(state)), #Blue Cape chest
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Tucked up high against River Cats Ego", 1692 + treasure_index_offset), #Blue Cape chest
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - In spikes and storm", 1358 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Defender chest
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Fancy some spikes cliff diving?", 1693 + treasure_index_offset), #Rune Ward chest
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Inside the shrine", 3017 + treasure_index_offset), #Ether chest
- LocationData(LANDS_END_AP_REGION, "Overpass Chest - Lonely mountain ledge below owl shrine", 3678 + treasure_index_offset), #(191, 177, -214) 9th Scrap on main Overpass map
- #Owl Tree
- LocationData(OWL_TREE_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - By the lovely owl tree", 1561 + treasure_index_offset), # Callisto Stone chest
-
- #NPCs
- #Land's End Northern Peak
- LocationData(LANDS_END_NORTHERN_PEAK_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Lets get down to business in the mountains for Gold", 2848 + npc_index_offset), #Ingot
- #Land's End
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Pillar Gold by River Cats Ego", 2850 + npc_index_offset), #Ore
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Gold in spikes and storm", 2851 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), #Dust
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Gold behind the shrine", 2852 + npc_index_offset), #Ingot
- #Owl Tree
- LocationData(OWL_TREE_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Owl Drum", 1176 + npc_index_offset),
-
- #Slip Glide Ride
- #Treasure chests
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Back out to 1st room", 2554 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY)), #Money chest
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Climb up and fall down", 1172 + treasure_index_offset), #Plate of Lion chest
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Sparks and tar", 1298 + treasure_index_offset), #Red Door Key chest
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - To the left through 2nd red lock", 1698 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY, 2)), #Red Door Key chest
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Through 1st red lock", 1716 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY)), #Red Door Key chest
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Past the mean Ibek jump", 1282 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY)), #Sages Walker chest
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Nickelodeon slime time :)", 1269 + treasure_index_offset), #Seekers Garb chest
-
- #Sequoia Athenaeum
- #Treasure chests
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Atop the shelves above the books door", 2932 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #(412, 200, -551) Spellsword Helm chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Bullshit book-smart Chips Challenge", 2569 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), #(403, 199, -547) Z-Potion Pouch chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Brain-dumb Chips Challenge", 2322 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #(415, 180, -570) Ice Puzzle Key chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - 3rd library Chips Challenge", 2375 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(396, 180, -570) Ice Puzzle Key chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Chips Challenge ice squared", 2341 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 2) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(396, 164, -570) Ice Puzzle Key chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Chips Challenge we cheated on this one", 2371 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 3) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(415, 164, -572) Ice Puzzle Key chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Triple Chip Challenge", 2372 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 4) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(434, 164, -570) Ice Puzzle Key chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Shattered labyrinth Chips Challenge", 2373 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 5) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(424, 148, -570) Ice Puzzle Key chest
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - You expected another Chips Challenge, but it was me, Dio!", 2335 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 6) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(415, 131, -565) Skeleton Key chest
-
- #Northern Stretch
- #Treasure chests
- LocationData(SUMMONERS_TOWER_AP_REGION, "Overpass Chest - At the base of Summoners Lookout", 3655 + treasure_index_offset), #Northern Stretch map in Overpass (Outpost)
-
- #Castle Ramparts
- #Treasure chests
- #Lone Chest Rampart
- LocationData(LONE_CHEST_RAMPART_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Tucked beside eastern turret", 1547 + treasure_index_offset), #(443, 206, -378) Money chest
- #Peak Ramparts
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Below the crystal", 2908 + treasure_index_offset), #(407, 228, -383) Castle Ramparts map chest
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Jump down from eastern save point", 2742 + treasure_index_offset), #(440, 227, -386) Conquest chest
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Jump down from western save point", 2741 + treasure_index_offset), #(369, 227, -386) Rune Sword chest
- #Technically Castle Sequoia but they're in a locked room only accessible from Peak Ramparts
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Locked Ramparts storage room 1", 2758 + treasure_index_offset, lambda state: logic.has_key(state, RAMPART_KEY)), #(375, 232, -452) (Skums) Decapitator chest
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Locked Ramparts storage room 2", 3657 + treasure_index_offset, lambda state: logic.has_key(state, RAMPART_KEY)), #(371, 231, -457) (Skums) Castle Sequoia map chest
-
- #NPCs
- #Peak Ramparts
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " NPC - Western Gold above spikes", 2843 + npc_index_offset, lambda state: logic.has_glide(state)), #(354, 231, -429) Ingot
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " NPC - Eastern Gold above spikes", 2842 + npc_index_offset, lambda state: logic.has_glide(state)), #(458, 231, -436) Ore
-
- #The Chalice of Tar
- #Treasure chests
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Chest - At the tippy-top", 3544 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), #The Chalice of Tar map chest
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Chest - Dont let your feathers touch the tar", 2587 + treasure_index_offset), #Vermillion Book chest
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Chest - Post tar tunnel", 2806 + treasure_index_offset), #Windsong chest
-
- #NPCs
- #Todo NPCs Job Masters: The Chalice of Tar has Master Mimic ID 3606 (526, 234, -438); gives you Mimic Seal in exchange for job mastery
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " NPC - Gold sparkling above the Overpass on the way up", 2841 + npc_index_offset), #Ore
-
- #Flyers Crag
- #Treasure chests
- LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " Chest - You cant miss it", 3656 + treasure_index_offset), #(658, 216, -170) Flyers Crag map chest
-
- #NPCs
- LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " NPC - Gold twinsies the 1st south of Ganymede Shrine", 2820 + npc_index_offset), #(695, 137, -159) Dust
- LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " NPC - Gold twinsies the 2nd south of Ganymede Shrine", 2819 + npc_index_offset), #(686, 132, -162) Ingot
-
- #Flyers Lookout
- #Treasure chests
- #There are no checks here unless an Overpass Scrap shows up
-
- #Jidamba Tangle
- #Treasure chests
- #Forest Floor
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Inside overgrown building E of Eaclaneya", 1629 + treasure_index_offset), # Demon Plate chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Atop overgrown building E of Eaclaneya", 3028 + treasure_index_offset), # Ether chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - NW foliage", 2807 + treasure_index_offset), # Partizan chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Tucked below NW foliage", 3025 + treasure_index_offset), # Potion chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - North of foliage river", 3027 + treasure_index_offset), # Z-Potion chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Smack in the center of the foliage", 1632 + treasure_index_offset), # Foliage Key chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Up a tree in north foliage", 3024 + treasure_index_offset), # Ether chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Along a river through the foliage", 3026 + treasure_index_offset), # Ether chest
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Island in the river through the foliage", 3011 + treasure_index_offset), # Tower Shield chest
- #Jidamba Cave
- LocationData(JIDAMBA_CAVE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Underground sneaky passage by NE cave exit", 2797 + treasure_index_offset), # Ravens Hood chest
- LocationData(JIDAMBA_CAVE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Hop from underground root to sneaky passage pond", 2798 + treasure_index_offset, lambda state: logic.has_glide(state)), # Ravens Cloak chest
- LocationData(JIDAMBA_CAVE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Accompanied by orange cave flowers", 1435 + treasure_index_offset, lambda state: logic.has_glide(state)), # Cave Key chest
- #Eaclaneya Courtyard
- LocationData(JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Eaclaneya entrance hall", 2919 + treasure_index_offset), # Jidamba Tangle map chest
- #Summit
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Tucked against eastern side of Eaclaneya", 2801 + treasure_index_offset), # Flame Guard chest
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Atop Eaclaneya", 2808 + treasure_index_offset), # Rune Bow chest
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Tucked against western side of Eaclaneya", 2802 + treasure_index_offset), # Flamespike chest
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Scramble down the escarpment from Weaver Outpost to pedestal", 2803 + treasure_index_offset), # Siege Bow chest
- #Canopy
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Up in the canopy south of shrine", 1171 + treasure_index_offset), # Demon Helm chest
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Smack in the center of the canopy", 1631 + treasure_index_offset), # Canopy Key chest
-
- #NPCs
- #Todo NPCs Job Masters: Jidamba Tangle (Outpost) has Master Weaver ID 3579 (627, 140, 77); gives you Weaver Seal in exchange for job mastery
- #Forest Floor
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond atop broken ruins along the beach", 2872 + npc_index_offset, lambda state: logic.has_glide(state)), # Ore
- #Diamondsmith
- LocationData(JIDAMBA_DIAMONDSMITH_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Damp Diamond lurking beneath diamondsmith", 2869 + npc_index_offset), # Ingot
- #Southwest Beach
- LocationData(JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond hot girl summer on the beach", 2873 + npc_index_offset), # Dust
- #South Cliff
- LocationData(JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Splish splash Diamond", 2871 + npc_index_offset), # Dust
- #Southwest Beach Cliff
- LocationData(JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond at southern mouth of cave", 2874 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), # Ingot
- #Summit
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Fly from Summoners weeping tree to hot tub Diamond", 2876 + npc_index_offset, lambda state: logic.has_glide(state)), # Ingot
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond atop broken ruins by the Summoner tree", 2875 + npc_index_offset, lambda state: logic.has_glide(state)), # Ore
- #Canopy
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Chloe Queen of the Canopy", 2775 + npc_index_offset), #Super Rod (828, 119, 99); Fixed Missable
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Splash Mountain Diamond (pool at S end of canopy)", 2870 + npc_index_offset), # Ore
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Splash Mountain Gold (pool at NE end of canopy)", 2900 + npc_index_offset), # Ore
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond in the boughs above the shrine", 2898 + npc_index_offset), # Ingot
- LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Canopy Gold above big lake", 2899 + npc_index_offset), #Ingot
-
- #Jidamba Eaclaneya
- #Treasure chests
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Climb the lamp in the south room", 2799 + treasure_index_offset, lambda state: logic.has_glide(state) and logic.has_vertical_movement(state)), #Celestial Crown chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - At the end of spike hallway", 2755 + treasure_index_offset, lambda state: logic.has_glide(state)), #Flame Sword chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Monster cubby", 2920 + treasure_index_offset), #Jidamba Eaclaneya map chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Practice your swimming", 2282 + treasure_index_offset, lambda state: logic.has_glide(state)), #Ether Pouch chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Right side of the swimming puzzle", 2289 + treasure_index_offset, lambda state: logic.has_glide(state)), #Staff of Balance chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Glass elevator room", 2301 + treasure_index_offset, lambda state: logic.has_glide(state)), #Stardust Wand chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Underwater swimming puzzle", 2308 + treasure_index_offset, lambda state: logic.has_glide(state)), #Flameseeker chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Left side of underwater swimming puzzle", 2317 + treasure_index_offset, lambda state: logic.has_glide(state)), #Viridian Book chest
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Salmon Violin past the fish puzzles", 2288 + treasure_index_offset, lambda state: logic.has_glide(state)),
-
- #NPCs
- #1 Diamond Dust on Jidamba Eaclaneya Fish Floor map has been categorized under the Capital Pipeline
-
- #The Deep Sea
- #Treasure chests
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Descend into undersea vent where the flesh eaters live 1", 3451 + treasure_index_offset), #(878, 39, -536) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Touching Jidamba", 3658 + treasure_index_offset), #Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Ruins just south of Jidamba 1", 3659 + treasure_index_offset), #Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked sunken house off Jidamba with mighty arch 1", 3660 + treasure_index_offset, lambda state: logic.has_key(state, FORGOTTEN_KEY)), #(657, 53, 165) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Buried tunnel NW of Jidamba", 3661 + treasure_index_offset), #(545, 47, -31) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - By The Depths chasm SE of Shoudu Province", 3662 + treasure_index_offset), #(890, 51, -66) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Crumbling shrine 1", 3663 + treasure_index_offset), #(842, 53, -359) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Beside an undersea microruin NW of Tall Tall Heights", 3666 + treasure_index_offset), #(-23, 39, -557) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Underwater cove south of volcano", 3667 + treasure_index_offset), #(94, 59, 133) Deep Sea Scrap chest
- #next 2 scraps from The Deep Sea (Sand Bar)
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Sunken shipwreck off west coast of Sara Sara Beach", 3664 + treasure_index_offset), #(-364, 53, -183) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Undersea valley S of Sara Sara Beach", 3665 + treasure_index_offset), #(-226, 49, 164) Deep Sea Scrap chest
- #next scrap from The Deep Sea (Shrooms)
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Cavern below N coast of Tall Tall Heights", 3668 + treasure_index_offset), #(254, 53, -547) Deep Sea Scrap chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Descend into undersea vent where the flesh eaters live 2", 2767 + treasure_index_offset), #(872, 39, -517) Forgotten Key chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Crumbling shrine 2", 2290 + treasure_index_offset), #(838, 52, -357) Oven Mitt chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Ruins just south of Jidamba 2", 2937 + treasure_index_offset), #Paladin Wand chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Hidden tunnel in canyon W of cheeky sunroof NW of Jidamba", 2589 + treasure_index_offset), #(582, 47, -51) Rampart Key chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked sunken house off Jidamba with mighty arch 2", 2766 + treasure_index_offset, lambda state: logic.has_key(state, FORGOTTEN_KEY)), #(663, 54, 165) Soul Keeper chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked *wink* sunken house off Jidamba 1", 2768 + treasure_index_offset), #(649, 53, 195) Zether chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked *wink* sunken house off Jidamba 2", 3726 + treasure_index_offset), #(646, 53, 196) Zether Pouch chest
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Quizard challenge below N coast of Tall Tall Heights", 595 + treasure_index_offset), #(270, 29, -591) Treasure Finder chest
-
- #NPCs
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Locked *wink* sunken house 2 off Jidamba Diamond", 2519 + npc_index_offset), #(639, 54, 182) Dust
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Locked *wink* sunken house 2 off Jidamba Gold", 2518 + npc_index_offset), #(648, 54, 180) Dust
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Burrow to burrow crab", 3409 + npc_index_offset), #(20, 53, 251) Crab 1
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Tall tall crab S of volcano", 3426 + npc_index_offset), #(76, 76, 178) Crab 2
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab in underwater cove south of volcano", 3427 + npc_index_offset), #(96, 60, 131) Crab 3
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Zigzaggedy crab NW of Tall Tall Heights", 3428 + npc_index_offset), #(142, 51, -611) Crab 4
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab strolling around the undersea block", 3429 + npc_index_offset), #(60, 53, -609) Crab 5
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab clinging to SW underwater volcano slope", 3430 + npc_index_offset), #(54, 62, 155) Crab 6
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Low low crab S of volcano", 3431 + npc_index_offset), #(128, 49, 196) Crab 7
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Serpentine crab S of crab hole", 3432 + npc_index_offset), #(239, 53, -613) for some reason crab 8 is on (Depths Fix) submap
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Patrolling crab S of crab hole", 3433 + npc_index_offset), #(254, 53, 215) Crab 9
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab living on the edge S of crab hole", 3434 + npc_index_offset), #(306, 53, 224) Crab 10
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab on a mission N of Tall Tall Heights", 3435 + npc_index_offset), #(288, 53, -620) Crab 11
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Middle of nowhere sandy speedster crab", 3436 + npc_index_offset), #(58, 52, 244) Crab 12
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Putt Putt Crab mows the lawn", 3437 + npc_index_offset), #(54, 52, 200) Crab 13
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - King of the middle of nowhere ocean crab", 3438 + npc_index_offset), #(52, 76, -616) Crab 14
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab scuttling SE of volcano", 3439 + npc_index_offset), #(207, 53, 152) Crab 15
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Undersea Crab People Crab Retirement Home south of Salmon Race start", 3424 + npc_index_offset, lambda state: state.has(UNDERSEA_CRAB, player, 15)), #(256, 63, 113)
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Fastest squid in the West", 3450 + npc_index_offset), #(-314, 64, -624) (swims in a fixed path; slightly slower than golden Quintar but faster than royal salmon) Z35_SpeedOcto
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Sunken shipwreck Gold off west coast of Sara Sara Beach 1", 2855 + npc_index_offset), #(-367, 53, -182) Dust
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Sunken shipwreck Gold off west coast of Sara Sara Beach 2", 2857 + npc_index_offset), #(-356, 55, -167) Ingot
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Sunken shipwreck Gold off west coast of Sara Sara Beach 3", 2856 + npc_index_offset), #(-370, 53, -173) Ore
-
- #Jade Cavern
- #Treasure chests
- LocationData(JADE_CAVERN_AP_REGION, JADE_CAVERN_DISPLAY_NAME + " Chest - Tell Archie to say hi to the Quizard", 3604 + treasure_index_offset), #(239, 99, -124) Jade Cavern map chest
-
- #NPCs
- #Todo NPCs CheckOrNot Job Masters: this guy trades you a thing for each job seal you've gotten from a job master for mastering that job
- #LocationData(JADE_CAVERN, JADE_CAVERN_DISPLAY_NAME + " NPC - Jade Cavern Map chest", 3603 + npc_index_offset), #(255, 91, -90)
-
- #Continental Tram
- #Treasure chests
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Chest - Tickets please 1", 1844 + treasure_index_offset), #Continental Tram map chest
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Chest - End of the line", 3012 + treasure_index_offset), #Nomads Guard chest
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Chest - Tickets please 2", 1654 + treasure_index_offset), #Tram Key chest
-
- #NPCs
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " NPC - Diamond hanging out with the conscript 1", 2895 + npc_index_offset), #Dust
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " NPC - Diamond hanging out with the conscript 2", 2894 + npc_index_offset), #Ingot
-
- #Zones (End-Game)
- #Ancient Labyrinth
- #Treasure chests
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - Dungeon East sneaky hole in wall", 1274 + treasure_index_offset), #(-186, 125, -316) F2 Money chest
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - Dungeon West sneaky hole in wall", 2412 + treasure_index_offset), #(-190, 125, -316) F2 Archmage Vest chest
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B1 Searching for greener pastures", 1738 + treasure_index_offset), #(-209, 87, -311) F3 Vita Crown chest
- LocationData(LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B2 North weird rebar hallway", 2924 + treasure_index_offset), #(-185, 63, -363) F4 Judgement chest
- LocationData(LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B2 East weird rebar hallway", 2926 + treasure_index_offset), #(-162, 63, -336) F4 Zether Pouch chest
- LocationData(LABYRINTH_CORE_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B4 Tar pit platform", 3649 + treasure_index_offset), #(-185, 38, -301) F6 Ancient Labyrinth map chest
- LocationData(LABYRINTH_CORE_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - Dog-headed dogshit boss", 2591 + treasure_index_offset), #(-219, 27, -350) F7 Mirror Shield chest
-
- #NPCs
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " NPC - Dungeon East sneaky hole in wall Diamond", 2881 + npc_index_offset), #(-186, 125, -300) F2 Ingot
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " NPC - Sneaky hole in wall Diamond in entry touchdown room", 2880 + npc_index_offset), #(-182, 126, -326) F2 Ore
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " NPC - B1 Thats right, Diamond goes in the bluish-white square hole", 2882 + npc_index_offset), #(-200, 98, -334) F3 Ingot
-
- #The Sequoia
- #Treasure chests
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Waterfall climb sneaky hollow", 2934 + treasure_index_offset), #(-286, 90, -539) Stealth Cape chest
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Balanced on bark", 2437 + treasure_index_offset), #(-250, 174, -512) Battle Band chest
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Back indoors then follow water channel outside", 2935 + treasure_index_offset), #(-296, 182, -533) Sange chest
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Waterfall climb sneaky eastern exit", 2884 + treasure_index_offset), #(-223, 118, -541) Zether Pouch chest
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Go out on a limb", 2887 + treasure_index_offset), #(-244, 168, -498) Z-Potion Pouch chest
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Back indoors by water channel", 2933 + treasure_index_offset), #(-282, 182, -528) Aphotic Edge chest
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Post-boss victory pedestal", 2451 + treasure_index_offset), #(-272, 241, -544) The Hand of Midas chest
-
- #NPCs
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Low-hanging Diamond fruit", 2885 + npc_index_offset), #(-223, 160, -530) Dust
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Waterfall climb sneaky eastern exit Diamond", 2883 + npc_index_offset), #(-237, 117, -563) Ore
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Diamond glittering on a bough", 2886 + npc_index_offset), #(-311, 160, -540) Ore
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Post-boss victory Diamond 1", 2889 + npc_index_offset), #(-269, 240, -545) Dust
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Post-boss victory Diamond 2", 2890 + npc_index_offset), #(-268, 240, -547) Ingot
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Post-boss victory Diamond 3", 2888 + npc_index_offset), #(-275, 240, -546) Ore
-
- #The Depths
- #Treasure chests
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Chest - Down among glowing blue seaweed between Poko Poko Desert & Jidamba 1", 2588 + treasure_index_offset), #(-358, 1, 18) Cerulean Book chest
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Chest - Down among glowing blue seaweed between Poko Poko Desert & Jidamba 2", 2714 + treasure_index_offset), #(-357, 2, 20) #Z-Potion chest
-
- #NPCs
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond on blue rock 1", 2865 + npc_index_offset), #(692, 20, -618) Dust
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond by seaweed river 1", 2868 + npc_index_offset), #(823, 17, -595) Dust
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Floating rock Diamond SE of Volcano 1", 2891 + npc_index_offset), #(161, 20, 240) Dust
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Diamond dive beside sunken shipwreck 1", 1214 + npc_index_offset), #(-377, 20, -220) Ingot
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Diamond dive beside sunken shipwreck 2", 2859 + npc_index_offset), #(-373, 19, -261) Ingot
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond on blue rock 2", 2863 + npc_index_offset), #(784, 17, -611) Ingot
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of sunken town on blue rock", 2866 + npc_index_offset), #(629, 20, -615) Ingot
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Floating rock Diamond SE of Volcano 2", 2893 + npc_index_offset), #(180, 20, 255) Ingot
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Yellow flower Diamond W of Sara Sara Beach", 1213 + npc_index_offset), #(932, 19, -199) Ore
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Sneaky Diamond W of Sara Sara Beach", 2858 + npc_index_offset), #(924, 20, -235) Ore
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond by seaweed river 2", 2864 + npc_index_offset), #(760, 12, -612) Ore
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond by seaweed river 3", 2867 + npc_index_offset), #(722, 20, -604) Ore
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Floating rock Diamond SE of Volcano 3", 2892 + npc_index_offset), #(189, 30, 235) Ore
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Follow barnacled meat branches for Diamond 1", 2861 + npc_index_offset), #(-308, 12, 132) Dust
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Follow barnacled meat branches for Diamond 2", 2862 + npc_index_offset), #(-303, 14, 183) Ingot
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Follow barnacled meat branches for Diamond 3", 2860 + npc_index_offset), #(-359, 10, 162) Ore
-
- #Castle Sequoia
- #Treasure chests
- #Map and Decapitator chests categorized in Castle Ramparts since they're in a locked room there requiring the Ramparts key
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Hop through keyhole over lava", 2515 + treasure_index_offset), #(422, 169, -406) (Skums) Zether Pouch chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Stared at by lava miniboss", 1465 + treasure_index_offset), #(427, 170, -441) (Skums) Z-Potion chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Enjoy riding lava shrooms", 1447 + treasure_index_offset), #(409, 169, -406) (Skums) Z-Potion Pouch chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Biiig bounce!", 1472 + treasure_index_offset), #(375, 153, -405) (Bounce) Beads of Defense chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce up!!", 2949 + treasure_index_offset), #(401, 151, -404) (Bounce) Ether chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce around!", 2948 + treasure_index_offset), #(401, 151, -424) (Bounce) Fenix Syrup chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce right!", 2945 + treasure_index_offset), #(434, 154, -441) (Bounce) Potion chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce up!", 2922 + treasure_index_offset), #(395, 155, -453) (Bounce) Protector chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Chilly uno in Chips Challenge of doom", 2927 + treasure_index_offset), #(387, 134, -431) (Ice) Kings Guard chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Chilly duo in Chips Challenge of doom", 1492 + treasure_index_offset), #(387, 134, -427) (Ice) Royal Guard chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - One fish", 2479 + treasure_index_offset), #(401, 119, -415) (Fish) Dream Hunter chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Two fish", 2509 + treasure_index_offset), #(388, 122, -445) (Fish) Nightingale chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Redfish bluefish", 2484 + treasure_index_offset), #(397, 119, -415) (Fish) Oily Sword chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce for your life", 2510 + treasure_index_offset), #(364, 85, -424) (2D) Paladin Wand chest
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Throne snacks", 2505 + treasure_index_offset), #(401, 250, -478) (Throne) New World Stone chest
-
- #NPCs
- #NPCs CheckOrNot: Z58_StrandedShard ID 3785 (401, 183, -382); this gives you a Gaea Shard if you're stuck: no
- #NPCs Blocker: i think this guy might only show up in the vanilla game's randomizer? checks if Z58_EleOn (Z58 is Castle Sequoia); Z58Progression_Gate ID 3824 (400, 250, -478)
-
- #The New World
- #Treasure chests
- LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Chest - NW lavafall", 2930 + treasure_index_offset), #(-134, 8, 230) Lunar Mail chest
- LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Chest - Desolate peninsula past bounce shrooms", 2931 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #(-11, 12, -577) Mages Pike chest
- LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Chest - Tiny shrooms keep shed", 1938 + treasure_index_offset), #(-85, 8, 142) The New World map chest
- ]
- return location_table
-
-
-def get_crystal_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
- logic = CrystalProjectLogic(player, options)
- location_table: List[LocationData] = [
- # Zones (Beginner)
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + FENCER_JOB_CRYSTAL_LOCATION, 130 + crystal_index_offset),
- LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + SHAMAN_JOB_CRYSTAL_LOCATION, 35 + crystal_index_offset),
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + SCHOLAR_JOB_CRYSTAL_LOCATION, 166 + crystal_index_offset),
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + AEGIS_JOB_CRYSTAL_LOCATION, 68 + crystal_index_offset),
- # Zones (Advanced)
- LocationData(BEATSMITH_DISCO_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + BEATSMITH_JOB_CRYSTAL_LOCATION, 1087 + crystal_index_offset),
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + HUNTER_JOB_CRYSTAL_LOCATION, 621 + crystal_index_offset),
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + CHEMIST_JOB_CRYSTAL_LOCATION, 970 + crystal_index_offset),
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + REAPER_JOB_CRYSTAL_LOCATION, 908 + crystal_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)),
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + NINJA_JOB_CRYSTAL_LOCATION, 699 + crystal_index_offset),
- LocationData(RIVER_CATS_EGO_AP_REGION, RIVER_CATS_EGO_AP_REGION + NOMAD_JOB_CRYSTAL_LOCATION, 630 + crystal_index_offset),
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + DERVISH_JOB_CRYSTAL_LOCATION, 1121 + crystal_index_offset),
- # Zones (Expert)
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + SAMURAI_JOB_CRYSTAL_LOCATION, 1206 + crystal_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, THIRD_SKY_ARENA_FIGHT_LEVEL)),
- # Can just swim or defeat the Undercity Masters; Blade Master: Ibek or Owl, Shadow Master: Horizontal or Fish, Duel Master: Ibek or Owl; to defeat all masters, you either need both ibek + quintar, owl, or fish
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + ASSASSIN_JOB_CRYSTAL_LOCATION, 1204 + crystal_index_offset, lambda state: (logic.has_horizontal_movement(state) and logic.has_vertical_movement(state)) or logic.has_glide(state) or logic.has_swimming(state)),
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + VALKYRIE_JOB_CRYSTAL_LOCATION, 1086 + crystal_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)),
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + SUMMONER_JOB_CRYSTAL_LOCATION, 1714 + crystal_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY, 3)),
- # (404, 243, -386)
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + BEASTMASTER_JOB_CRYSTAL_LOCATION, 1370 + crystal_index_offset, lambda state: logic.has_glide(state)),
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + MIMIC_JOB_CRYSTAL_LOCATION, 3701 + crystal_index_offset, lambda state: logic.has_vertical_movement(state)),
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + WEAVER_JOB_CRYSTAL_LOCATION, 2403 + crystal_index_offset),
- ]
-
- return location_table
-
-def get_boss_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
- logic = CrystalProjectLogic(player, options)
- location_table: List[LocationData] = [
- #IDs here are specifically the spark IDs or NPC IDs for where you go to fight them from Crystal Edit
- #Summons
- #Not included: Pinga (unused)
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon", 477 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, SHAKU_FIGHT_LEVEL)), #(118, 109, 10) Monster ID: 102
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Boss - Niltsi Summon", 1109 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, NILTSI_FIGHT_LEVEL)), #376, 178, -345 (Capital Sequoia (Maze) map) Monster ID: 93
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Boss - Guaba Summon", 1138 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, GUABA_FIGHT_LEVEL)), #-50, 91, -330 Monster ID: 94
- LocationData(THE_UNDERCITY_AP_REGION, "Underpass Boss - Pah Summon", 1130 + boss_index_offset, lambda state: (logic.has_swimming(state) or logic.has_glide(state)) and state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, PAH_FIGHT_LEVEL)), #614, 91, -213 Monster ID: 97
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Boss - Tira Summon", 1132 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and (logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player)) and logic.is_area_in_level_range(state, TIRA_FIGHT_LEVEL)), #(720, 138, -278) Monster ID: 98
- LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Boss - Ioske Summon", 1111 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, IOSKE_FIGHT_LEVEL)), #97, 126, -211 Monster ID: 92
- LocationData(PAMOA_TREE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Boss - Pamoa Summon", 1136 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, PAMOA_FIGHT_LEVEL)), #498, 218, -412 Monster ID: 91
- LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Boss - Juses Summon", 1134 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, JUSES_FIGHT_LEVEL)), #(672, 124, 106) Monster ID: 99
- #Mind's Delusion is part of the Coyote fight
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Boss - Coyote Summon", 1140 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, COYOTE_FIGHT_LEVEL)), #(-60, 53, 202) Monster ID: 95
-
- #Bosses
- #Not included: Bone Drinker (unused), Rexosaur (too RNG), Astley/Chloe/Reid/Talon Final Fights, Buttermint (you monster)
- LocationData(DELENDE_PEAK_AP_REGION, DELENDE_DISPLAY_NAME + " Boss - Troll", 153 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, TROLL_FIGHT_LEVEL)), #Monster ID: 8
- LocationData(GRAN_AP_REGION, DELENDE_DISPLAY_NAME + " Boss - Gran...?", 183 + boss_index_offset), #Monster ID: 28 (min level 30)
- LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Boss - Bone Thief", 175 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, BONE_THIEF_FIGHT_LEVEL)), #Monster ID: 12
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Boss - Guardian", 143 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, GUARDIAN_FIGHT_LEVEL)), #Monster ID: 23
- LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Boss - Canal Beast", 138 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, CANAL_BEAST_FIGHT_LEVEL)), #Monster ID: 18
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Boss - Sepulchra", 167 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, SEPULCHRA_FIGHT_LEVEL)), #Monster ID: 27
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Boss - Knight", 128 + boss_index_offset, lambda state: logic.has_jobs(state, 3) and logic.is_area_in_level_range(state, KNIGHT_FIGHT_LEVEL)), #Monster ID: 212 (using Z7_CrystalCheckerNPC)
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Boss - Parasite", 333 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, PARASITE_FIGHT_LEVEL)), #Monster ID: 38
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Boss - Defeat L60 dummy and it shall appear...!", 3530 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, DUMMY_BOSS_FIGHT_LEVEL)), #Monster ID: 303
- LocationData(MOAT_SHALLOWS_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Boss - Enami", 458 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, ENAMI_FIGHT_LEVEL)), #Monster ID: 49
- LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Boss - Blood Slop", 758 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, BLOOD_SLOP_FIGHT_LEVEL)), #Monster ID: 4
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Boss - Fancy Quintar", 971 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, FANCY_QUINTAR_FIGHT_LEVEL)), #Monster ID: 68
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Boss - Warden", 907 + boss_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY) and logic.is_area_in_level_range(state, WARDEN_FIGHT_LEVEL)), #Monster ID: 37
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Boss - Crag Demon", 1118 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, CRAG_DEMON_FIGHT_LEVEL)), #Monster ID: 217
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Boss - Kuromanto", 698 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, KUROMANTO_FIGHT_LEVEL)), #Monster ID: 63
- LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Boss - Possessor", 1674 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, POSSESSOR_FIGHT_LEVEL)), #Monster ID: 221
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Boss - Final Sky Arena Fight: Arachlea", 1366 + boss_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #Monster ID: 252 (SkyArenaRegistrar)
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Boss - Blade Master", 1939 + boss_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, BLADE_MASTER_FIGHT_LEVEL)), #Monster ID: 145
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Boss - Shadow Master", 1940 + boss_index_offset, lambda state: (logic.has_swimming(state) or logic.has_horizontal_movement(state)) and logic.is_area_in_level_range(state, SHADOW_MASTER_FIGHT_LEVEL)), #Monster ID: 144
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Boss - Duel Master", 1941 + boss_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, DUEL_MASTER_FIGHT_LEVEL)), #Monster ID: 146
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Boss - Ancient Sword", 821 + boss_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2) and logic.is_area_in_level_range(state, ANCIENT_SWORD_FIGHT_LEVEL)), #Monster ID: 59
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Boss - Iguanadon & Iguanadin", 862 + boss_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY) and logic.is_area_in_level_range(state, IGUANADON_AND_DIN_FIGHT_LEVEL)), #Monster ID: 78 and 100
- LocationData(EASTERN_CHASM_AP_REGION, EASTERN_CHASM_DISPLAY_NAME + " Boss - Undergrowth", 3476 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, UNDERGROWTH_FIGHT_LEVEL)), #Monster ID: 293
- LocationData(SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Boss - Hermetic", 3637 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, HERMETIC_FIGHT_LEVEL)), #Monster ID: 309
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Boss - The Owlbear", 2104 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, OWLBEAR_FIGHT_LEVEL)), #Monster ID: 143
- LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Boss - Red Guardian", 1713 + boss_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY, 3) and logic.is_area_in_level_range(state, RED_GUARDIAN_FIGHT_LEVEL)), #Monster ID: 224
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Boss - Rampart Demon", 1373 + boss_index_offset, lambda state: logic.has_glide(state) and logic.is_area_in_level_range(state, RAMPART_DEMON_FIGHT_LEVEL)), #Monster ID: 222
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Boss - Conscript", 1621 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, CONSCRIPT_FIGHT_LEVEL)), #Monster ID: 242
- LocationData(LABYRINTH_CORE_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Boss - Anubis", 2473 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, ANUBIS_FIGHT_LEVEL)), #Monster ID: 117
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Boss - Spirit Cage", 2453 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, SPIRIT_CAGE_FIGHT_LEVEL)), #Monster ID: 192
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Devourer", 1265 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, DEVOURER_FIGHT_LEVEL)), #Monster ID: 171
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Old One", 206 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, OLD_ONE_FIGHT_LEVEL)), #Monster ID: 170
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Enforcer", 1128 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, ENFORCER_FIGHT_LEVEL)), #Monster ID: 172
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Peacekeeper", 2579 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, PEACEKEEPER_FIGHT_LEVEL)), #Monster ID: 169
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - With STEM WARD in hand, offer 4 Deity Eyes to battle Gabriel", 2580 + boss_index_offset, lambda state: state.has(DEITY_EYE, player, 4) and state.has(STEM_WARD, player) and logic.is_area_in_level_range(state, GABRIEL_FIGHT_LEVEL)), #Monster ID: 241
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Boss - Parasite X", 1457 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, PARASITE_X_FIGHT_LEVEL)), #Monster ID: 198 (Castle Sequoia (Skums) Z58_TrapdoorGuard)
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Boss - Neo Warden", 1477 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, NEO_WARDEN_FIGHT_LEVEL)), #Monster ID: 200 (Castle Sequoia (Bounce) Z58_F2TrapdoorGuard)
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Boss - Akamanto", 1503 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, AKAMANTO_FIGHT_LEVEL)), #Monster ID: 201 (Castle Sequoia (Ice) Z58_F3TrapdoorGuard)
- LocationData(THE_OLD_WORLD_AP_REGION, THE_OLD_WORLD_DISPLAY_NAME + " Boss - Periculum", 3650 + boss_index_offset), #Monster ID: 268 (min level 70)
- ]
- return location_table
-
-def get_shop_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
- logic = CrystalProjectLogic(player, options)
-
- location_table: List[LocationData] = [
- #Zones (Beginner)
- #Spawning Meadows
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 1", 10013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 2", 20013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 3", 30013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 4", 40013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 5", 50013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 6", 60013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 7", 70013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 8", 80013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 9", 90013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 10", 100013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 11", 110013 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 12", 120013 + shop_index_offset),
-
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Stew 1", 10014 + shop_index_offset),
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Stew 2", 20014 + shop_index_offset),
-
- #Delende
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Weapon Merchant 1", 10052 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Weapon Merchant 2", 20052 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Weapon Merchant 3", 30052 + shop_index_offset),
-
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 1", 10063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 2", 20063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 3", 30063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 4", 40063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 5", 50063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 6", 60063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 7", 70063 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 8", 80063 + shop_index_offset),
-
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 1", 10199 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 2", 20199 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 3", 30199 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 4", 40199 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 5", 50199 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 6", 60199 + shop_index_offset),
-
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 1", 10115 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 2", 20115 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 3", 30115 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 4", 40115 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 5", 50115 + shop_index_offset),
-
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 1", 10446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 2", 20446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 3", 30446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 4", 40446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 5", 50446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 6", 60446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 7", 70446 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 8", 80446 + shop_index_offset),
-
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Item Merchant camped in front of Pale Grotto 1", 10266 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Item Merchant camped in front of Pale Grotto 2", 20266 + shop_index_offset),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Item Merchant camped in front of Pale Grotto 3", 30266 + shop_index_offset),
-
- #Seaside Cliffs
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 1", 10116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 2", 20116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 3", 30116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 4", 40116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 5", 50116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 6", 60116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 7", 70116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 8", 80116 + shop_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 9", 90116 + shop_index_offset),
-
- #Mercury Shrine
- LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Shop - Attendant 1", 10132 + shop_index_offset),
- LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Shop - Attendant 2", 20132 + shop_index_offset),
- LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Shop - Attendant 3", 30132 + shop_index_offset),
-
- #Proving Meadows
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 1", 10253 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 2", 20253 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 3", 30253 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 4", 40253 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 5", 50253 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 6", 60253 + shop_index_offset),
-
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 1", 10133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 2", 20133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 3", 30133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 4", 40133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 5", 50133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 6", 60133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 7", 70133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 8", 80133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 9", 90133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 10", 100133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 11", 110133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 12", 120133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 13", 130133 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 14", 140133 + shop_index_offset),
-
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 1", 10117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 2", 20117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 3", 30117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 4", 40117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 5", 50117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 6", 60117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 7", 70117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 8", 80117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 9", 90117 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 10", 100117 + shop_index_offset),
-
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 1", 10134 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 2", 20134 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 3", 30134 + shop_index_offset),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 4", 40134 + shop_index_offset),
-
- #Zones (Advanced)
- #Capital Sequoia
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Gaea Shrine Shop - Attendant 1", 10379 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, "Gaea Shrine Shop - Attendant 2", 20379 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Candy Merchant 1", 10575 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Candy Merchant 2", 20575 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 1", 11158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 2", 21158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 3", 31158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 4", 41158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 5", 51158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 6", 61158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 7", 71158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 8", 81158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 9", 91158 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 10", 101158 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 1", 10599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 2", 20599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 3", 30599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 4", 40599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 1", 50599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 2", 60599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 3", 70599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 4", 80599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 5", 90599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 6", 100599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 1", 10600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 2", 20600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 3", 30600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 4", 40600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 1", 50600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 2", 60600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 3", 70600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 4", 80600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Heavy Armor 1", 10601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Heavy Armor 2", 20601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Heavy Armor 3", 30601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Heavy Armor 1", 40601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Heavy Armor 2", 50601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Heavy Armor 3", 60601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Medium Armor 1", 10602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Medium Armor 2", 20602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Medium Armor 1", 30602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Medium Armor 2", 40602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Magic Weapons 1", 10603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Magic Weapons 2", 20603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Magic Weapons 3", 30603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Magic Weapons 1", 40603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Magic Weapons 2", 50603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Magic Weapons 3", 60603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Light Armor 1", 10604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Light Armor 2", 20604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Light Armor 1", 30604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Light Armor 2", 40604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 1", 10500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 2", 20500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 3", 30500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 4", 40500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 5", 50500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 6", 60500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 7", 70500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 8", 80500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 9", 90500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 10", 100500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 11", 110500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 12", 120500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 13", 130500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 14", 140500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 15", 150500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 16", 160500 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 17", 170500 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 1", 10501 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 2", 20501 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 3", 30501 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 4", 40501 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 5", 50501 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 6", 60501 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Milk Man", 10579 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 1", 10416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 2", 20416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 3", 30416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 4", 40416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 5", 50416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 6", 60416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 7", 70416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 8", 80416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 9", 90416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 10", 100416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 11", 110416 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 12", 120416 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 1", 10417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 2", 20417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 3", 30417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 4", 40417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 5", 50417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 6", 60417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 7", 70417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 8", 80417 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 9", 90417 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 1", 10455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 2", 20455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 3", 30455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 4", 40455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 5", 50455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 6", 60455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 7", 70455 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 8", 80455 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 1", 10499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 2", 20499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 3", 30499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 4", 40499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 5", 50499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 6", 60499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 7", 70499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 8", 80499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 9", 90499 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 10", 100499 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 1", 10398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 2", 20398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 3", 30398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 4", 40398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 5", 50398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 6", 60398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 7", 70398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 8", 80398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 9", 90398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 10", 100398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 11", 110398 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 12", 120398 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 1", 10456 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 2", 20456 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 3", 30456 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 4", 40456 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 5", 50456 + shop_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 6", 60456 + shop_index_offset),
-
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Old Nans Stew", 10423 + shop_index_offset),
-
- #Salmon River
- #Poseidon Shrine Proper
- LocationData(POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION + " Shop - Attendant 1", 10631 + shop_index_offset),
- LocationData(POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION + " Shop - Attendant 2", 20631 + shop_index_offset),
- LocationData(POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION + " Shop - Attendant 3", 30631 + shop_index_offset),
-
- #Sara Sara Bazaar
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Old Nans Stew Subsidiary", 10957 + shop_index_offset),
-
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 1", 11386 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 2", 21386 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 3", 31386 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 4", 41386 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 5", 51386 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 6", 61386 + shop_index_offset),
-
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 1", 11193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 2", 21193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 3", 31193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 4", 41193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 5", 51193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 6", 61193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 7", 71193 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 8", 81193 + shop_index_offset),
-
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Fish Merchant 1", 10942 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Fish Merchant 2", 20942 + shop_index_offset),
-
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 1", 11603 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 2", 21603 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 3", 31603 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 4", 41603 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 5", 51603 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 6", 61603 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 7", 71603 + shop_index_offset),
-
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 1", 11604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 2", 21604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 3", 31604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 4", 41604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 5", 51604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 6", 61604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 7", 71604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 8", 81604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 9", 91604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 10", 101604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 11", 111604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 12", 121604 + shop_index_offset),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 13", 131604 + shop_index_offset),
-
- #Zones (Expert)
- #Shoudu Province
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 1", 10951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 2", 20951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 3", 30951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 4", 40951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 5", 50951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 6", 60951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 7", 70951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 8", 80951 + shop_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 9", 90951 + shop_index_offset),
-
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - No Shoudu Stew for you 1", 11199 + shop_index_offset),
-
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 1", 11614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 2", 21614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 3", 31614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 4", 41614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 5", 51614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 6", 61614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
-
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 1", 11535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 2", 21535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 3", 31535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 4", 41535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 5", 51535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 6", 61535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 7", 71535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 8", 81535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 9", 91535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 10", 101535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
-
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 1", 11544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 2", 21544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 3", 31544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 4", 41544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 5", 51544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 6", 61544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 7", 71544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 8", 81544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 9", 91544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 10", 101544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 11", 111544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 12", 121544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 13", 131544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 14", 141544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 15", 151544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 16", 161544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
-
- #Ganymede Shrine
- LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Shop - Attendant 1", 11574 + shop_index_offset),
- LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Shop - Attendant 2", 21574 + shop_index_offset),
- LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Shop - Attendant 3", 31574 + shop_index_offset),
-
- #Quintar Reserve
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 1", 10470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 2", 20470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 3", 30470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 4", 40470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 5", 50470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
-
- #Dione Shrine
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Attendant 1", 12253 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Attendant 2", 22253 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
-
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 1", 12227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 2", 22227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 3", 32227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 4", 42227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 5", 52227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 6", 62227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 7", 72227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 8", 82227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 9", 92227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
-
- #Tall Tall Heights
- LocationData(UPPER_ICE_LAKES_AP_REGION, "Triton Shrine Shop - Attendant 1", 11165 + shop_index_offset),
- LocationData(UPPER_ICE_LAKES_AP_REGION, "Triton Shrine Shop - Attendant 2", 21165 + shop_index_offset),
-
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 1", 12746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 2", 22746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 3", 32746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 4", 42746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 5", 52746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 6", 62746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 7", 72746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 8", 82746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 9", 92746 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 10", 102746 + shop_index_offset),
-
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 1", 10540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 2", 20540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 3", 30540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 4", 40540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 5", 50540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 6", 60540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 7", 70540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 8", 80540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 9", 90540 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 10", 100540 + shop_index_offset),
-
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 1", 12918 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 2", 22918 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 3", 32918 + shop_index_offset),
- LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 4", 42918 + shop_index_offset),
-
- #Jidamba Tangle
- LocationData(EUROPA_SHRINE_AP_REGION, EUROPA_SHRINE_AP_REGION + " Shop - Attendant 1", 11163 + shop_index_offset),
- LocationData(EUROPA_SHRINE_AP_REGION, EUROPA_SHRINE_AP_REGION + " Shop - Attendant 2", 21163 + shop_index_offset),
-
- #Neptune Shrine
- LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Shop - Attendant 1", 13164 + shop_index_offset),
- LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Shop - Attendant 2", 23164 + shop_index_offset),
- LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Shop - Attendant 3", 33164 + shop_index_offset),
-
- #Zones (End-Game)
- #The New World
- LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Shrine Shop - Attendant 1", 11877 + shop_index_offset),
- LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Shrine Shop - Attendant 2", 21877 + shop_index_offset),
- ]
- return location_table
-
-def get_region_completion_locations(player: int, options: CrystalProjectOptions) -> List[LocationData]:
- logic = CrystalProjectLogic(player, options)
- location_table: List[LocationData] = [
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion", 6001 + regionsanity_index_offset, regionsanity=True),
- LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Region Completion", 6002 + regionsanity_index_offset, regionsanity=True),
- LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Region Completion", 6007 + regionsanity_index_offset, regionsanity=True),
- LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Region Completion", 6003 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Region Completion", 6004 + regionsanity_index_offset, regionsanity=True),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Region Completion", 6005 + regionsanity_index_offset, regionsanity=True),
- LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Region Completion", 6006 + regionsanity_index_offset, regionsanity=True),
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Region Completion", 6008 + regionsanity_index_offset, regionsanity=True),
- LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Region Completion", 6009 + regionsanity_index_offset, regionsanity=True),
- LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Region Completion", 6010 + regionsanity_index_offset, regionsanity=True),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Region Completion", 6011 + regionsanity_index_offset, regionsanity=True),
- LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Region Completion", 6012 + regionsanity_index_offset, regionsanity=True),
- LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " Region Completion", 6013 + regionsanity_index_offset, regionsanity=True),
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Region Completion", 6014 + regionsanity_index_offset, regionsanity=True),
- LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Region Completion", 6015 + regionsanity_index_offset, regionsanity=True),
- LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Region Completion", 6016 + regionsanity_index_offset, regionsanity=True),
- LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Region Completion", 6017 + regionsanity_index_offset, regionsanity=True),
- LocationData(PIPELINE_SOUTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " Region Completion", 6018 + regionsanity_index_offset, regionsanity=True),
- LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Region Completion", 6019 + regionsanity_index_offset, regionsanity=True),
- LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Region Completion", 6020 + regionsanity_index_offset, regionsanity=True),
- LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Region Completion", 6021 + regionsanity_index_offset, regionsanity=True),
- LocationData(SALMON_PASS_EAST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Region Completion", 6022 + regionsanity_index_offset, regionsanity=True),
- LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Region Completion", 6023 + regionsanity_index_offset, regionsanity=True),
- LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Region Completion", 6024 + regionsanity_index_offset, regionsanity=True),
- LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Region Completion", 6025 + regionsanity_index_offset, regionsanity=True),
- LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Region Completion", 6026 + regionsanity_index_offset, regionsanity=True),
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Region Completion", 6027 + regionsanity_index_offset, regionsanity=True),
- LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Region Completion", 6028 + regionsanity_index_offset, regionsanity=True),
- LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Region Completion", 6030 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_OPEN_SEA_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " Region Completion", 6031 + regionsanity_index_offset, regionsanity=True),
- LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Region Completion", 6032 + regionsanity_index_offset, regionsanity=True),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Region Completion", 6033 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Region Completion", 6034 + regionsanity_index_offset, regionsanity=True),
- LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Region Completion", 6035 + regionsanity_index_offset, regionsanity=True),
- LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Region Completion", 6036 + regionsanity_index_offset, regionsanity=True),
- LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Region Completion", 6037 + regionsanity_index_offset, regionsanity=True),
- LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Region Completion", 6038 + regionsanity_index_offset, regionsanity=True),
- LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Region Completion", 6039 + regionsanity_index_offset, regionsanity=True),
- LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Region Completion", 6040 + regionsanity_index_offset, regionsanity=True),
- LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Region Completion", 6041 + regionsanity_index_offset, regionsanity=True),
- LocationData(EASTERN_CHASM_AP_REGION, EASTERN_CHASM_DISPLAY_NAME + " Region Completion", 6042 + regionsanity_index_offset, regionsanity=True),
- LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Region Completion", 6043 + regionsanity_index_offset, regionsanity=True),
- LocationData(UPPER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Region Completion", 6044 + regionsanity_index_offset, regionsanity=True),
- LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Region Completion", 6045 + regionsanity_index_offset, regionsanity=True),
- LocationData(SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Region Completion", 6046 + regionsanity_index_offset, regionsanity=True),
- LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Region Completion", 6047 + regionsanity_index_offset, regionsanity=True),
- LocationData(NORTHERN_STRETCH_RACE_START_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " Region Completion", 6048 + regionsanity_index_offset, regionsanity=True),
- LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Region Completion", 6049 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Region Completion", 6050 + regionsanity_index_offset, regionsanity=True),
- LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " Region Completion", 6051 + regionsanity_index_offset, regionsanity=True),
- LocationData(EUROPA_SHRINE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Region Completion", 6052 + regionsanity_index_offset, regionsanity=True),
- LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Region Completion", 6053 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Region Completion", 6054 + regionsanity_index_offset, regionsanity=True),
- LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Region Completion", 6055 + regionsanity_index_offset, regionsanity=True),
- LocationData(JADE_CAVERN_AP_REGION, JADE_CAVERN_DISPLAY_NAME + " Region Completion", 6056 + regionsanity_index_offset, regionsanity=True),
- LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Region Completion", 6057 + regionsanity_index_offset, regionsanity=True),
- LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Region Completion", 6058 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Region Completion", 6059 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Region Completion", 6060 + regionsanity_index_offset, regionsanity=True),
- LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Region Completion", 6061 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Region Completion", 6063 + regionsanity_index_offset, regionsanity=True),
- LocationData(THE_OLD_WORLD_AP_REGION, THE_OLD_WORLD_DISPLAY_NAME + " Region Completion", 6064 + regionsanity_index_offset, regionsanity=True),
- ]
- return location_table
-
-def get_location_names_per_category() -> Dict[str, Set[str]]:
- categories: Dict[str, Set[str]] = {}
-
- for location in get_crystal_locations(-1, None):
- categories.setdefault("Crystals", set()).add(location.name)
-
- for location in get_shop_locations(-1, None):
- categories.setdefault("Shops", set()).add(location.name)
-
- for location in get_boss_locations(-1, None):
- categories.setdefault("Bosses", set()).add(location.name)
-
- for location in get_region_completion_locations(-1, None):
- categories.setdefault("Region Completions", set()).add(location.name)
-
+from typing import List, Optional, Callable, NamedTuple, Dict, Set
+from BaseClasses import CollectionState
+from .options import CrystalProjectOptions
+from .rules import CrystalProjectLogic
+from .constants.jobs import *
+from .constants.keys import *
+from .constants.key_items import *
+from .constants.ap_regions import *
+from .constants.region_passes import *
+from .constants.display_regions import *
+from .constants.teleport_stones import *
+from .constants.crystal_locations import *
+from .constants.level_requirements import *
+
+class LocationData(NamedTuple):
+ ap_region: str
+ name: str
+ code: int
+ rule: Optional[Callable[[CollectionState], bool]] = None
+ regionsanity: bool = False
+
+treasure_index_offset = 1
+npc_index_offset = 10000
+crystal_index_offset = 100000
+boss_index_offset = 1000000
+shop_index_offset = 10000000
+regionsanity_index_offset = 100000000
+
+def get_location_name_to_id() -> dict[str, int]:
+ location_name_to_id = {location.name: location.code for location in get_treasure_and_npc_locations(-1, None)}
+ crystal_name_to_id = {crystal.name: crystal.code for crystal in get_crystal_locations(-1, None)}
+ boss_name_to_id = {boss.name: boss.code for boss in get_boss_locations(-1, None)}
+ shop_name_to_id = {shop.name: shop.code for shop in get_shop_locations(-1, None)}
+ region_completion_name_to_id = {region_completion.name: region_completion.code for region_completion in get_region_completion_locations(-1, None)}
+ location_name_to_id.update(crystal_name_to_id)
+ location_name_to_id.update(boss_name_to_id)
+ location_name_to_id.update(shop_name_to_id)
+ location_name_to_id.update(region_completion_name_to_id)
+
+ return location_name_to_id
+
+def get_treasure_and_npc_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
+ logic = CrystalProjectLogic(player, options)
+
+ location_table: List[LocationData] = [
+ #Zones (Beginner)
+ #Spawning Meadows
+ #Treasure chests
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - On cliff north of spawn", 101 + treasure_index_offset), #Money chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Under overpass", 292 + treasure_index_offset), #Money chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Jump on Nan", 41 + treasure_index_offset), #Burglars Glove chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Above waterfall", 17 + treasure_index_offset), #Cedar Staff chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Behind Nan house", 61 + treasure_index_offset), #Cedar Wand chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Promontory south of waterfall", 54 + treasure_index_offset), #Cleaver chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Jump on secret tunnel chest", 5 + treasure_index_offset), #Fenix Juice chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - On path to Delende", 49 + treasure_index_offset), #Fenix Juice chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Secret tunnel", 47 + treasure_index_offset), #Stabbers chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - On ledge jump from tree", 50 + treasure_index_offset), #Stout Shield chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Cross trees and jump down", 38 + treasure_index_offset), #Tincture chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - West of spawn", 1 + treasure_index_offset), #Tonic chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - In cave NW of spawn", 2 + treasure_index_offset), #Tonic chest
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Chest - Mountain summit jump on Nan", 1142 + treasure_index_offset), #Tonic Pouch chest
+
+ #NPCs
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Pouch Nan", 53 + npc_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Secret Herb near Shaku", 627 + npc_index_offset), #Secret Herb 0 Fixed Missable
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Promontory south of waterfall Secret Herb", 297 + npc_index_offset), #(48, 112, -36) Secret Herb 1 Fixed Missable
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Secret Herb past mountain summit chest", 545 + npc_index_offset), #(79, 112, -30) Secret Herb 2 Fixed Missable
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Cross trees and jump down for Secret Herb", 546 + npc_index_offset), #(43, 104, -8) Secret Herb 3 Fixed Missable
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Map Nan", 84 + npc_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Nan Stew", 14 + npc_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo", 194 + npc_index_offset, lambda state: state.has(BLACK_SQUIRREL, player, 3)), #Tree Fairy NPC seems to have the dialogue for this (ID 194)
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on tree SW of spawn", 264 + npc_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on tree NW of spawn", 296 + npc_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on tree near lampposts", 110 + npc_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Buttersquirrel on Mario jump tree", 3085 + npc_index_offset),
+
+ #Delende
+ #Treasure chests
+ #Delende Plains
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Fallen log parkour", 208 + treasure_index_offset), # Earring chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - On west mountainside", 209 + treasure_index_offset), # Iron Sword chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Off north path", 259 + treasure_index_offset), # Tonic chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Fish hatchery approach", 79 + treasure_index_offset), # Tincture chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In front of fish hatchery lower level", 210 + treasure_index_offset), # Money chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Up near hatchery", 169 + treasure_index_offset), # Protect Amulet chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In front of fish hatchery below tree", 2997 + treasure_index_offset), # Tonic Pouch chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In fish hatchery", 39 + treasure_index_offset), # Tincture chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Across river from fish hatchery", 123 + treasure_index_offset), # Looters Ring chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Return from fish hatchery", 34 + treasure_index_offset), # Bracer chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Under tree", 261 + treasure_index_offset), # Tincture chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Mushroom underpass", 262 + treasure_index_offset), # Cotton Hood chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - In front of camp", 263 + treasure_index_offset), # Money chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Under ambush tree", 212 + treasure_index_offset), # Fenix Juice chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Outside spooky cave", 27 + treasure_index_offset), # Storm Hood chest
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Across river", 213 + treasure_index_offset), # Earring chest
+ #Eastern Delende (High Bridges)
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Hollow riverbank", 43 + treasure_index_offset), # Underground Ether chest
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Before Proving Meadows", 216 + treasure_index_offset), # Tonic Pouch chest
+ #Delende High Bridges
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - High up west mountainside", 33 + treasure_index_offset), #Mages Robe chest
+ # Gran's House (Delende High Bridges)
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 1", 87 + treasure_index_offset), # (126, 128, -58) style: blank
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 2", 100 + treasure_index_offset), # (127, 128, -58) style: weapon
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 3", 177 + treasure_index_offset), # (137, 128, -57) style: consumable
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Gran's House 4", 178 + treasure_index_offset), # (137, 128, -56) style: consumable
+ # Basement (Delende High Bridges)
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Basement Chest - Gran...?", 179 + treasure_index_offset), # Empty chest
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, "Basement Chest - Gran......?", 180 + treasure_index_offset), # Digested Head chest
+ # Below Gran
+ LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Cracks in Gran's foundation", 3653 + treasure_index_offset), # (126, 115, -102) Basement map chest
+ LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Gran's subbasement pair 1", 181 + treasure_index_offset), # (129, 98, -111) Fenix Juice Pouch chest
+ LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Gran's subbasement pair 2", 182 + treasure_index_offset), # (128, 98, -111) Plate of Wolf chest
+ LocationData(BELOW_GRAN_AP_REGION, "Underpass Chest - Gran's subbasement loner", 3671 + treasure_index_offset), # (119, 98, -110) Underpass Scrap
+ #Mesa Above Spooky Cave
+ LocationData(DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Top of spooky cave", 73 + treasure_index_offset), #Tincture Pouch chest
+ #Delende Peak
+ LocationData(DELENDE_PEAK_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Troll", 451 + treasure_index_offset), #Tincture Pouch chest
+ #Heart Tarn
+ LocationData(HEART_TARN_AP_REGION, DELENDE_DISPLAY_NAME + " Chest - Looking for love in all the high places (glide to the heart tarn)", 1554 + treasure_index_offset, lambda state: logic.has_swimming(state)), # Chartreuse chest
+
+ #NPCs
+ #NPCs Shortcuts: shortcut girl (Z2_Collector Sister ID 3769 (169, 132, -89))
+ #NPCs Shortcuts: Rabbit Claws shortcut guy (Z2_RoosterFeetGuy ID 74(281, 128, -159))
+ #Delende Plains
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Astley gives you a home point stone", 28 + npc_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Not-at-all shady guy", 124 + npc_index_offset), # (181, 132, -200); Rotten Salmon
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Fish at the hatchery", 121 + npc_index_offset, lambda state: state.has("Item - Flimsy Rod", player) and state.has("Item - Plug Lure", player)), # Fisher (Z2_FisherOnDock ID 121 (166, 133, -208))
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dizzy noob chucks something at your face", 831 + npc_index_offset), # (276, 116, -204); Fervor Charm
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dog Bone Guy", 31 + npc_index_offset, lambda state: state.has(DOG_BONE, player, 3)),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dog Bone south of Soiled Den", 184 + npc_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Dog Bone in spooky cave", 1915 + npc_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Cartographer", 1153 + npc_index_offset), # guy who gives you a map of Delende if you don't have one (Z2_MapMan (198, 131, -74)) Fixed Missable
+
+ #Soiled Den
+ #Treasure chests
+ #Soiled Denlende
+ LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - Riverside", 1155 + treasure_index_offset), # (249, 116, -156) Tonic Pouch chest
+ #The Bangler
+ LocationData(THE_BANGLER_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - Lurking in the shadows by the Bangler", 218 + treasure_index_offset), #(311, 111, -96) Clamshell chest
+ LocationData(THE_BANGLER_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - By the Bangler", 271 + treasure_index_offset), #(322, 111, -101) Clamshell chest
+ LocationData(THE_BANGLER_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Chest - Long river jump", 448 + treasure_index_offset), #(326, 111, -116) Dodge Charm chest
+
+ #NPCs
+ #Soiled Denlende
+ LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " NPC - Dog Bone among the bones and flowers", 176 + npc_index_offset), #(296, 112, -155)
+
+ #Pale Grotto
+ #Treasure chests
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Across from fish island", 228 + treasure_index_offset), #(#316, 120, -262) Fenix Juice chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - North from save point", 144 + treasure_index_offset), #(307, 124, -345) Poisonkiss chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Entrance river hop", 229 + treasure_index_offset), #Tonic chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - On promontory", 2979 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Island 1", 3622 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_glide(state)), #Underpass Scrap chest; somehow this is actually in the pale grotto and not the underpass
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Island 2", 3077 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_glide(state)), #Z-Potion Pouch chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Tucked behind path to temple", 267 + treasure_index_offset), #Tincture chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Jumping puzzle", 226 + treasure_index_offset), #Storm Helm chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - South of temple", 136 + treasure_index_offset), #Money chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Temple antechamber", 222 + treasure_index_offset), #Toothpick chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Chest - Temple sanctuary", 1154 + treasure_index_offset), #Pale Grotto map chest
+ LocationData(THE_PALE_GROTTO_AP_REGION, "Underpass Chest - Blue flower ledge between Pale Grotto & Soiled Den", 3621 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(245, 116, -199) Underpass Scrap chest
+
+ #NPCs
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " NPC - Reid gives you gently worn armor", 1166 + npc_index_offset), #Pale Grotto Temple map (Z2_ReidCamp (273, 122, -327)) gives you Ring Mail
+
+ #Seaside Cliffs
+ #Treasure chests
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - North across river from double giant box", 282 + treasure_index_offset), #Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Stonehenge", 150 + treasure_index_offset), #Bracer chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - ClamHaters Mulan jumping puzzle", 268 + treasure_index_offset), #Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - After ClamHater made a man out of you", 2981 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - South of ClamHater", 281 + treasure_index_offset), #Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - South of chest south of ClamHater", 286 + treasure_index_offset), #Tonic chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Climbing the seaside cliffs", 42 + treasure_index_offset), #Potion chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Three Amigos Uno", 1161 + treasure_index_offset), #Tonic chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Three Amigos Dos", 447 + treasure_index_offset), #Scope Bit chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Three Amigos Tres", 270 + treasure_index_offset), #Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Beneath encampment ledge", 217 + treasure_index_offset), #(310, 116, -68) Money chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - On jigsaw mountain 1", 449 + treasure_index_offset), #(213, 107, 27) Money chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - In cliffs nook south of encampment", 80 + treasure_index_offset), #(307, 113, -22) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Below west Delende entrance", 273 + treasure_index_offset), #(275,108,-28) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Above the eastern beach standing stones", 274 + treasure_index_offset), #(312, 95, 12) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Below jigsaw mountain", 275 + treasure_index_offset), #(223, 94, 26) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Island by the waterfalls", 277 + treasure_index_offset), #(259, 107, -18) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - East of the river above the beach", 278 + treasure_index_offset), #(281, 98, -3) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Along the eastern beach up the cliffs", 279 + treasure_index_offset), #(302, 101, 4) Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Parkour by the island waterfalls", 289 + treasure_index_offset), #(250, 104, -13) Fenix Juice chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - On jigsaw mountain 2", 157 + treasure_index_offset), #(218, 107, 23) Headgear chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - South of encampment on the canyon mountainside", 272 + treasure_index_offset), #(289, 110, -18) Jewel of Defense chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Downstream of the island waterfalls", 288 + treasure_index_offset), #(250, 98, -4) Tincture chest
+ #Seaside Cliffs Beach
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - The little mermaid", 276 + treasure_index_offset), #Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Hop along the sea stacks south of the peninsula", 280 + treasure_index_offset), #Clamshell chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Peninsula past the standing stones jump puzzle", 205 + treasure_index_offset), #Storm Cap chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - MR SNIPS", 287 + treasure_index_offset), #Fenix Juice chest
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Atop sea stack east of the bay", 450 + treasure_index_offset), #Swimmers Top chest
+ #Seaside Cliffs Valley
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Chest - Rocky cove down the lazy river", 269 + treasure_index_offset), #Clamshell chest
+
+ #NPCs
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - ClamHater above the mist", 283 + npc_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - If you give a Manana Man a clam... (he will ask you for more)", 284 + npc_index_offset, lambda state: logic.has_enough_clamshells(state)),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - Diamond below the bay", 2896 + npc_index_offset, lambda state: logic.has_swimming(state)), #(343, 81, 0) Ore
+ #Todo NPCs Job Masters: Seaside Cliffs Outpost map has Master Shaman ID 3572 (387, 155, -104); gives you Shaman Seal in exchange for job mastery
+
+ #Draft Shaft Conduit
+ #Treasure chests
+ LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Chest - Straight shot", 82 + treasure_index_offset), #Torch chest
+ LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Chest - Ring around the rosy", 81 + treasure_index_offset), #Tonic Pouch chest
+
+ #Mercury Shrine
+ #Treasure chests
+ LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Chest - Pinnacle", 155 + treasure_index_offset, lambda state: state.has(MERCURY_STONE, player) or logic.has_vertical_movement(state)), #Contract chest
+
+ #Yamagawa M.A.
+ #Treasure chests
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Up first cliff", 2995 + treasure_index_offset), #Money chest
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Sneaky behind tree", 91 + treasure_index_offset), #Broadsword chest
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Tucked next to waterfall", 95 + treasure_index_offset), #Iron Guard chest
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Dead end", 3056 + treasure_index_offset), #Tonic chest
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Hidden stairway", 757 + treasure_index_offset), #Tonic Pouch chest
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Chest - Drop down to mountain balcony", 290 + treasure_index_offset), #Torpid Cuffs chest
+ #Fencer's Keep Chest
+ LocationData(FENCERS_KEEP_CHEST_AP_REGION, "Overpass Chest - Hop over from Yamagawa to dead tree by Fencers Keep", 3537 + treasure_index_offset), #(148, 151, -114) 6th Overpass Scrap on Overpass main map
+
+ #NPCs
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " NPC - Hidden inside waterfall source", 628 + npc_index_offset, lambda state: logic.has_swimming(state)), #Autumns Oath
+ #Todo NPCs Job Masters: Yamagawa M.A. Temple map has Master Scholar ID 3574 (59, 151, -98); gives you Scholar Seal in exchange for job mastery
+
+ #Proving Meadows
+ #Treasure chests
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Next to trial guard", 207 + treasure_index_offset), #Money chest
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Hop on shops to sneak behind waterfall", 258 + treasure_index_offset), #Battle Scythe chest
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Hidden behind the inn", 118 + treasure_index_offset), #Burglars Glove chest
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Tucked into waterfall nook", 2980 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - Tarzan", 256 + treasure_index_offset), #Tonic chest
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Chest - On the climb up outside Skumparadise", 193 + treasure_index_offset), #Tonic Pouch chest
+
+ #NPCs
+ #NPCs Blocker: this guy checks whether you have enough crystals to pass; this is a blocker guy not a location check guy
+ #LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " NPC - Crystal Checker", 128 + npc_index_offset),
+
+ #Skumparadise (we're smushing Trial Caves into there)
+ #Treasure chests
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Stairs are lava", 126 + treasure_index_offset), #Stalwart Shield chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Shroom dodging", 120 + treasure_index_offset), #Help the Prince chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Ride the shroom", 670 + treasure_index_offset), #Awake Ring chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Wall niche", 671 + treasure_index_offset), #Awake Ring chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Smaller wall niche", 669 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Lava-loving shrooms", 684 + treasure_index_offset), #Tonic Pouch chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Behind the lava shroom colonnade", 685 + treasure_index_offset), #Mana Ring chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - There and back again", 683 + treasure_index_offset), #Sharp Sword chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Accompanied by yellow flower in tunnel", 1110 + treasure_index_offset), #Fenix Juice chest
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Chest - Behind boss", 332 + treasure_index_offset), #Money chest
+
+ #Zones (Advanced)
+ #Capital Sequoia (smushed Capital Courtyard in)
+ #Treasure chests
+ #Moat Shallows
+ LocationData(MOAT_SHALLOWS_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Beyond the courtyard wall", 2671 + treasure_index_offset), #Tonic Pouch chest
+ #Capital Sequoia
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Inn room", 1388 + treasure_index_offset), #Craftwork Staff chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Second story by Master Rogue", 158 + treasure_index_offset), #Craftwork Dagger chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Magic store attic", 1389 + treasure_index_offset), #Craftwork Scythe chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Training ground parkour", 1390 + treasure_index_offset), #Craftwork Katana chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Behind Luxury store", 2651 + treasure_index_offset), #Craftwork Cap chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Locked in Luxury Store storage 1", 1533 + treasure_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #Fenix Syrup Pouch chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Locked in Luxury Store storage 2", 1532 + treasure_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #Lucky Briefs chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Locked in Luxury Store storage 3", 1531 + treasure_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #Lucky Socks chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Inn attic by Master Monk", 2656 + treasure_index_offset), #Craftwork Vest chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - By Master Warrior atop the Luxury Store", 2655 + treasure_index_offset), #Craftwork Shield chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Atop library bookcases", 1392 + treasure_index_offset), #Craftwork Sword chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Penguin sanctuary", 2654 + treasure_index_offset), #Craftwork Robe chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 1", 137 + treasure_index_offset), #Gaea Shard chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 2", 227 + treasure_index_offset), #Gaea Shard chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 3", 381 + treasure_index_offset), #Gaea Shard chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gaea Shrine 4", 548 + treasure_index_offset), #Gaea Shard chest
+ #Next check can be acquired with either Owl, Ibek, Quintar, or Gaea Stone; vanilla expects Gaea Stone so that's the logic were using
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Cleric's Lounge", 1391 + treasure_index_offset, lambda state: state.has(GAEA_STONE, player) or (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Craftwork Bow chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Instrducktor classroom", 1387 + treasure_index_offset), #Craftwork Axe chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Master Warlock's chambers atop Weapons R Us", 2732 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Watering Can chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Master Wizard's Library atop Weapons R Us", 168 + treasure_index_offset), #Craftwork Pages chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Fenced off in Armor Merchant alley", 2653 + treasure_index_offset), #Craftwork Helm chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Beneath grand staircase", 1393 + treasure_index_offset), #Craftwork Rapier chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Tucked into maze entrance hedge", 389 + treasure_index_offset), #Fang Pendant chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Down left maze path", 452 + treasure_index_offset), #Craftwork Wand chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Hop moat to maze", 863 + treasure_index_offset), #Craftwork Spear chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Accompanied by blue flower pair in maze", 390 + treasure_index_offset), #Craftwork Crown chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Below maze-cheating Lost Penguin", 388 + treasure_index_offset), #Gardeners Key chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Above maze fountain", 387 + treasure_index_offset), #Givers Ring chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gardener's Shed 1", 2652 + treasure_index_offset, lambda state: logic.has_key(state, GARDENERS_KEY)), #Craftwork Mail chest
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gardener's Shed 2", 2663 + treasure_index_offset, lambda state: logic.has_key(state, GARDENERS_KEY)), #Tuber Seed
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Chest - Gardener's Shed 3", 2664 + treasure_index_offset, lambda state: logic.has_key(state, GARDENERS_KEY)), #Tuber Seed
+
+ #NPCs
+ #Todo NPCs Job Masters: Master Beatsmith ID 3560 (361, 170, -268); gives you Beatsmith Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Cleric ID 3568 (363, 166, -266); gives you Cleric Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Monk ID 3567 (394, 179, -295); gives you Monk Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Rogue ID 3571 (444, 167, -264); gives you Rogue Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Warlock ID 3570 (400, 171, -267); gives you Warlock Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Warrior ID 3566 (424, 182, -293); gives you Warrior Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Wizard ID 3569 (391, 168, -266); gives you Wizard Seal in exchange for job mastery
+ #NPCs Blocker: Z14_ProgressionGate ID 3823 (403, 180, -367) requires 18 crystals; we think it"s an original-randomizer-only NPC blocking the way to the castle
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Home Point Stone duck", 560 + npc_index_offset), #Home Point Stone (403, 161, -265) Fixed Missable
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Impress the Luxury Equipment Shop Bouncer with 6 jobs collected", 51162 + npc_index_offset, lambda state: logic.has_jobs(state, 6)), #(419, 171, -289) Blocker-No-Longer, Fixed Missable, and Multichecks
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Impress the Luxury Equipment Shop Bouncer further with 11 jobs collected", 1162 + npc_index_offset, lambda state: logic.has_jobs(state, 11)), #(419, 171, -289)
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Plunder the Luxury storage and skip town to meet a very slow thief", 1529 + npc_index_offset, lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player)), #(417, 171, -299) Fixed Missable
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Courtyard Chloe", 1661 + npc_index_offset, lambda state: (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Fly Lure (399, 155, -219) Fixed Missable
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Glinting Courtyard Key", 2486 + npc_index_offset), #Courtyard Key sparkle that appears if you miss Courtyard Reid in Salmon River (424, 150, -222) Fixed Missable
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Sparkling in the fountain", 2584 + npc_index_offset), #Plug Lure
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin on a tent", 605 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Speedy Lost Penguin on patrol", 584 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin kiosk keeper", 508 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin skulking in store alley", 565 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin on gender change bench porch", 1095 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin enjoying inn hospitality", 946 + npc_index_offset),
+ # Progressive Location: 4 checks on the Penguin Keeper, must add a progressive location in the C# app every time you use one of these.
+ # The original check that corresponds to the npc id should be last so that when it completes it stops showing up on your minimap.
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring 3 Lost Penguins to Penguin Keeper", 50531 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 3)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring 6 Lost Penguins to Penguin Keeper", 50532 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 6)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring 9 Lost Penguins to Penguin Keeper", 50533 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 9)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Bring all 12 Lost Penguins to Penguin Keeper", 531 + npc_index_offset, lambda state: state.has(LOST_PENGUIN, player, 12)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin trampling Cleric's flowers", 564 + npc_index_offset, lambda state: state.has(GAEA_STONE, player) or (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Sadist Sam gives you pain, you give Sadist Sam head(s)", 536 + npc_index_offset, lambda state: state.has(DIGESTED_HEAD, player, 3)), #name is ca69011a in Crystal Edit why lmao
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin wandering Magic Store rooftop garden", 573 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin atop sewer exit rooftop", 567 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Lost Penguin cheating at Garden Maze", 421 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - How did you climb that tree, Lost Penguin?", 422 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Library roof Lost Penguin", 594 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Library Morii of the East!", 1948 + npc_index_offset), #(440, 171, -296) Z14_Library Scholar
+
+ #Jojo Sewers
+ #Treasure chests
+ #Sewer Main (lol)
+ LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Hiding in the guarded grass", 743 + treasure_index_offset), #Tonic Pouch chest
+ LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Eastside sewer green room", 2658 + treasure_index_offset), #Iron Helm chest
+ LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Invisible maze", 744 + treasure_index_offset), #Iron Armor chest
+ LocationData(JOJO_SEWERS_AP_REGION, "Underpass Chest - Walking the plank above Pale Grotto waterfall", 3670 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(337, 155, -319) Underpass Scrap chest
+ #Past Secret Password
+ LocationData(SEWERS_TO_BOOMER_SOCIETY_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Drowned passage to Boomer Society", 634 + treasure_index_offset), # Money chest
+ LocationData(SEWERS_TO_BOOMER_SOCIETY_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - In the shadow of the waterfall", 1126 + treasure_index_offset), # Tincture Pouch
+ LocationData(SEWERS_TO_BOOMER_SOCIETY_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Chest - Leap of faith", 887 + treasure_index_offset), # Smelly Gi chest
+
+ #NPCs
+ #Sewer Main (lol)
+ LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " NPC - Who even wants Stone of Jordan these days?", 2759 + npc_index_offset, lambda state: state.has(CRAG_DEMON_HORN, player)),
+
+ #Boomer Society
+ #Treasure chests
+ LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " Chest - Log cabin", 2667 + treasure_index_offset), #Gospel chest
+ LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " Chest - 2nd floor of log cabin", 2909 + treasure_index_offset), #Boomer Society map chest
+
+ #NPCs
+ LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " NPC - Nice Allowance Lady", 476 + npc_index_offset),
+ LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " NPC - Treasury Grandpa", 547 + npc_index_offset),
+
+ #Rolling Quintar Fields
+ #Treasure chests
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - South of east gate", 826 + treasure_index_offset), #Potion chest
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Chevy divot south of east gate", 828 + treasure_index_offset), #Fenix Juice chest
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Deep in Quintar cave", 817 + treasure_index_offset), #Hunting Axe chest
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Sneaky chest behind tree", 829 + treasure_index_offset), #Potion chest
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Deep in eastern Quintar cave", 745 + treasure_index_offset), #Hunting Bow chest
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - At the end of the road", 825 + treasure_index_offset), #Money chest
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Hidden beneath end of the road", 2674 + treasure_index_offset), #Tonic Pouch chest
+ #Rolling Treetop Highway
+ LocationData(ROLLING_TREETOP_HIGHWAY_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - West of and above sneaky chest", 338 + treasure_index_offset), #Money chest
+ #Quintar Sanctum Entrance
+ LocationData(SANCTUM_ENTRANCE_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Pinnacle by short and tall box friends", 471 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(SANCTUM_ENTRANCE_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Chest - Treetop west of Quintar Sanctum", 365 + treasure_index_offset), #Spore Blocker chest
+ #Hunter's Tower
+ LocationData(HUNTERS_TOWER_AP_REGION, "Overpass Chest - Climb the mountain west of Quintar Sanctum entrance", 3532 + treasure_index_offset), #1st Overpass Scrap chest on main Overpass map
+
+ #NPCs
+ #NPCs CheckOrNot: two Quintar Eggs (decided against)
+ #Hunter Master is in Hunter's Tower region, but you need quintar to climb the tower
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Quintar Stable Owner by Capital Sequoia's eastern gate", 375 + npc_index_offset, lambda state: logic.has_jobs(state, 7)), #Quintar Pass, Fixed Missable
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Silver beneath overhang in eastern Quintar cave crevasse", 2678 + npc_index_offset), #Dust
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Quintar Enthusiast (always pet Buttermint)", 464 + npc_index_offset), #Fixed Missable
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Silver in Quintar cave beneath the end of the road", 454 + npc_index_offset), #Ingot
+ #Rolling Treetop Highway
+ LocationData(ROLLING_TREETOP_HIGHWAY_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Silver behind Quintar Nest befriending a stack of boxes", 323 + npc_index_offset), #Ore
+
+ #Quintar Nest
+ #Treasure chests
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - West Donut Lake sprinkle", 883 + treasure_index_offset), #Money chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - East Donut Lake sprinkle", 884 + treasure_index_offset), #Ether chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Jumping puzzle above the donut", 756 + treasure_index_offset), #Fenix Juice chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Northwest Donut Lake sprinkle", 432 + treasure_index_offset), #Potion chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Welcome", 3078 + treasure_index_offset), #Potion chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Mighty jump along east side wall", 746 + treasure_index_offset), #Scope Bit chest
+ #You can't detour through the sewers if you can't reach the sewers (relevant for regionsanity)
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Detour through the sewers", 638 + treasure_index_offset, lambda state: state.can_reach(JOJO_SEWERS_AP_REGION, player=player) or logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state)), #Static Rod chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - North Donut Lake sprinkle", 852 + treasure_index_offset), #Tincture chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Hop along west side wall", 2982 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Chest - Donut Lake crown sprinkle", 851 + treasure_index_offset), #Tonic chest
+ LocationData(QUINTAR_NEST_AP_REGION, "Underpass Chest - Up north Quintar Nest waterfall", 3620 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(524, 146, -368) Underpass Scrap chest
+
+ #NPCs
+ #Todo NPCs CheckOrNot: two Quintar Eggs here
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " NPC - Eastside Silver come on down to the water", 711 + npc_index_offset), #Dust
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " NPC - South of sewers Silver", 850 + npc_index_offset), #Ingot
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " NPC - Silver on the way out", 755 + npc_index_offset), #Ore
+
+ #Quintar Sanctum
+ #Treasure chests
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - West wall big bounce", 810 + treasure_index_offset), #Money chest
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - Bounce field", 969 + treasure_index_offset), #Fenix Juice chest
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - In front of the shrine", 2910 + treasure_index_offset), #Quintar Sanctum map chest
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - West at ground level", 2983 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Chest - North at ground level", 593 + treasure_index_offset), #Tonic Pouch chest
+ #Sanctum Exit Clifftop
+ LocationData(SANCTUM_EXIT_CLIFFTOP_AP_REGION, "Overpass Chest - Lonely chest above Quintar Sanctum", 3533 + treasure_index_offset), #2nd Overpass Scrap chest on main map
+
+ #NPCs
+ #Todo NPCs CheckOrNot: Quintar Egg here (on Quintar Sanctum Mushroom map)
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Silver going back down", 802 + npc_index_offset), #Dust
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Silver almost to the top", 965 + npc_index_offset), #Dust
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Mushroom bounce Silver", 411 + npc_index_offset), #Ingot
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Silver beneath the shroom", 801 + npc_index_offset), #Ingot
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - East side Silver (Do not look down)", 737 + npc_index_offset), #Ore
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Big bounce Silver", 754 + npc_index_offset), #Ore
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Two Toads bestow Princess Toadstool", 963 + npc_index_offset),
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " NPC - Two Toads crown Bowsette", 964 + npc_index_offset),
+
+ #Capital Jail
+ #Treasure chests
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Touchdown", 640 + treasure_index_offset), # South Wing Key chest
+ #South Wing
+ LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - South Wing jail cell across from busted wall", 930 + treasure_index_offset), # West Wing Key chest
+ LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Haunted jail cell in South Wing dead end", 931 + treasure_index_offset), # East Wing Key chest
+ #South Wing Rubble
+ LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Fiercely guarded and locked in South Wing rubble 1", 990 + treasure_index_offset), # Cell Key chest
+ LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Fiercely guarded and locked in South Wing rubble 2", 2668 + treasure_index_offset), # Iron Rod chest
+ LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked in South Wing rubble", 991 + treasure_index_offset), # Battleplate chest
+ LocationData(JAIL_SOUTH_WING_RUBBLE_AP_REGION, "Underpass Chest - Drop down behind Capital Jail South Wing rubble", 3675 + treasure_index_offset), # 7th Underpass Scrap on main map
+ #West Wing
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - West Wing jail cell among the glowy plants", 925 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY)), #Cell Key chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - West Wing arrow plants", 923 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY)), #Battle Helm chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked among the foliage in West Wing", 916 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, CELL_KEY, 4)), #Cell Key chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked beyond overgrown West Wing hallway", 909 + treasure_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), # Dark Wing Key chest
+ #East Wing
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - East Wing bedroom closet twinsies the 1st", 2999 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #empty chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - East Wing bedroom closet twinsies the 2nd", 906 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #Potion chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Waterlogged East Wing hallway twinsies the 1st", 676 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #Cell Key top chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Waterlogged East Wing hallway twinsies the 2nd", 707 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY)), #Cell Key bottom chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked in broken East Wing jail cell", 708 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Cell Key chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Locked in East Wing bedroom", 763 + treasure_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Cell Key chest
+ #Dark Wing
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Dark Wing entry left cell", 2911 + treasure_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Capital Jail map chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Sneaky chest in Dark Wing", 929 + treasure_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Woven Hood chest
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Chest - Corner lava jump in Dark Wing", 920 + treasure_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Woven Shirt chest
+
+ #NPCs
+ #South Wing
+ LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver in haunted South Wing jail cell", 972 + npc_index_offset), #Ingot
+ LocationData(JAIL_SOUTH_WING_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver in zombified South Wing jail cell", 989 + npc_index_offset), #Ingot
+ #West Wing
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver locked in overgrown West Wing hallway", 759 + npc_index_offset, lambda state: logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), # Ore
+ #East Wing
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver locked in broken East Wing jail cell accompanied by blue flower", 760 + npc_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Ore
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver locked in East Wing bedroom", 782 + npc_index_offset, lambda state: logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, CELL_KEY, 6)), #Dust
+ #Dark Wing
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " NPC - Silver in Dark Wing entry right cell", 472 + npc_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)), #Dust
+
+ #Capital Pipeline
+ #Treasure chests
+ #Pipeline South
+ LocationData(PIPELINE_SOUTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " Chest - I wanna go home", 2912 + treasure_index_offset), #Capital Pipeline map chest
+ #Pipeline North
+ LocationData(PIPELINE_NORTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " Chest - Do not anger the fungus", 1294 + treasure_index_offset), #Lucky Platter chest
+
+ #NPCs
+ #Pipeline North
+ LocationData(PIPELINE_NORTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " NPC - Silver in corrupted tunnel 1", 2660 + npc_index_offset), #Ingot
+ LocationData(PIPELINE_NORTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " NPC - Silver in corrupted tunnel 2", 1295 + npc_index_offset), #Ore
+ # Pipeline Jidamba Connector
+ LocationData(PIPELINE_JIDAMBA_CONNECTOR_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " NPC - Ride the Pipeline elevator for Jidamba diamond heist", 2897 + npc_index_offset), # Dust
+
+ #Cobblestone Crag
+ #Treasure chests
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Behind sluice gate", 479 + treasure_index_offset), #Ether Pouch chest
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Long jump", 382 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Potion chest
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Tucked in cranny between two tall spikes", 1119 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Potion Pouch chest
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - I totally meant to miss that jump", 2670 + treasure_index_offset), #Skewer chest
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Upon exiting from Quintar Nest", 478 + treasure_index_offset), #Tonic Pouch chest
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Chest - Could really use a Walking Stick (chest) right about now...", 2669 + treasure_index_offset),
+ LocationData(COBBLESTONE_CRAG_AP_REGION, "Underpass Chest - On the way to village hidden among leaves", 3669 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Underpass Scrap (Okimoto)
+
+ #NPCs
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " NPC - Westernmost Silver", 1120 + npc_index_offset), #Dust
+
+ #Okimoto N.S.
+ #Treasure chests
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Moth love lamp", 364 + treasure_index_offset), #Butterfly chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Dont bump your head", 2661 + treasure_index_offset), #Ether Pouch chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Parkour to the west", 337 + treasure_index_offset), #Float Shoes chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Just kinda in there, its not special", 356 + treasure_index_offset), #Potion chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - East of save point", 344 + treasure_index_offset), #Tanto chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - On yashiki balcony", 690 + treasure_index_offset), #Money chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Down hidden stairs in library", 686 + treasure_index_offset), #Art of War chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - East ground floor room", 2673 + treasure_index_offset), #Magic Finder chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Lurking behind bookcase", 434 + treasure_index_offset), #Potion Pouch chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Past hidden staircase", 694 + treasure_index_offset), #Tachi chest
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Chest - Dance above the koi pond", 1103 + treasure_index_offset), #Training Gi chest
+ LocationData(OKIMOTO_NS_AP_REGION, "Overpass Chest - Mountain lake north of the yashiki", 3534 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or logic.has_swimming(state)), #(605, 228, -270) 3rd Overpass Scrap in Overpass main map
+
+ #NPCs
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Silver on the way up", 359 + npc_index_offset), #Dust
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Why does a room like this exist? (Silver)", 692 + npc_index_offset), #Silver Dust
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Eastern Silver atop pond box", 689 + npc_index_offset), #Ingot
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Silver behind room that shall not be named", 691 + npc_index_offset), #Ingot
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Silver atop yashiki", 2659 + npc_index_offset), #Ore
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " NPC - Lets get down to business western Silver", 429 + npc_index_offset), #Ore
+ LocationData(OKIMOTO_NS_AP_REGION, "Overpass NPC - Swim up koi pond waterfall into cherry tree", 1583 + npc_index_offset, lambda state: logic.has_swimming(state)), #Springs Oath (632, 243, -261) Overpass main map
+
+ #Greenshire Reprise
+ #Treasure chests
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Jump off bridge 4", 483 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Ambush Knife chest
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Atop the waterfalls", 490 + treasure_index_offset), #Ether chest
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Jump off bridge 3", 482 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Looters Ring chest
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Tall taunter", 373 + treasure_index_offset), #Shell Amulet chest
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - In the valley of trees", 487 + treasure_index_offset), #Tincture Pouch chest
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Chest - Tip of peninsula south of 2nd bridge", 491 + treasure_index_offset), #Tonic Pouch chest
+
+ #NPCs
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " NPC - Jump down from 2nd bridge to Silver fallen in north crack", 485 + npc_index_offset), #Ore
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " NPC - Silver across 1st bridge hiding in a crack", 486 + npc_index_offset), #Dust
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " NPC - The furthest southern edge Silver", 474 + npc_index_offset), #Ingot
+
+ #Salmon Pass
+ #Treasure chests
+ #Salmon Pass East: 2 chests on the east side that either require the ability to get to Greenshire Reprise and cross it or hop the bridge (also you need the 5 jobs bc the rental quintar comes from Capital Sequoia)
+ LocationData(SALMON_PASS_EAST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Chest - Riverbank among yellow flowers", 2700 + treasure_index_offset), #Paypirbak chest
+ LocationData(SALMON_PASS_EAST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Chest - Admiring the hidden waterfall", 419 + treasure_index_offset), #Fenix Juice chest
+ #Salmon Pass West: 1 chest on the west side that either requires crossing the bridge or hoofing it from Salmon River
+ LocationData(SALMON_PASS_WEST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Chest - Across a bridge and around through a tunnel", 2420 + treasure_index_offset), # Fenix Juice chest
+
+ #Salmon River
+ #Treasure chests
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - Hop on chest once you have become frogger", 1264 + treasure_index_offset), #Money chest
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - Atop river island crown", 1297 + treasure_index_offset), #Bloodbind chest
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - It also wishes to be frogger", 325 + treasure_index_offset), #Money chest
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - In the stands of Salmon race finish line", 2976 + treasure_index_offset, lambda state: logic.has_rental_salmon(state)), #Ether Pouch chest
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Chest - Inside Salmon Shack", 2913 + treasure_index_offset), #Salmon River map chest
+ #Mushroom Mountain
+ LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Hop west from shrine to shroom-studded mountainside", 3539 + treasure_index_offset), #(32, 181, -373) 2nd Overpass scrap on (Cloudy Wind)
+ LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Frigid dip high behind River Cat", 3654 + treasure_index_offset, lambda state: logic.has_glide(state) or logic.has_swimming(state)), #(60, 225, -435) Overpass (Snow) River Cats Ego map
+ LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Chilling by Nomad's Outpost", 3676 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state)), # (45, 215, -465) Overpass (Outpost) Scrap
+ LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass Chest - Ultimate Mulan challenge past mushroom mountain", 1401 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #(-35, 166, -387) Overpass (Cloudy Wind) Zether Pouch chest
+
+ #NPCs
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Reid chilling by the Fish Hatchery", 2410 + npc_index_offset), #(113, 172, -372) Courtyard Key; Fixed Missable
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race Participation Prize", 50639 + npc_index_offset, lambda state: logic.has_rental_salmon(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 14th place prize", 50640 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 12th place prize", 50641 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 10th place prize", 50642 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 8th place prize", 50643 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 6th place prize", 50644 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 4th place prize", 50645 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 3rd place prize", 50646 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Salmon Race 2nd place prize", 50647 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " NPC - Win the Salmon Race", 639 + npc_index_offset, lambda state: logic.has_swimming(state)),
+ #Mushroom Mountain
+ LocationData(MUSHROOM_MOUNTAIN_AP_REGION, "Overpass NPC - Fall off mushroom mountain onto Gold", 2739 + npc_index_offset), #(63, 191, -399) 2nd Gold Dust on Overpass (Cloudy Wind)
+
+ #Poko Poko Desert
+ #Treasure chests
+ #Poko Poko Desert main
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - West Lookout Tower", 1170 + treasure_index_offset), # West Lookout Token chest
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Quintar leapfrog", 1080 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Butter Cutter chest
+ #if you're Good At Jumping you can get to this chest south of rocky outcropping gold with no mounts
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - South of tricky Quintar Gold", 1082 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Hatchet chest
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - This chests (on) a butte", 1169 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME)), # Dueller
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - North Lookout Tower", 1190 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME) or logic.has_vertical_movement(state)), #North Lookout Token chest
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Stormy first floor of ruins", 2676 + treasure_index_offset), #Fenix Juice chest
+ #Poko Poko Desert Eastern Plateau
+ LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Potion chest to fortify you for jumping puzzle from hell", 2708 + treasure_index_offset),
+ LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Central Lookout Tower (ok maybe that jumping puzzle wasn't that bad)", 1189 + treasure_index_offset), # Central Lookout Token chest
+ LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Balance beam", 97 + treasure_index_offset), # Scope Specs chest
+ LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Past Lost Son", 1667 + treasure_index_offset), # Ether Pouch chest
+ #Tower of Zot
+ LocationData(TOWER_OF_ZOT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Chest - Cooling off in the tent before the Tower of Zot", 2914 + treasure_index_offset), #Salmon Bay map chest
+
+ #NPCs
+ #NPCs CheckOrNot: three Quintar Eggs in Poko Poko Desert (Nest) map - not
+ #Poko Poko Desert main
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver beneath overhang in ruins south of shrine", 2675 + npc_index_offset), #Dust
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver slumbering in broken house NE of shrine", 1081 + npc_index_offset), #Ingot
+ # if you're Good At Jumping you can get to this rocky outcropping gold with no mounts
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Rocky outcropping Gold will put your Quintar to the test", 2817 + npc_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Dust
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Stormy Silver atop ruins", 2677 + npc_index_offset), # Ore
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold Ingot atop ridge south of North Lookout Tower", 2818 + npc_index_offset),
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Thirsty Lad", 1201 + npc_index_offset, lambda state: state.has(SPECIAL_MILK, player)),
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver in desert arch shade", 2682 + npc_index_offset), # Ingot
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Silver in the sandstorm on ruins 2nd floor", 2680 + npc_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), #Dust
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Stormy Silver on ruined building floor", 2681 + npc_index_offset), #Ore
+ #Poko Poko Eastern Plateau
+ LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold on an outcropping by long loop-around chest", 2706 + npc_index_offset), # Ore
+ LocationData(POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold overlooking Sara Sara Bazaar", 2707 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Ingot
+ #Poko Poko Sara Sara Beach West Mountain Pass
+ LocationData(POKO_POKO_BEACH_WEST_PASS_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - High western gold (approach from the Lookout Tower or the Beach Nest)", 2711 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), #Dust
+ #Tower of Zot
+ LocationData(TOWER_OF_ZOT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Diamond bedazzling the Tower of Zot", 2879 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #Dust
+ LocationData(TOWER_OF_ZOT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " NPC - Gold bedazzling the Tower of Zot", 2816 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #Ore
+
+ #Sara Sara Bazaar
+ #Treasure chests
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Someone took the St James and left a...", 408 + treasure_index_offset, lambda state: logic.has_key(state, ROOM_ONE_KEY)), #Knockout Stick chest
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Darkened upper storeroom", 414 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME) or logic.has_horizontal_movement(state)), #Potion chest
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Wedge yourself through the crates in the darkened upper storeroom", 513 + treasure_index_offset, lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME) or logic.has_horizontal_movement(state)), #Storm Rod chest
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Potion Mixer", 1194 + treasure_index_offset), #Beaurior Volcano map chest
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Chest - Spilled booty", 2936 + treasure_index_offset, lambda state: logic.has_swimming(state)), #Captains Hat chest
+
+ #NPCs
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Quintar West Stable Owner", 1852 + npc_index_offset, lambda state: logic.has_jobs(state, 7)), #Quintar Pass; Fixed Missable
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Quintar East Stable Owner", 2234 + npc_index_offset, lambda state: logic.has_jobs(state, 7)), #Quintar Pass; Fixed Missable
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Ferry Ticket Agent forgot what Ferry Passes are but she found something in her desk", 940 + npc_index_offset, lambda state: logic.has_jobs(state, 11)), #(-166,93,56) Fixed Missable
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Three tokens makes a Pyramid Key something something triangles", 949 + npc_index_offset, lambda state: state.has(WEST_LOOKOUT_TOKEN, player) and state.has(CENTRAL_LOOKOUT_TOKEN, player) and state.has(NORTH_LOOKOUT_TOKEN, player)),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - The One and Only Room 1 Key", 385 + npc_index_offset, lambda state: logic.can_earn_money(state, SARA_SARA_BAZAAR_AP_REGION)),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Circle the eastern desert wall for Worried Moms Lost Son", 1196 + npc_index_offset, lambda state: (state.has(POKO_POKO_DESERT_PASS, player) or logic.options.regionsanity.value != options.regionsanity.option_extreme) and logic.is_area_in_level_range(state, POKO_POKO_ENEMY_LEVEL)), #Ferry Pass, if regionsanity extreme is enabled, you'll need the pass for the desert
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Pelt this Fish Merchant with Rotten Salmon", 942 + npc_index_offset, lambda state: state.has(SPECIAL_ROTTEN_SALMON, player) and state.has(SPECIAL_FRESH_SALMON, player)),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - No Shoudu Stew for you!", 1200 + npc_index_offset, lambda state: state.has(SPECIAL_SHOUDU_STEW, player)),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silver", 2905 + npc_index_offset, lambda state: logic.has_swimming(state)), #Dust
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silverer", 2906 + npc_index_offset, lambda state: logic.has_swimming(state)), #Dust
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silvererer", 2903 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ingot
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silverererer", 2904 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ingot
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silvererererer", 2901 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ore
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " NPC - Spilled booty Silverererererer", 2902 + npc_index_offset, lambda state: logic.has_swimming(state)), #Ore
+
+ #Sara Sara Beach East
+ # Treasure chests
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Chest - Glittering in the sun at Ibek Cave exit 1", 1083 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Tincture Pouch chest
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Chest - Glittering in the sun at Ibek Cave exit 2", 1085 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Tonic Pouch chest
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Chest - How dare you stand where he stood?", 1084 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Money chest
+
+ # NPCs
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 1", 2683 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Dust
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 2", 2684 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Dust
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 3", 2686 + npc_index_offset), # Dust
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 4", 2688 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Silver
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 5", 2689 + npc_index_offset), # Ore
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver glittering in the sun at Ibek Cave exit 6", 2690 + npc_index_offset), # Ore
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Jaunt along cliff past Dr Cool Aids perch to Silver", 2685 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Ingot
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver on the beach rocks at eastern edge", 2687 + npc_index_offset), # Ingot
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " NPC - Silver beheld by Dr Cool Aids", 2691 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Ore
+
+ #Sara Sara Beach West
+ #Treasure chests
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - South of Beach Birds Nest", 154 + treasure_index_offset), #Ether chest
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - Across the palms above the dust", 1509 + treasure_index_offset), #Potion chest
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - Beach cave", 2718 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Blank Pages chest
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Chest - Tightrope walk below Beach Birds Nest", 1546 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state)), #Potion chest; possible with rental if masochists play our game/Good At Jumping option
+
+ #NPCs
+ #Todo NPCs Job Masters: Master Dervish ID 3575 (-255, 103, -237); gives you Dervish Seal in exchange for job mastery
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Cross my palms with Silver", 2693 + npc_index_offset), #Dust
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver past angry birds", 2697 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_glide(state)), #Dust
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver south of Beach Birds Nest", 2694 + npc_index_offset), #Ingot
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver at the foot of the Tower of Zot", 2699 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Ingot
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Lonely Islet Silver", 2878 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Ingot
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Southern silver along the cliffside", 2692 + npc_index_offset), #Ore
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver chilling in beach cave", 2698 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)) or logic.has_swimming(state)), #Ore
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Silver further along beach", 2877 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ore
+
+ #Ancient Reservoir
+ #Treasure chests
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Really elaborate crystal rafters", 1123 + treasure_index_offset), #Red Coat chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Crystal gutters", 1122 + treasure_index_offset), #Red Cap chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Tucked on ledge by aqueduct", 1982 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state)), #Resist Shifter chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - East Switch Room", 2977 + treasure_index_offset), #Ether Pouch chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Eastern nyoom room", 2056 + treasure_index_offset), #Money chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Hiding behind aqueduct grate", 2703 + treasure_index_offset), #Potion Pouch chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Hiding behind western aqueduct grate", 2702 + treasure_index_offset), #Money chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Twinsies the 1st at west waterfall base", 2704 + treasure_index_offset), #Defense Shifter chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Twinsies the 2nd at west waterfall base", 1145 + treasure_index_offset), #Money chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - I saw Red vent in the eastern stairwell", 2701 + treasure_index_offset), #Grim Scythe chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Goat snack for later", 2915 + treasure_index_offset), #Ancient Reservoir map chest
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, "Underpass Chest - Waterway nook between Gran & Ancient Reservoir", 3541 + treasure_index_offset, lambda state: logic.has_swimming(state)), #(64, 98, -111) 1st Underpass Scrap on main map
+
+ #NPCs
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Silver in odd flooded room 1", 2695 + npc_index_offset), #Ingot
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Silver in odd flooded room 2", 1675 + npc_index_offset), #Ore
+
+ #Ibek Cave
+ #Treasure chests
+ LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Chest - Celebrate your new hops", 2517 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Fenix Juice Pouch chest
+
+ #NPCs
+ LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Goat victory Ibek Bell", 1676 + npc_index_offset), # Z30_PostBossEvent;
+ LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " NPC - Silver in the goat digs", 2696 + npc_index_offset, lambda state: logic.has_vertical_movement(state)), # Dust
+
+ #Salmon Bay
+ #Treasure chests
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Chest - Cliff diving", 2975 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), #Ether Pouch chest
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Chest - Across the bridge", 2974 + treasure_index_offset), #Potion Pouch chest
+ LocationData(SALMON_RIVER_MOUTH_AP_REGION, "Overpass Chest - Lonely scrap among half-dead pines above Salmon Bay", 3677 + treasure_index_offset), #8th Scrap in Overpass main map
+
+ #NPCs
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " NPC - Ancient Tablet B on moodlit shore behind waterfall", 2438 + npc_index_offset),
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " NPC - West cliff diving Ancient Tablet C", 1271 + npc_index_offset, lambda state: logic.has_vertical_movement(state)),
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " NPC - Quintar splish splash Ancient Tablet A", 1272 + npc_index_offset),
+
+ #Overpass
+ #Treasure chests
+ #Life Jewel on main map has been categorized under Dione Shrine
+ #1st Scrap on main Overpass map has been categorized under Rolling Quintar Fields
+ #2nd Scrap on main Overpass map has been categorized under the Quintar Sanctum
+ #3rd Scrap on main Overpass map has been categorized under the Okimoto N.S.
+ #4th Scrap on main Overpass map has been categorized under Dione Shrine
+ #5th Scrap on main Overpass map has been categorized under Quintar Reserve
+ #6th Scrap on main Overpass map has been categorized under Yamagawa M.A.
+ #7th Scrap on main Overpass map has been categorized under Beaurior Rock
+ #8th Scrap on main Overpass map has been categorized under Salmon Bay
+ #9th Scrap on main Overpass map has been categorized under Lands End
+ #1st Overpass Scrap (Cloudy Wind) has been categorized under Tall Tall Heights
+ #Zether Pouch (Cloudy Wind) has been categorized under Salmon River
+ #River Cats Ego map (Snow) recategorized under Salmon River
+ #Overpass (Outpost) Northern Stretch map has been categorized under Northern Stretch
+ #Overpass (Outpost) Scrap has been categorized under Tall Tall Heights
+
+ #NPCs
+ #Todo NPCs Job Masters: Overpass main map has Master Hunter ID 3558 (496, 198, -346); gives you Hunter Seal in exchange for job mastery
+ #Gold Ingot on Overpass main map has been categorized under Dione Shrine
+ #Z20_WaterOrigin ID 1583 (gives Springs Oath) has been categorized under Okimoto N.S. (632, 243, -261)
+ #1st Gold Dust (Cloudy Wind) has been categorized under Tall Tall Heights
+ #2nd Gold Dust (Cloudy Wind) has been categorized under Salmon River
+ #Todo NPCs CheckOrNot: Overpass 4 Quintar Eggs (Dirt Nest), 1 Quintar Egg (Stone Nest), 1 Quintar Egg (Cave Nest), 1 Quintar Egg (Stone Nest) submap
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Aegis ID 3610 (501, 194, -210); gives you Aegis Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Beastmaster ID 3608 (312, 236, -344); gives you Beastmaster Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Ninja ID 3550 (561, 238, -209); gives you Ninja Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Nomad ID 3548 (53, 227, -465); gives you Nomad Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Reaper ID 3611 (232, 220, -383); gives you Reaper Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Summoner ID 3557 (379, 246, -575); gives you Summoner Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost) has Master Valkyrie ID 3554 (24, 137, 71); gives you Valkyrie Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Overpass (Outpost 3H) has Master Fencer ID 3573 (166, 154, -124); gives you Fencer Seal in exchange for job mastery
+
+ #Underpass
+ #Treasure chests
+ #3 treasures categorized under Delende
+ #1st Scrap main Underpass map has been categorized under Ancient Reservoir
+ #1st Scrap main Underpass map has been categorized under Quintar Nest
+ #3rd Scrap main Underpass map has been categorized under Pale Grotto
+ #4th Scrap main Underpass map has been categorized under Jojo Sewers
+ #5th Scrap main Underpass map categorized under Delende
+ #6th Scrap main Underpass map has been categorized under the Quintar Mausoleum
+ #7th Scrap main Underpass map has been categorized under the Capital Jail
+ #Underpass (Okimoto) Scrap has been categorized under Cobblestone Crag
+ #Underpass (Summon Pah) Scrap has been categorized under The Undercity
+ #Underpass (Ice Pass) Scrap chest has been categorized under Tall Tall Heights
+ #Underpass (Ice Pass) Potion chest has been categorized under Tall Tall Heights
+ #Underpass (Underwater) Scrap chest has been categorized under Tall Tall Heights
+
+ #Zones (Expert)
+ #The Open Sea
+ #Treasure chests
+ #Atolls
+ LocationData(JIDAMBA_ATOLLS_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " Chest - Atoll southeast of Jidamba Tangle", 3767 + treasure_index_offset), # Fenix Syrup chest
+ LocationData(JIDAMBA_ATOLLS_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " Chest - Atoll southwest of southeast Jidamba Tangle atoll", 3765 + treasure_index_offset), # Z-Potion chest
+ #NPCs
+ LocationData(THE_OPEN_SEA_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " NPC - I'm on a raft!", 2804 + npc_index_offset, lambda state: state.has("Item - Super Rod", player) and state.has("Item - Jigging Lure", player)),
+ #CheckOrNot: (930, 91, 253) do we put a check on the guy who gives you a Gaea Shard if you get there with no Salmon lol: no
+ #LocationData(THE_OPEN_SEA_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " NPC - Z34_SinisterSailor", 2520 + npc_index_offset),
+
+ #Shoudu Waterfront
+ #Treasure chests
+ LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Chest - Along the water", 2419 + treasure_index_offset), #Money chest
+ LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Chest - Hop around 1", 3690 + treasure_index_offset), #Empty chest
+ LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Chest - Hop around 2", 1114 + treasure_index_offset), #Mars Stone chest
+
+ #Shoudu Province
+ #Treasure chests
+ #Port area
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Jump through a window", 1507 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Ether Pouch chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Lurking above spike ball pit by goldsmith", 2984 + treasure_index_offset), # (753, 105, -176) Tincture Pouch chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Weaponsmith", 1505 + treasure_index_offset), # Plague Mask chest
+
+ #Shanty Inn Home Point area
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Outside the inn", 2985 + treasure_index_offset), # Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Sneaky back door of cramped storage room", 1519 + treasure_index_offset), # Looters Pin chest
+
+ #Moving up past here requires Ibek/Owl
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Below fast boi spark", 3504 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Through rooftop window south of fast boi spark 1", 3506 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Through rooftop window south of fast boi spark 2", 2763 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Reservoir above the water", 3508 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_swimming(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above accessory store", 3509 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Building near all the grates", 3510 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above Samurai Lounge 1", 3511 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above Samurai Lounge 2", 1541 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Fleuret chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Samurai Lounge", 3512 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Assassin Lounge", 3513 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Among crates across from Assassin Lounge", 3514 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Its in a room and there is a bed", 3515 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Granary", 3520 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Below the flower house", 3521 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - White hut", 3522 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Across the reservoir", 2978 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) and logic.has_swimming(state)) or logic.has_glide(state)), #Ether Pouch chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Crawl along the attic", 1536 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Knicked Knackers chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Sneak behind crates near Assassin Lounge", 2760 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Muggers Glove chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Go in the back door", 1506 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Near the Assassin Lounge", 2762 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Jump along the lamppost", 2752 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Suitor Hat chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Above the armor store", 1517 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Enter building next to white hut to balance above The Undercity 1", 2717 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ether chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Enter building next to white hut to balance above The Undercity 2", 2716 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Tincture Pouch chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Cross the balance beam east of Fields save point", 3040 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion Pouch chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Cross the balance beam on the way to Sky Arena", 2754 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Acrobat Shoes chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Atop the roofs near the grates", 1369 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - In the flower room", 2789 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Hidden in a house by the elevator 1", 3505 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Hidden in a house by the elevator 2", 2790 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Near sky fishing 1", 3507 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Elevator Part chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Near sky fishing 2", 2986 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Under the dry kid pit", 1365 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #The Immovable chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Fall through broken grate in building west of Sky Arena Prize Counter", 2951 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Potion chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins Room 1", 2794 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), # Money chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins Room 2", 2751 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), # Bone Mail chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins room 3", 2747 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), # Cutlass chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins room 4", 2796 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), #Tonic Pouch
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 2 Sky Arena Wins room 5", 2748 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), #Soul Kris chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 1", 2812 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Money chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 2", 2723 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Gaia Axe chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 3", 2813 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Money chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 5 Sky Arena Wins room 4", 2753 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Gaia Vest chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 8 Sky Arena Wins room 1", 2665 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Gravedigger chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 8 Sky Arena Wins room 2", 2805 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Malifice chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 8 Sky Arena Wins room 3", 2800 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Wizards Wall chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 10 Sky Arena Wins room 1", 2756 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(753, 134, -263) Yasha chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 10 Sky Arena Wins room 2", 2928 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(754, 134, -264) Muramasa chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - 10 Sky Arena Wins room 3", 2929 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(755, 134, -263) Shadow Gi chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Fall through floorboards of 10 Sky Arena Wins room 1", 3763 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(754, 130, -264) Zether chest
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Chest - Fall through floorboards of 10 Sky Arena Wins room 2", 3764 + treasure_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(755, 130, -263) Z-Potion chest
+
+ #NPCs
+ #Todo NPCs Job Masters: Master Assassin ID 3605 (769, 123, -201); gives you Assassin Seal in exchange for job mastery
+ #Todo NPCs Job Masters: Master Samurai ID 3576 (800, 115, -221); gives you Samurai Seal in exchange for job mastery
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Chloe and Talon sky fishing", 3702 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #(765, 125, -248) Fixed Missable; removed post-sparkle
+ #NPCs Multichecks: Shoudu Province (Sky Arena) map Z38_SkyArenaPrizes ID 1921 (765, 125, -248) gives 5 prizes in exchange for winning fights
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 1 Sky Arena Win Prize", 51921 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIRST_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 4 Sky Arena Wins Prize", 51922 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FOURTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 6 Sky Arena Wins Prize", 51923 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SIXTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 7 Sky Arena Wins Prize", 51924 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SEVENTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 9 Sky Arena Wins Prize", 1921 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, NINTH_SKY_ARENA_FIGHT_LEVEL)), #(765, 125, -248)
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 10 Sky Arena Wins room Diamond 1", 2833 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(752, 133, -262) Dust
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - 10 Sky Arena Wins room Diamond 2", 2811 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(756, 133, -261) Ingot
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Diamond through a hole in the 10 Sky Arena Wins room floor", 2832 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #(753, 130, -264) Ore
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold at back reservoir wall", 2827 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.has_swimming(state)), #Ingot
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in farmland on way to shrine", 2821 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ingot
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold near sky fishing", 2834 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ore
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 2 Sky Arena Wins room", 2829 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, SECOND_SKY_ARENA_FIGHT_LEVEL)), #Dust
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 5 Sky Arena Wins room 1", 2720 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Ore
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 5 Sky Arena Wins room 2", 2722 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Ingot
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 5 Sky Arena Wins room 3", 2721 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, FIFTH_SKY_ARENA_FIGHT_LEVEL)), #Dust
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 8 Sky Arena Wins room 1", 2830 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Ingot
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Gold in 8 Sky Arena Wins room 2", 2831 + npc_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, EIGHTH_SKY_ARENA_FIGHT_LEVEL)), #Ore
+
+ #The Undercity
+ #Treasures
+ # can get without mounts from Shoudu
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 1", 2988 + treasure_index_offset), # Fenix Juice chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 2", 2987 + treasure_index_offset), # Ether chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 3", 1147 + treasure_index_offset), # Potion chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Gated-off room 4", 3517 + treasure_index_offset), # (778, 94, -254) Elevator Part chest
+ #except these up high
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Climb up lampposts and run across the fence", 1925 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Cursegiver chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - North wall climb (or fall through broken grate by Sky Arena Prize Counter)", 3516 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Elevator Part chest
+ #Home Point Stone area - can walk from Ganymede Shrine -> Undercity entrance next to white hut -> fall off rafter; can also swim from the sea
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Hiding in the rafters", 2989 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Potion Pouch chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Atop awning east of the waterfall", 3518 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Elevator Part chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Hidden in a nook in the wall", 2793 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), # Knights Plate chest
+ #ibek from Home Point Stone area
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Up the rafters against a pillar", 2990 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Ether chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Even further up the rafters", 2991 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), # Ether Pouch chest
+ #Undercity Inn area - can walk from Home Point Stone area if you hop up on guy who wants you to defeat the Undercity masters, go north, and hop west across lamps
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Hiding in a building in the north area", 2826 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Potion chest
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - Undercity Inn", 3519 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Elevator Part
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Chest - South of the Undercity Inn", 1695 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), # Brigandine chest
+ #swimmy swimmy
+ LocationData(THE_UNDERCITY_AP_REGION, "Underpass Chest - Lovely bounce tree W of The Undercity", 3673 + treasure_index_offset, lambda state: logic.has_swimming(state) or logic.has_glide(state)), #(608, 91, -215) (Summon Pah) Underpass Scrap chest
+
+ #NPCs
+ #can get without mounts from Shoudu
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Gold hiding from the bats under the awning", 2835 + npc_index_offset), #Dust
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Gated-off room Gold", 2825 + npc_index_offset), #Ore
+ #other side of canal from Home Point Stone area
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Gold in the sewer offshoot", 1696 + npc_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), #Dust
+ #Undercity Inn area
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " NPC - Storage room Gold of the Undercity Inns", 1694 + npc_index_offset, lambda state: logic.has_swimming(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player)), #Ingot
+
+ #Ganymede Shrine
+ #Treasure chests
+ LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Chest - drop down from the top", 1594 + treasure_index_offset, lambda state: state.has(GANYMEDE_STONE, player)),
+
+ #Beaurior Volcano
+ #Treasure chests
+ LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Chest - Beaurior Rock entrance", 3770 + treasure_index_offset), #Fenix Syrup chest
+ LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Chest - Outcropping above the fog", 1168 + treasure_index_offset), #Temporal Blade chest
+ LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Chest - Tricky jumps past Rock entrance", 2750 + treasure_index_offset), #Tome of Light chest
+
+ #Beaurior Rock
+ #Treasure chests
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B4 big lava room with optional boss", 1796 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Ether chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Caged in far corner of B4 big lava room with optional boss", 481 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Guard Crown chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Caged through secret tunnel in B4 big lava room with optional boss", 724 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Halberd chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Island in B4 big lava room with optional boss", 1682 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Small Key chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Elevator down from entrance to happy spike land", 894 + treasure_index_offset), #Small Key chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B3 balcony above the lava", 1337 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY)), #Small Key chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B2 with the Lets Make a Deal doors", 2973 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Potion Pouch chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 overlooking the catwalks room", 818 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Small Key chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 square pokeball room", 2916 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #map chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B3 behind wrought-iron fence", 899 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Shelter Dress chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B3 ominous green dumplings room", 1797 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2)), #Fenix Juice chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B2 danger hops back to purple", 2044 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Ether Pouch chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 cheeky lava platforming 1", 2041 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Potion
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 cheeky lava platforming 2", 1799 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Ether
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - B1 jump to odd ice block", 2040 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Cold Touch chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Chest - Top floor ominous lamps room", 1683 + treasure_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Boss Key chest
+ LocationData(BEAURIOR_ROCK_AP_REGION, "Overpass Chest - West of Valkyrie Watchtower", 3540 + treasure_index_offset), # (1, 129, 62) 7th Overpass Scrap on main map
+
+ #NPCs
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " NPC - Gold in B4 big lava room with optional boss", 2822 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY) and logic.has_horizontal_movement(state)), #Gold Ingot
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " NPC - B1 cheeky lava platforming Gold", 2823 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4)), #Dust
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " NPC - B1 Magic Well friendos hiding in the pillars", 2824 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_horizontal_movement(state)), #Ore
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " NPC - Summit Gold", 2836 + npc_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)), #Ore Beaurior Volcano but it's at the top so Rock it is
+
+ #Lake Delende
+ #Treasure chests
+ LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Chest - North edge 1", 1263 + treasure_index_offset), #Float Shoes chest
+ LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Chest - North edge 2", 2917 + treasure_index_offset), #Lake Delende map chest
+
+ #NPCs
+ #Atop Salmon River Dam
+ LocationData(ATOP_DAM_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " NPC - Panning for Gold down Salmon Creek without a paddle", 2854 + npc_index_offset), #Dust
+
+ #Quintar Reserve
+ #Treasure chests
+ LocationData(QUINTAR_RESERVE_AP_REGION, "Overpass Chest - Climbing the boughs up from the elevator", 3536 + treasure_index_offset), #5th Scrap on Overpass main map
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Chest - Race start hut", 1591 + treasure_index_offset), #Quintar Grass chest
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Chest - Hollowed-out wall of Mausoleum", 1320 + treasure_index_offset, lambda state: logic.has_glide(state)), #Undead Ring chest
+
+ #NPCs
+ #Reserve main
+ #Todo NPCs CheckOrNot: 3 Quintar Eggs here
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Eastern Quintar overlooking the sea", 427 + npc_index_offset, lambda state: state.has(BABEL_QUINTAR, player)), #The Sequoia map (789, 191, -338); Fixed Missable
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding in hidden Quintar nest down in the ravine SE of Dione Shrine", 2255 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state) and logic.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)), #Shedding 1
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding in sneaky Quintar nest in eastern wall of the canyon north of the Mausoleum", 2256 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state) and logic.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)), #Shedding 2
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding in super sneaky Quintar nest in canyon north of the Mausoleum", 2257 + npc_index_offset, lambda state: logic.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)), #Shedding 3
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding east of shrine", 2259 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 4
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Long jog along the east mountain to shedding", 2260 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 5
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding overlooking the east ocean", 2261 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 6
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Treetop shedding north of Mausoleum", 2263 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 8
+ #shedding 9 is in the Dione Shrine because why not I guess
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding overlooking the race start point", 2265 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 10
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding north of Mausoleum", 2266 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Shedding 11
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding just north of Quintar cosplayer", 2267 + npc_index_offset), #Shedding 12
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Gold on east side of map", 2837 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Ore
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Climb the center mountain for Gold", 2839 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)), #Ore
+ #Reserve Treetops
+ LocationData(RESERVE_TREETOPS_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Shedding atop the Mausoleum", 2262 + npc_index_offset, lambda state: logic.has_glide(state)), #Shedding 7
+ LocationData(RESERVE_TREETOPS_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " NPC - Jump across the treetops for Gold", 2840 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), #Dust
+
+ #Dione Shrine
+ #Treasure chests
+ #Shrine proper
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - Lobby", 2791 + treasure_index_offset), #Dione Shard chest
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - 2nd floor", 2792 + treasure_index_offset), #Dione Shard chest
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - 2nd floor balcony", 1146 + treasure_index_offset), #Dione Shard chest
+ #Shrine Roof
+ LocationData(DIONE_ROOF_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Chest - Roof", 2154 + treasure_index_offset), # Dione Shard chest
+ LocationData(DIONE_ROOF_AP_REGION, "Overpass Chest - Glide SW from top of shrine 1", 3535 + treasure_index_offset, lambda state: logic.has_glide(state)), #4th Scrap on main Overpass map
+ LocationData(DIONE_ROOF_AP_REGION, "Overpass Chest - Glide SW from top of shrine 2", 2749 + treasure_index_offset, lambda state: logic.has_glide(state)), #Life Jewel Overpass main map
+
+ #NPCs
+ #Shrine Roof
+ LocationData(DIONE_ROOF_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " NPC - Shedding on roof", 2264 + npc_index_offset), #Shedding 9
+ LocationData(DIONE_ROOF_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " NPC - Glide SW from top of shrine to Gold", 2838 + npc_index_offset, lambda state: logic.has_glide(state)), #Ingot on Overpass main map
+
+ #Quintar Mausoleum
+ #Treasure chests
+ LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Chest - Past the switches race", 2153 + treasure_index_offset, lambda state: logic.has_fast(state)), #(688, 114, -464) Babel Quintar chest
+ LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Chest - Rocky room", 3401 + treasure_index_offset), #(664, 129, -425) Quintar Mausoleum map chest
+ LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Chest - Glowing grass room", 3768 + treasure_index_offset), #(709, 129, -442) Wind Thresher chest
+ LocationData(QUINTAR_MAUSOLEUM_AP_REGION, "Underpass Chest - Up the waterfall inside Quintar Mausoleum", 3674 + treasure_index_offset), #(614, 146, -410) 6th Scrap chest on main Underpass map
+
+ #Eastern Chasm
+ #Treasure chests
+ LocationData(EASTERN_CHASM_AP_REGION, EASTERN_CHASM_DISPLAY_NAME + " Chest - Overgrown opposite of chasm", 3543 + treasure_index_offset), #Eastern Chasm map chest
+
+ #Tall Tall Heights
+ #Treasure chests
+ #Boomer Overlook
+ LocationData(BOOMER_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Above the Boomer Society", 2844 + treasure_index_offset), # Z-Potion Pouch chest
+ #Note: this chest is not technically connected to this region but it has the exact same requirements to get to it from Boomer Society/Tall, Tall Heights Save Point
+ LocationData(BOOMER_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Lonely chest over the ridge from Boomer Society", 2428 + treasure_index_offset), # Ether
+ #Greenshire Overlook
+ LocationData(GREENSHIRE_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the icy Chips Challenge", 2786 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), #Tear Seed chest
+ #Tall, Tall Heights Save Point
+ LocationData(TALL_TALL_SAVE_POINT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the 2nd icy Chips Challenge", 2788 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), # Tear Seed chest
+ LocationData(TALL_TALL_SAVE_POINT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the 3rd icy Chips Challenge", 1254 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), # Potion chest
+ #Lower Ice Lakes
+ LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Past the Chips Challenge fishing hut", 1578 + treasure_index_offset, lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)), # Frost Reaper chest
+ LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Tall stones and blue flowers", 2992 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Potion Pouch chest
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Underpass Chest - Ice swimming instead of ice fishing", 3623 + treasure_index_offset, lambda state: logic.has_swimming(state)), # (191, 172, -437) (Underwater) Underpass Scrap chest
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Overpass Chest - Past Tall Tall Heights spiky tunnel to Salmon River", 3538 + treasure_index_offset), # 1st Overpass (Cloudy Wind) Scrap
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Underpass Chest - Tall Tall Heights spiky tunnel to Salmon River 1", 3672 + treasure_index_offset), # Underpass (Ice Pass) Scrap
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Underpass Chest - Tall Tall Heights spiky tunnel to Salmon River 2", 1601 + treasure_index_offset), # Underpass (Ice Pass) Potion
+ #Upper Ice Lakes
+ LocationData(UPPER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Above the Triton Shrine", 2795 + treasure_index_offset), # Ether chest
+ #Tall, Tall Diamondsmith
+ LocationData(TALL_TALL_DIAMONDSMITH_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Parkour off the diamondsmith beneath the dead tree", 2810 + treasure_index_offset), # Judo Gi chest
+ #Slip Glide Ride Exit
+ LocationData(SLIP_GLIDE_RIDE_EXIT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Break the ice", 2744 + treasure_index_offset), # Radiance Northern Cave
+ #Sequoia Athenaeum Entrance
+ LocationData(SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - East of the souvenir store", 2993 + treasure_index_offset), # Money chest
+ #Sequoia Athenaeum Balcony
+ LocationData(SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - A funny chest happened on the way to the Athenaeum", 2994 + treasure_index_offset), # Z-Potion chest
+ LocationData(SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - East of the Athenaeum", 2565 + treasure_index_offset), # Potion Pouch chest
+ #Tall, Tall Tall Chest
+ LocationData(TALL_TALL_TALL_CHEST_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Chest - Athenaeum outdoors Chip's Challenge (or be a bird)", 2785 + treasure_index_offset), #Insignia Helm chest
+
+ #NPCs
+ #Todo NPCs Job Masters: Tall Tall Heights (Outpost) map has Master Chemist ID 3707 (491, 221, -389); gives you Chemist Seal in exchange for job mastery
+ #Todo NPCs Player Options: (197, 192, -441) do we want a filter option to add the guys who fish things up for you
+ #Boomer Overlook
+ LocationData(BOOMER_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold above the Boomer Society", 1600 + npc_index_offset), #Ingot
+ #Tall, Tall Ramparts Crag Chest
+ LocationData(TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold tucked in melted snow past the Chip's Challenge east of shrine", 2846 + npc_index_offset), # Ore
+ #Greenshire Overlook
+ LocationData(GREENSHIRE_OVERLOOK_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - I bek you can get this one (or come back with the bird) for Gold", 2845 + npc_index_offset, lambda state: logic.has_glide(state) or logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME)), # Ingot
+ #Lower Ice Lakes
+ LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Melted snow Gold past the chest east of the Athenaeum", 2847 + npc_index_offset, lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)), # Ingot
+ LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Fish in the hut", 1549 + npc_index_offset, lambda state: state.has("Item - Tough Rod", player) and state.has("Item - Fly Lure", player)), # Z8_FisherInHut (197, 192, -441)
+ LocationData(LOWER_ICE_LAKES_AP_REGION, "Overpass NPC - Gold past Tall Tall Heights spiky tunnel to Salmon River", 2710 + npc_index_offset), # 1st Gold Dust Overpass (Cloudy Wind)
+ #Upper Ice Lakes
+ LocationData(UPPER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Chip Challenge himself", 2388 + npc_index_offset),
+ #Land's End Cottage Ridge
+ LocationData(LANDS_END_COTTAGE_RIDGE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Hop along spike mountain to Gold", 2853 + npc_index_offset), #Dust
+ #Slip Glide Ride Exit
+ LocationData(SLIP_GLIDE_RIDE_EXIT_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold by the breakable ice wall", 2814 + npc_index_offset), #Ingot
+ #Sequoia Athenaeum Balcony
+ LocationData(SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Gold past the Athenaeum Chips Challenge", 1602 + npc_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), # Dust
+ #Icy Spikes Madness
+ LocationData(ICY_SPIKES_MADNESS_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Treacherous landing Gold above the spikes", 1584 + npc_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)), #Ore
+
+ #Northern Cave
+ #Treasure chests
+ #Lower Northern Cave
+ LocationData(LOWER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Island in the ice", 2787 + treasure_index_offset), #Tear Seed chest
+ #Ice Cell
+ LocationData(ICE_CELL_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Ominous Chips Challenge cave", 1579 + treasure_index_offset), #Ice Cell Key chest
+ #Note: ibek here requires ice block; check can be made with quintar and not owl but the owl is required from Slip Glide Ride save point or Ice Cell start
+ LocationData(ICE_CELL_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Hop along the igloos or Quintar skip the Chip", 1552 + treasure_index_offset, lambda state: logic.has_glide(state) or logic.can_push_ice_block_and_goat(state, NORTHERN_CAVE_DISPLAY_NAME)), #Apprentice chest
+ #Upper Northern Cave
+ LocationData(UPPER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Chest - Past the wiggly block spike pit", 3001 + treasure_index_offset), #Money chest
+
+ #NPCs
+ #Upper Northern Cave
+ LocationData(UPPER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " NPC - Gold past the wiggly block spike pit", 2815 + npc_index_offset), #Ore
+
+ #Lands End
+ #Treasure chests
+ #Land's End Northern Peak
+ LocationData(LANDS_END_NORTHERN_PEAK_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Brave the spikes to climb the northern peak", 3002 + treasure_index_offset), # Money chest
+ #Land's End
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Definitely requires Quintar *wink* among the first spikes 1", 2849 + treasure_index_offset), #Ether chest
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Definitely requires Quintar *wink* among the first spikes 2", 3003 + treasure_index_offset), #Potion chest
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - To defeat the Huns", 2740 + treasure_index_offset, lambda state: logic.has_glide(state) or logic.has_vertical_movement(state)), #Blue Cape chest
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Tucked up high against River Cats Ego", 1692 + treasure_index_offset), #Blue Cape chest
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - In spikes and storm", 1358 + treasure_index_offset, lambda state: logic.has_horizontal_movement(state)), #Defender chest
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Fancy some spikes cliff diving?", 1693 + treasure_index_offset), #Rune Ward chest
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - Inside the shrine", 3017 + treasure_index_offset), #Ether chest
+ LocationData(LANDS_END_AP_REGION, "Overpass Chest - Lonely mountain ledge below owl shrine", 3678 + treasure_index_offset), #(191, 177, -214) 9th Scrap on main Overpass map
+ #Owl Tree
+ LocationData(OWL_TREE_AP_REGION, LANDS_END_DISPLAY_NAME + " Chest - By the lovely owl tree", 1561 + treasure_index_offset), # Callisto Stone chest
+
+ #NPCs
+ #Land's End Northern Peak
+ LocationData(LANDS_END_NORTHERN_PEAK_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Lets get down to business in the mountains for Gold", 2848 + npc_index_offset), #Ingot
+ #Land's End
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Pillar Gold by River Cats Ego", 2850 + npc_index_offset), #Ore
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Gold in spikes and storm", 2851 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), #Dust
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Gold behind the shrine", 2852 + npc_index_offset), #Ingot
+ #Owl Tree
+ LocationData(OWL_TREE_AP_REGION, LANDS_END_DISPLAY_NAME + " NPC - Owl Drum", 1176 + npc_index_offset),
+
+ #Slip Glide Ride
+ #Treasure chests
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Back out to 1st room", 2554 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY)), #Money chest
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Climb up and fall down", 1172 + treasure_index_offset), #Plate of Lion chest
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Sparks and tar", 1298 + treasure_index_offset), #Red Door Key chest
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - To the left through 2nd red lock", 1698 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY, 2)), #Red Door Key chest
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Through 1st red lock", 1716 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY)), #Red Door Key chest
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Past the mean Ibek jump", 1282 + treasure_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY)), #Sages Walker chest
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Chest - Nickelodeon slime time :)", 1269 + treasure_index_offset), #Seekers Garb chest
+
+ #Sequoia Athenaeum
+ #Treasure chests
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Atop the shelves above the books door", 2932 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #(412, 200, -551) Spellsword Helm chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Bullshit book-smart Chips Challenge", 2569 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), #(403, 199, -547) Z-Potion Pouch chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Brain-dumb Chips Challenge", 2322 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #(415, 180, -570) Ice Puzzle Key chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - 3rd library Chips Challenge", 2375 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(396, 180, -570) Ice Puzzle Key chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Chips Challenge ice squared", 2341 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 2) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(396, 164, -570) Ice Puzzle Key chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Chips Challenge we cheated on this one", 2371 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 3) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(415, 164, -572) Ice Puzzle Key chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Triple Chip Challenge", 2372 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 4) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(434, 164, -570) Ice Puzzle Key chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - Shattered labyrinth Chips Challenge", 2373 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 5) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(424, 148, -570) Ice Puzzle Key chest
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Chest - You expected another Chips Challenge, but it was me, Dio!", 2335 + treasure_index_offset, lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 6) and logic.has_vertical_movement(state) and logic.has_glide(state)), #(415, 131, -565) Skeleton Key chest
+
+ #Northern Stretch
+ #Treasure chests
+ LocationData(SUMMONERS_TOWER_AP_REGION, "Overpass Chest - At the base of Summoners Lookout", 3655 + treasure_index_offset), #Northern Stretch map in Overpass (Outpost)
+
+ #Castle Ramparts
+ #Treasure chests
+ #Lone Chest Rampart
+ LocationData(LONE_CHEST_RAMPART_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Tucked beside eastern turret", 1547 + treasure_index_offset), #(443, 206, -378) Money chest
+ #Peak Ramparts
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Below the crystal", 2908 + treasure_index_offset), #(407, 228, -383) Castle Ramparts map chest
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Jump down from eastern save point", 2742 + treasure_index_offset), #(440, 227, -386) Conquest chest
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Chest - Jump down from western save point", 2741 + treasure_index_offset), #(369, 227, -386) Rune Sword chest
+ #Technically Castle Sequoia but they're in a locked room only accessible from Peak Ramparts
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Locked Ramparts storage room 1", 2758 + treasure_index_offset, lambda state: logic.has_key(state, RAMPART_KEY)), #(375, 232, -452) (Skums) Decapitator chest
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Locked Ramparts storage room 2", 3657 + treasure_index_offset, lambda state: logic.has_key(state, RAMPART_KEY)), #(371, 231, -457) (Skums) Castle Sequoia map chest
+
+ #NPCs
+ #Peak Ramparts
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " NPC - Western Gold above spikes", 2843 + npc_index_offset, lambda state: logic.has_glide(state)), #(354, 231, -429) Ingot
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " NPC - Eastern Gold above spikes", 2842 + npc_index_offset, lambda state: logic.has_glide(state)), #(458, 231, -436) Ore
+
+ #The Chalice of Tar
+ #Treasure chests
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Chest - At the tippy-top", 3544 + treasure_index_offset, lambda state: logic.has_vertical_movement(state)), #The Chalice of Tar map chest
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Chest - Dont let your feathers touch the tar", 2587 + treasure_index_offset), #Vermillion Book chest
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Chest - Post tar tunnel", 2806 + treasure_index_offset), #Windsong chest
+
+ #NPCs
+ #Todo NPCs Job Masters: The Chalice of Tar has Master Mimic ID 3606 (526, 234, -438); gives you Mimic Seal in exchange for job mastery
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " NPC - Gold sparkling above the Overpass on the way up", 2841 + npc_index_offset), #Ore
+
+ #Flyers Crag
+ #Treasure chests
+ LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " Chest - You cant miss it", 3656 + treasure_index_offset), #(658, 216, -170) Flyers Crag map chest
+
+ #NPCs
+ LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " NPC - Gold twinsies the 1st south of Ganymede Shrine", 2820 + npc_index_offset), #(695, 137, -159) Dust
+ LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " NPC - Gold twinsies the 2nd south of Ganymede Shrine", 2819 + npc_index_offset), #(686, 132, -162) Ingot
+
+ #Flyers Lookout
+ #Treasure chests
+ #There are no checks here unless an Overpass Scrap shows up
+
+ #Jidamba Tangle
+ #Treasure chests
+ #Forest Floor
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Inside overgrown building E of Eaclaneya", 1629 + treasure_index_offset), # Demon Plate chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Atop overgrown building E of Eaclaneya", 3028 + treasure_index_offset), # Ether chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - NW foliage", 2807 + treasure_index_offset), # Partizan chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Tucked below NW foliage", 3025 + treasure_index_offset), # Potion chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - North of foliage river", 3027 + treasure_index_offset), # Z-Potion chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Smack in the center of the foliage", 1632 + treasure_index_offset), # Foliage Key chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Up a tree in north foliage", 3024 + treasure_index_offset), # Ether chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Along a river through the foliage", 3026 + treasure_index_offset), # Ether chest
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Island in the river through the foliage", 3011 + treasure_index_offset), # Tower Shield chest
+ #Jidamba Cave
+ LocationData(JIDAMBA_CAVE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Underground sneaky passage by NE cave exit", 2797 + treasure_index_offset), # Ravens Hood chest
+ LocationData(JIDAMBA_CAVE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Hop from underground root to sneaky passage pond", 2798 + treasure_index_offset, lambda state: logic.has_glide(state)), # Ravens Cloak chest
+ LocationData(JIDAMBA_CAVE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Accompanied by orange cave flowers", 1435 + treasure_index_offset, lambda state: logic.has_glide(state)), # Cave Key chest
+ #Eaclaneya Courtyard
+ LocationData(JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Eaclaneya entrance hall", 2919 + treasure_index_offset), # Jidamba Tangle map chest
+ #Summit
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Tucked against eastern side of Eaclaneya", 2801 + treasure_index_offset), # Flame Guard chest
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Atop Eaclaneya", 2808 + treasure_index_offset), # Rune Bow chest
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Tucked against western side of Eaclaneya", 2802 + treasure_index_offset), # Flamespike chest
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Scramble down the escarpment from Weaver Outpost to pedestal", 2803 + treasure_index_offset), # Siege Bow chest
+ #Canopy
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Up in the canopy south of shrine", 1171 + treasure_index_offset), # Demon Helm chest
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Chest - Smack in the center of the canopy", 1631 + treasure_index_offset), # Canopy Key chest
+
+ #NPCs
+ #Todo NPCs Job Masters: Jidamba Tangle (Outpost) has Master Weaver ID 3579 (627, 140, 77); gives you Weaver Seal in exchange for job mastery
+ #Forest Floor
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond atop broken ruins along the beach", 2872 + npc_index_offset, lambda state: logic.has_glide(state)), # Ore
+ #Diamondsmith
+ LocationData(JIDAMBA_DIAMONDSMITH_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Damp Diamond lurking beneath diamondsmith", 2869 + npc_index_offset), # Ingot
+ #Southwest Beach
+ LocationData(JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond hot girl summer on the beach", 2873 + npc_index_offset), # Dust
+ #South Cliff
+ LocationData(JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Splish splash Diamond", 2871 + npc_index_offset), # Dust
+ #Southwest Beach Cliff
+ LocationData(JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond at southern mouth of cave", 2874 + npc_index_offset, lambda state: logic.has_horizontal_movement(state)), # Ingot
+ #Summit
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Fly from Summoners weeping tree to hot tub Diamond", 2876 + npc_index_offset, lambda state: logic.has_glide(state)), # Ingot
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond atop broken ruins by the Summoner tree", 2875 + npc_index_offset, lambda state: logic.has_glide(state)), # Ore
+ #Canopy
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Chloe Queen of the Canopy", 2775 + npc_index_offset), #Super Rod (828, 119, 99); Fixed Missable
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Splash Mountain Diamond (pool at S end of canopy)", 2870 + npc_index_offset), # Ore
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Splash Mountain Gold (pool at NE end of canopy)", 2900 + npc_index_offset), # Ore
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Diamond in the boughs above the shrine", 2898 + npc_index_offset), # Ingot
+ LocationData(JIDAMBA_CANOPY_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Canopy Gold above big lake", 2899 + npc_index_offset), #Ingot
+
+ #Jidamba Eaclaneya
+ #Treasure chests
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Climb the lamp in the south room", 2799 + treasure_index_offset, lambda state: logic.has_glide(state) and logic.has_vertical_movement(state)), #Celestial Crown chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - At the end of spike hallway", 2755 + treasure_index_offset, lambda state: logic.has_glide(state)), #Flame Sword chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Monster cubby", 2920 + treasure_index_offset), #Jidamba Eaclaneya map chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Practice your swimming", 2282 + treasure_index_offset, lambda state: logic.has_glide(state)), #Ether Pouch chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Right side of the swimming puzzle", 2289 + treasure_index_offset, lambda state: logic.has_glide(state)), #Staff of Balance chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Glass elevator room", 2301 + treasure_index_offset, lambda state: logic.has_glide(state)), #Stardust Wand chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Underwater swimming puzzle", 2308 + treasure_index_offset, lambda state: logic.has_glide(state)), #Flameseeker chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Left side of underwater swimming puzzle", 2317 + treasure_index_offset, lambda state: logic.has_glide(state)), #Viridian Book chest
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Chest - Salmon Violin past the fish puzzles", 2288 + treasure_index_offset, lambda state: logic.has_glide(state)),
+
+ #NPCs
+ #1 Diamond Dust on Jidamba Eaclaneya Fish Floor map has been categorized under the Capital Pipeline
+
+ #The Deep Sea
+ #Treasure chests
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Descend into undersea vent where the flesh eaters live 1", 3451 + treasure_index_offset), #(878, 39, -536) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Touching Jidamba", 3658 + treasure_index_offset), #Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Ruins just south of Jidamba 1", 3659 + treasure_index_offset), #Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked sunken house off Jidamba with mighty arch 1", 3660 + treasure_index_offset, lambda state: logic.has_key(state, FORGOTTEN_KEY)), #(657, 53, 165) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Buried tunnel NW of Jidamba", 3661 + treasure_index_offset), #(545, 47, -31) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - By The Depths chasm SE of Shoudu Province", 3662 + treasure_index_offset), #(890, 51, -66) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Crumbling shrine 1", 3663 + treasure_index_offset), #(842, 53, -359) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Beside an undersea microruin NW of Tall Tall Heights", 3666 + treasure_index_offset), #(-23, 39, -557) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Underwater cove south of volcano", 3667 + treasure_index_offset), #(94, 59, 133) Deep Sea Scrap chest
+ #next 2 scraps from The Deep Sea (Sand Bar)
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Sunken shipwreck off west coast of Sara Sara Beach", 3664 + treasure_index_offset), #(-364, 53, -183) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Undersea valley S of Sara Sara Beach", 3665 + treasure_index_offset), #(-226, 49, 164) Deep Sea Scrap chest
+ #next scrap from The Deep Sea (Shrooms)
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Cavern below N coast of Tall Tall Heights", 3668 + treasure_index_offset), #(254, 53, -547) Deep Sea Scrap chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Descend into undersea vent where the flesh eaters live 2", 2767 + treasure_index_offset), #(872, 39, -517) Forgotten Key chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Crumbling shrine 2", 2290 + treasure_index_offset), #(838, 52, -357) Oven Mitt chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Ruins just south of Jidamba 2", 2937 + treasure_index_offset), #Paladin Wand chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Hidden tunnel in canyon W of cheeky sunroof NW of Jidamba", 2589 + treasure_index_offset), #(582, 47, -51) Rampart Key chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked sunken house off Jidamba with mighty arch 2", 2766 + treasure_index_offset, lambda state: logic.has_key(state, FORGOTTEN_KEY)), #(663, 54, 165) Soul Keeper chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked *wink* sunken house off Jidamba 1", 2768 + treasure_index_offset), #(649, 53, 195) Zether chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Locked *wink* sunken house off Jidamba 2", 3726 + treasure_index_offset), #(646, 53, 196) Zether Pouch chest
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Chest - Quizard challenge below N coast of Tall Tall Heights", 595 + treasure_index_offset), #(270, 29, -591) Treasure Finder chest
+
+ #NPCs
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Locked *wink* sunken house 2 off Jidamba Diamond", 2519 + npc_index_offset), #(639, 54, 182) Dust
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Locked *wink* sunken house 2 off Jidamba Gold", 2518 + npc_index_offset), #(648, 54, 180) Dust
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Burrow to burrow crab", 3409 + npc_index_offset), #(20, 53, 251) Crab 1
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Tall tall crab S of volcano", 3426 + npc_index_offset), #(76, 76, 178) Crab 2
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab in underwater cove south of volcano", 3427 + npc_index_offset), #(96, 60, 131) Crab 3
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Zigzaggedy crab NW of Tall Tall Heights", 3428 + npc_index_offset), #(142, 51, -611) Crab 4
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab strolling around the undersea block", 3429 + npc_index_offset), #(60, 53, -609) Crab 5
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab clinging to SW underwater volcano slope", 3430 + npc_index_offset), #(54, 62, 155) Crab 6
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Low low crab S of volcano", 3431 + npc_index_offset), #(128, 49, 196) Crab 7
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Serpentine crab S of crab hole", 3432 + npc_index_offset), #(239, 53, -613) for some reason crab 8 is on (Depths Fix) submap
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Patrolling crab S of crab hole", 3433 + npc_index_offset), #(254, 53, 215) Crab 9
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab living on the edge S of crab hole", 3434 + npc_index_offset), #(306, 53, 224) Crab 10
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab on a mission N of Tall Tall Heights", 3435 + npc_index_offset), #(288, 53, -620) Crab 11
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Middle of nowhere sandy speedster crab", 3436 + npc_index_offset), #(58, 52, 244) Crab 12
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Putt Putt Crab mows the lawn", 3437 + npc_index_offset), #(54, 52, 200) Crab 13
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - King of the middle of nowhere ocean crab", 3438 + npc_index_offset), #(52, 76, -616) Crab 14
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Crab scuttling SE of volcano", 3439 + npc_index_offset), #(207, 53, 152) Crab 15
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Undersea Crab People Crab Retirement Home south of Salmon Race start", 3424 + npc_index_offset, lambda state: state.has(UNDERSEA_CRAB, player, 15)), #(256, 63, 113)
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Fastest squid in the West", 3450 + npc_index_offset), #(-314, 64, -624) (swims in a fixed path; slightly slower than golden Quintar but faster than royal salmon) Z35_SpeedOcto
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Sunken shipwreck Gold off west coast of Sara Sara Beach 1", 2855 + npc_index_offset), #(-367, 53, -182) Dust
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Sunken shipwreck Gold off west coast of Sara Sara Beach 2", 2857 + npc_index_offset), #(-356, 55, -167) Ingot
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " NPC - Sunken shipwreck Gold off west coast of Sara Sara Beach 3", 2856 + npc_index_offset), #(-370, 53, -173) Ore
+
+ #Jade Cavern
+ #Treasure chests
+ LocationData(JADE_CAVERN_AP_REGION, JADE_CAVERN_DISPLAY_NAME + " Chest - Tell Archie to say hi to the Quizard", 3604 + treasure_index_offset), #(239, 99, -124) Jade Cavern map chest
+
+ #NPCs
+ #Todo NPCs CheckOrNot Job Masters: this guy trades you a thing for each job seal you've gotten from a job master for mastering that job
+ #LocationData(JADE_CAVERN, JADE_CAVERN_DISPLAY_NAME + " NPC - Jade Cavern Map chest", 3603 + npc_index_offset), #(255, 91, -90)
+
+ #Continental Tram
+ #Treasure chests
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Chest - Tickets please 1", 1844 + treasure_index_offset), #Continental Tram map chest
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Chest - End of the line", 3012 + treasure_index_offset), #Nomads Guard chest
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Chest - Tickets please 2", 1654 + treasure_index_offset), #Tram Key chest
+
+ #NPCs
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " NPC - Diamond hanging out with the conscript 1", 2895 + npc_index_offset), #Dust
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " NPC - Diamond hanging out with the conscript 2", 2894 + npc_index_offset), #Ingot
+
+ #Zones (End-Game)
+ #Ancient Labyrinth
+ #Treasure chests
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - Dungeon East sneaky hole in wall", 1274 + treasure_index_offset), #(-186, 125, -316) F2 Money chest
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - Dungeon West sneaky hole in wall", 2412 + treasure_index_offset), #(-190, 125, -316) F2 Archmage Vest chest
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B1 Searching for greener pastures", 1738 + treasure_index_offset), #(-209, 87, -311) F3 Vita Crown chest
+ LocationData(LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B2 North weird rebar hallway", 2924 + treasure_index_offset), #(-185, 63, -363) F4 Judgement chest
+ LocationData(LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B2 East weird rebar hallway", 2926 + treasure_index_offset), #(-162, 63, -336) F4 Zether Pouch chest
+ LocationData(LABYRINTH_CORE_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - B4 Tar pit platform", 3649 + treasure_index_offset), #(-185, 38, -301) F6 Ancient Labyrinth map chest
+ LocationData(LABYRINTH_CORE_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Chest - Dog-headed dogshit boss", 2591 + treasure_index_offset), #(-219, 27, -350) F7 Mirror Shield chest
+
+ #NPCs
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " NPC - Dungeon East sneaky hole in wall Diamond", 2881 + npc_index_offset), #(-186, 125, -300) F2 Ingot
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " NPC - Sneaky hole in wall Diamond in entry touchdown room", 2880 + npc_index_offset), #(-182, 126, -326) F2 Ore
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " NPC - B1 Thats right, Diamond goes in the bluish-white square hole", 2882 + npc_index_offset), #(-200, 98, -334) F3 Ingot
+
+ #The Sequoia
+ #Treasure chests
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Waterfall climb sneaky hollow", 2934 + treasure_index_offset), #(-286, 90, -539) Stealth Cape chest
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Balanced on bark", 2437 + treasure_index_offset), #(-250, 174, -512) Battle Band chest
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Back indoors then follow water channel outside", 2935 + treasure_index_offset), #(-296, 182, -533) Sange chest
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Waterfall climb sneaky eastern exit", 2884 + treasure_index_offset), #(-223, 118, -541) Zether Pouch chest
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Go out on a limb", 2887 + treasure_index_offset), #(-244, 168, -498) Z-Potion Pouch chest
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Back indoors by water channel", 2933 + treasure_index_offset), #(-282, 182, -528) Aphotic Edge chest
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Chest - Post-boss victory pedestal", 2451 + treasure_index_offset), #(-272, 241, -544) The Hand of Midas chest
+
+ #NPCs
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Low-hanging Diamond fruit", 2885 + npc_index_offset), #(-223, 160, -530) Dust
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Waterfall climb sneaky eastern exit Diamond", 2883 + npc_index_offset), #(-237, 117, -563) Ore
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Diamond glittering on a bough", 2886 + npc_index_offset), #(-311, 160, -540) Ore
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Post-boss victory Diamond 1", 2889 + npc_index_offset), #(-269, 240, -545) Dust
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Post-boss victory Diamond 2", 2890 + npc_index_offset), #(-268, 240, -547) Ingot
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " NPC - Post-boss victory Diamond 3", 2888 + npc_index_offset), #(-275, 240, -546) Ore
+
+ #The Depths
+ #Treasure chests
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Chest - Down among glowing blue seaweed between Poko Poko Desert & Jidamba 1", 2588 + treasure_index_offset), #(-358, 1, 18) Cerulean Book chest
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Chest - Down among glowing blue seaweed between Poko Poko Desert & Jidamba 2", 2714 + treasure_index_offset), #(-357, 2, 20) #Z-Potion chest
+
+ #NPCs
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond on blue rock 1", 2865 + npc_index_offset), #(692, 20, -618) Dust
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond by seaweed river 1", 2868 + npc_index_offset), #(823, 17, -595) Dust
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Floating rock Diamond SE of Volcano 1", 2891 + npc_index_offset), #(161, 20, 240) Dust
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Diamond dive beside sunken shipwreck 1", 1214 + npc_index_offset), #(-377, 20, -220) Ingot
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Diamond dive beside sunken shipwreck 2", 2859 + npc_index_offset), #(-373, 19, -261) Ingot
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond on blue rock 2", 2863 + npc_index_offset), #(784, 17, -611) Ingot
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of sunken town on blue rock", 2866 + npc_index_offset), #(629, 20, -615) Ingot
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Floating rock Diamond SE of Volcano 2", 2893 + npc_index_offset), #(180, 20, 255) Ingot
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Yellow flower Diamond W of Sara Sara Beach", 1213 + npc_index_offset), #(932, 19, -199) Ore
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Sneaky Diamond W of Sara Sara Beach", 2858 + npc_index_offset), #(924, 20, -235) Ore
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond by seaweed river 2", 2864 + npc_index_offset), #(760, 12, -612) Ore
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - S of Jidamba Diamond by seaweed river 3", 2867 + npc_index_offset), #(722, 20, -604) Ore
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Floating rock Diamond SE of Volcano 3", 2892 + npc_index_offset), #(189, 30, 235) Ore
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Follow barnacled meat branches for Diamond 1", 2861 + npc_index_offset), #(-308, 12, 132) Dust
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Follow barnacled meat branches for Diamond 2", 2862 + npc_index_offset), #(-303, 14, 183) Ingot
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " NPC - Follow barnacled meat branches for Diamond 3", 2860 + npc_index_offset), #(-359, 10, 162) Ore
+
+ #Castle Sequoia
+ #Treasure chests
+ #Map and Decapitator chests categorized in Castle Ramparts since they're in a locked room there requiring the Ramparts key
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Hop through keyhole over lava", 2515 + treasure_index_offset), #(422, 169, -406) (Skums) Zether Pouch chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Stared at by lava miniboss", 1465 + treasure_index_offset), #(427, 170, -441) (Skums) Z-Potion chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Enjoy riding lava shrooms", 1447 + treasure_index_offset), #(409, 169, -406) (Skums) Z-Potion Pouch chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Biiig bounce!", 1472 + treasure_index_offset), #(375, 153, -405) (Bounce) Beads of Defense chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce up!!", 2949 + treasure_index_offset), #(401, 151, -404) (Bounce) Ether chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce around!", 2948 + treasure_index_offset), #(401, 151, -424) (Bounce) Fenix Syrup chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce right!", 2945 + treasure_index_offset), #(434, 154, -441) (Bounce) Potion chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce up!", 2922 + treasure_index_offset), #(395, 155, -453) (Bounce) Protector chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Chilly uno in Chips Challenge of doom", 2927 + treasure_index_offset), #(387, 134, -431) (Ice) Kings Guard chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Chilly duo in Chips Challenge of doom", 1492 + treasure_index_offset), #(387, 134, -427) (Ice) Royal Guard chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - One fish", 2479 + treasure_index_offset), #(401, 119, -415) (Fish) Dream Hunter chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Two fish", 2509 + treasure_index_offset), #(388, 122, -445) (Fish) Nightingale chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Redfish bluefish", 2484 + treasure_index_offset), #(397, 119, -415) (Fish) Oily Sword chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Bounce for your life", 2510 + treasure_index_offset), #(364, 85, -424) (2D) Paladin Wand chest
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Chest - Throne snacks", 2505 + treasure_index_offset), #(401, 250, -478) (Throne) New World Stone chest
+
+ #NPCs
+ #NPCs CheckOrNot: Z58_StrandedShard ID 3785 (401, 183, -382); this gives you a Gaea Shard if you're stuck: no
+ #NPCs Blocker: i think this guy might only show up in the vanilla game's randomizer? checks if Z58_EleOn (Z58 is Castle Sequoia); Z58Progression_Gate ID 3824 (400, 250, -478)
+
+ #The New World
+ #Treasure chests
+ LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Chest - NW lavafall", 2930 + treasure_index_offset), #(-134, 8, 230) Lunar Mail chest
+ LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Chest - Desolate peninsula past bounce shrooms", 2931 + treasure_index_offset, lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)), #(-11, 12, -577) Mages Pike chest
+ LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Chest - Tiny shrooms keep shed", 1938 + treasure_index_offset), #(-85, 8, 142) The New World map chest
+ ]
+ return location_table
+
+
+def get_crystal_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
+ logic = CrystalProjectLogic(player, options)
+ location_table: List[LocationData] = [
+ # Zones (Beginner)
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + FENCER_JOB_CRYSTAL_LOCATION, 130 + crystal_index_offset),
+ LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + SHAMAN_JOB_CRYSTAL_LOCATION, 35 + crystal_index_offset),
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + SCHOLAR_JOB_CRYSTAL_LOCATION, 166 + crystal_index_offset),
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + AEGIS_JOB_CRYSTAL_LOCATION, 68 + crystal_index_offset),
+ # Zones (Advanced)
+ LocationData(BEATSMITH_DISCO_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + BEATSMITH_JOB_CRYSTAL_LOCATION, 1087 + crystal_index_offset),
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + HUNTER_JOB_CRYSTAL_LOCATION, 621 + crystal_index_offset),
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + CHEMIST_JOB_CRYSTAL_LOCATION, 970 + crystal_index_offset),
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + REAPER_JOB_CRYSTAL_LOCATION, 908 + crystal_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY)),
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + NINJA_JOB_CRYSTAL_LOCATION, 699 + crystal_index_offset),
+ LocationData(RIVER_CATS_EGO_AP_REGION, RIVER_CATS_EGO_AP_REGION + NOMAD_JOB_CRYSTAL_LOCATION, 630 + crystal_index_offset),
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + DERVISH_JOB_CRYSTAL_LOCATION, 1121 + crystal_index_offset),
+ # Zones (Expert)
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + SAMURAI_JOB_CRYSTAL_LOCATION, 1206 + crystal_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, THIRD_SKY_ARENA_FIGHT_LEVEL)),
+ # Can just swim or defeat the Undercity Masters; Blade Master: Ibek or Owl, Shadow Master: Horizontal or Fish, Duel Master: Ibek or Owl; to defeat all masters, you either need both ibek + quintar, owl, or fish
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + ASSASSIN_JOB_CRYSTAL_LOCATION, 1204 + crystal_index_offset, lambda state: (logic.has_horizontal_movement(state) and logic.has_vertical_movement(state)) or logic.has_glide(state) or logic.has_swimming(state)),
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + VALKYRIE_JOB_CRYSTAL_LOCATION, 1086 + crystal_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)),
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + SUMMONER_JOB_CRYSTAL_LOCATION, 1714 + crystal_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY, 3)),
+ # (404, 243, -386)
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + BEASTMASTER_JOB_CRYSTAL_LOCATION, 1370 + crystal_index_offset, lambda state: logic.has_glide(state)),
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + MIMIC_JOB_CRYSTAL_LOCATION, 3701 + crystal_index_offset, lambda state: logic.has_vertical_movement(state)),
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + WEAVER_JOB_CRYSTAL_LOCATION, 2403 + crystal_index_offset),
+ ]
+
+ return location_table
+
+def get_boss_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
+ logic = CrystalProjectLogic(player, options)
+ location_table: List[LocationData] = [
+ #IDs here are specifically the spark IDs or NPC IDs for where you go to fight them from Crystal Edit
+ #Summons
+ #Not included: Pinga (unused)
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon", 477 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, SHAKU_FIGHT_LEVEL)), #(118, 109, 10) Monster ID: 102
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Boss - Niltsi Summon", 1109 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, NILTSI_FIGHT_LEVEL)), #376, 178, -345 (Capital Sequoia (Maze) map) Monster ID: 93
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Boss - Guaba Summon", 1138 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, GUABA_FIGHT_LEVEL)), #-50, 91, -330 Monster ID: 94
+ LocationData(THE_UNDERCITY_AP_REGION, "Underpass Boss - Pah Summon", 1130 + boss_index_offset, lambda state: (logic.has_swimming(state) or logic.has_glide(state)) and state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, PAH_FIGHT_LEVEL)), #614, 91, -213 Monster ID: 97
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Boss - Tira Summon", 1132 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and (logic.has_vertical_movement(state) or logic.has_glide(state) or state.can_reach(QUINTAR_RESERVE_AP_REGION, player=player) or state.can_reach(GANYMEDE_SHRINE_AP_REGION, player=player)) and logic.is_area_in_level_range(state, TIRA_FIGHT_LEVEL)), #(720, 138, -278) Monster ID: 98
+ LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Boss - Ioske Summon", 1111 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, IOSKE_FIGHT_LEVEL)), #97, 126, -211 Monster ID: 92
+ LocationData(PAMOA_TREE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Boss - Pamoa Summon", 1136 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, PAMOA_FIGHT_LEVEL)), #498, 218, -412 Monster ID: 91
+ LocationData(JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Boss - Juses Summon", 1134 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, JUSES_FIGHT_LEVEL)), #(672, 124, 106) Monster ID: 99
+ #Mind's Delusion is part of the Coyote fight
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Boss - Coyote Summon", 1140 + boss_index_offset, lambda state: state.has(SUMMONER_JOB, player) and logic.is_area_in_level_range(state, COYOTE_FIGHT_LEVEL)), #(-60, 53, 202) Monster ID: 95
+
+ #Bosses
+ #Not included: Bone Drinker (unused), Rexosaur (too RNG), Astley/Chloe/Reid/Talon Final Fights, Buttermint (you monster)
+ LocationData(DELENDE_PEAK_AP_REGION, DELENDE_DISPLAY_NAME + " Boss - Troll", 153 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, TROLL_FIGHT_LEVEL)), #Monster ID: 8
+ LocationData(GRAN_AP_REGION, DELENDE_DISPLAY_NAME + " Boss - Gran...?", 183 + boss_index_offset), #Monster ID: 28 (min level 30)
+ LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Boss - Bone Thief", 175 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, BONE_THIEF_FIGHT_LEVEL)), #Monster ID: 12
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Boss - Guardian", 143 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, GUARDIAN_FIGHT_LEVEL)), #Monster ID: 23
+ LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Boss - Canal Beast", 138 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, CANAL_BEAST_FIGHT_LEVEL)), #Monster ID: 18
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Boss - Sepulchra", 167 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, SEPULCHRA_FIGHT_LEVEL)), #Monster ID: 27
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Boss - Knight", 128 + boss_index_offset, lambda state: logic.has_jobs(state, 3) and logic.is_area_in_level_range(state, KNIGHT_FIGHT_LEVEL)), #Monster ID: 212 (using Z7_CrystalCheckerNPC)
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Boss - Parasite", 333 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, PARASITE_FIGHT_LEVEL)), #Monster ID: 38
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Boss - Defeat L60 dummy and it shall appear...!", 3530 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, DUMMY_BOSS_FIGHT_LEVEL)), #Monster ID: 303
+ LocationData(MOAT_SHALLOWS_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Boss - Enami", 458 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, ENAMI_FIGHT_LEVEL)), #Monster ID: 49
+ LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Boss - Blood Slop", 758 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, BLOOD_SLOP_FIGHT_LEVEL)), #Monster ID: 4
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Boss - Fancy Quintar", 971 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, FANCY_QUINTAR_FIGHT_LEVEL)), #Monster ID: 68
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Boss - Warden", 907 + boss_index_offset, lambda state: logic.has_key(state, DARK_WING_KEY) and logic.is_area_in_level_range(state, WARDEN_FIGHT_LEVEL)), #Monster ID: 37
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Boss - Crag Demon", 1118 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, CRAG_DEMON_FIGHT_LEVEL)), #Monster ID: 217
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Boss - Kuromanto", 698 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, KUROMANTO_FIGHT_LEVEL)), #Monster ID: 63
+ LocationData(IBEK_CAVE_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Boss - Possessor", 1674 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, POSSESSOR_FIGHT_LEVEL)), #Monster ID: 221
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Boss - Final Sky Arena Fight: Arachlea", 1366 + boss_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, ARACHLEA_FIGHT_LEVEL)), #Monster ID: 252 (SkyArenaRegistrar)
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Boss - Blade Master", 1939 + boss_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, BLADE_MASTER_FIGHT_LEVEL)), #Monster ID: 145
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Boss - Shadow Master", 1940 + boss_index_offset, lambda state: (logic.has_swimming(state) or logic.has_horizontal_movement(state)) and logic.is_area_in_level_range(state, SHADOW_MASTER_FIGHT_LEVEL)), #Monster ID: 144
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Boss - Duel Master", 1941 + boss_index_offset, lambda state: (logic.has_vertical_movement(state) or logic.has_glide(state)) and logic.is_area_in_level_range(state, DUEL_MASTER_FIGHT_LEVEL)), #Monster ID: 146
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Boss - Ancient Sword", 821 + boss_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 2) and logic.is_area_in_level_range(state, ANCIENT_SWORD_FIGHT_LEVEL)), #Monster ID: 59
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Boss - Iguanadon & Iguanadin", 862 + boss_index_offset, lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY) and logic.is_area_in_level_range(state, IGUANADON_AND_DIN_FIGHT_LEVEL)), #Monster ID: 78 and 100
+ LocationData(EASTERN_CHASM_AP_REGION, EASTERN_CHASM_DISPLAY_NAME + " Boss - Undergrowth", 3476 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, UNDERGROWTH_FIGHT_LEVEL)), #Monster ID: 293
+ LocationData(SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Boss - Hermetic", 3637 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, HERMETIC_FIGHT_LEVEL)), #Monster ID: 309
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Boss - The Owlbear", 2104 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, OWLBEAR_FIGHT_LEVEL)), #Monster ID: 143
+ LocationData(SLIP_GLIDE_RIDE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Boss - Red Guardian", 1713 + boss_index_offset, lambda state: logic.has_key(state, RED_DOOR_KEY, 3) and logic.is_area_in_level_range(state, RED_GUARDIAN_FIGHT_LEVEL)), #Monster ID: 224
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Boss - Rampart Demon", 1373 + boss_index_offset, lambda state: logic.has_glide(state) and logic.is_area_in_level_range(state, RAMPART_DEMON_FIGHT_LEVEL)), #Monster ID: 222
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Boss - Conscript", 1621 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, CONSCRIPT_FIGHT_LEVEL)), #Monster ID: 242
+ LocationData(LABYRINTH_CORE_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Boss - Anubis", 2473 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, ANUBIS_FIGHT_LEVEL)), #Monster ID: 117
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Boss - Spirit Cage", 2453 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, SPIRIT_CAGE_FIGHT_LEVEL)), #Monster ID: 192
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Devourer", 1265 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, DEVOURER_FIGHT_LEVEL)), #Monster ID: 171
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Old One", 206 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, OLD_ONE_FIGHT_LEVEL)), #Monster ID: 170
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Enforcer", 1128 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, ENFORCER_FIGHT_LEVEL)), #Monster ID: 172
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - The Peacekeeper", 2579 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, PEACEKEEPER_FIGHT_LEVEL)), #Monster ID: 169
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Boss - With STEM WARD in hand, offer 4 Deity Eyes to battle Gabriel", 2580 + boss_index_offset, lambda state: state.has(DEITY_EYE, player, 4) and state.has(STEM_WARD, player) and logic.is_area_in_level_range(state, GABRIEL_FIGHT_LEVEL)), #Monster ID: 241
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Boss - Parasite X", 1457 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, PARASITE_X_FIGHT_LEVEL)), #Monster ID: 198 (Castle Sequoia (Skums) Z58_TrapdoorGuard)
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Boss - Neo Warden", 1477 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, NEO_WARDEN_FIGHT_LEVEL)), #Monster ID: 200 (Castle Sequoia (Bounce) Z58_F2TrapdoorGuard)
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Boss - Akamanto", 1503 + boss_index_offset, lambda state: logic.is_area_in_level_range(state, AKAMANTO_FIGHT_LEVEL)), #Monster ID: 201 (Castle Sequoia (Ice) Z58_F3TrapdoorGuard)
+ LocationData(THE_OLD_WORLD_AP_REGION, THE_OLD_WORLD_DISPLAY_NAME + " Boss - Periculum", 3650 + boss_index_offset), #Monster ID: 268 (min level 70)
+ ]
+ return location_table
+
+def get_shop_locations(player: int, options: CrystalProjectOptions | None) -> List[LocationData]:
+ logic = CrystalProjectLogic(player, options)
+
+ location_table: List[LocationData] = [
+ #Zones (Beginner)
+ #Spawning Meadows
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 1", 10013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 2", 20013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 3", 30013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 4", 40013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 5", 50013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 6", 60013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 7", 70013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 8", 80013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 9", 90013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 10", 100013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 11", 110013 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Knick Knacks 12", 120013 + shop_index_offset),
+
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Stew 1", 10014 + shop_index_offset),
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Shop - Nans Stew 2", 20014 + shop_index_offset),
+
+ #Delende
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Weapon Merchant 1", 10052 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Weapon Merchant 2", 20052 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Weapon Merchant 3", 30052 + shop_index_offset),
+
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 1", 10063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 2", 20063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 3", 30063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 4", 40063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 5", 50063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 6", 60063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 7", 70063 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Armor Merchant 8", 80063 + shop_index_offset),
+
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 1", 10199 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 2", 20199 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 3", 30199 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 4", 40199 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 5", 50199 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Fish Hatchery Fish Merchant 6", 60199 + shop_index_offset),
+
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 1", 10115 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 2", 20115 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 3", 30115 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 4", 40115 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Weapons Merchant camped in front of Pale Grotto 5", 50115 + shop_index_offset),
+
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 1", 10446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 2", 20446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 3", 30446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 4", 40446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 5", 50446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 6", 60446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 7", 70446 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Armor Merchant camped in front of Pale Grotto 8", 80446 + shop_index_offset),
+
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Item Merchant camped in front of Pale Grotto 1", 10266 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Item Merchant camped in front of Pale Grotto 2", 20266 + shop_index_offset),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Shop - Item Merchant camped in front of Pale Grotto 3", 30266 + shop_index_offset),
+
+ #Seaside Cliffs
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 1", 10116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 2", 20116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 3", 30116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 4", 40116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 5", 50116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 6", 60116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 7", 70116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 8", 80116 + shop_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Shop - Camp Merchant 9", 90116 + shop_index_offset),
+
+ #Mercury Shrine
+ LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Shop - Attendant 1", 10132 + shop_index_offset),
+ LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Shop - Attendant 2", 20132 + shop_index_offset),
+ LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Shop - Attendant 3", 30132 + shop_index_offset),
+
+ #Proving Meadows
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 1", 10253 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 2", 20253 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 3", 30253 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 4", 40253 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 5", 50253 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Item Merchant 6", 60253 + shop_index_offset),
+
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 1", 10133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 2", 20133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 3", 30133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 4", 40133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 5", 50133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 6", 60133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 7", 70133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 8", 80133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 9", 90133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 10", 100133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 11", 110133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 12", 120133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 13", 130133 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Armor Merchant 14", 140133 + shop_index_offset),
+
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 1", 10117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 2", 20117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 3", 30117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 4", 40117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 5", 50117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 6", 60117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 7", 70117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 8", 80117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 9", 90117 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Weapon Merchant 10", 100117 + shop_index_offset),
+
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 1", 10134 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 2", 20134 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 3", 30134 + shop_index_offset),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Shop - Accessories Merchant 4", 40134 + shop_index_offset),
+
+ #Zones (Advanced)
+ #Capital Sequoia
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Gaea Shrine Shop - Attendant 1", 10379 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, "Gaea Shrine Shop - Attendant 2", 20379 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Candy Merchant 1", 10575 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Candy Merchant 2", 20575 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 1", 11158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 2", 21158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 3", 31158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 4", 41158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 5", 51158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 6", 61158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 7", 71158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 8", 81158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 9", 91158 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Map Seller 10", 101158 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 1", 10599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 2", 20599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 3", 30599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Martial Weapons 4", 40599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 1", 50599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 2", 60599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 3", 70599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 4", 80599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 5", 90599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Martial Weapons 6", 100599 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 1", 10600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 2", 20600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 3", 30600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Exotic Weapons 4", 40600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 1", 50600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 2", 60600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 3", 70600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Exotic Weapons 4", 80600 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Heavy Armor 1", 10601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Heavy Armor 2", 20601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Heavy Armor 3", 30601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Heavy Armor 1", 40601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Heavy Armor 2", 50601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Heavy Armor 3", 60601 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Medium Armor 1", 10602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Medium Armor 2", 20602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Medium Armor 1", 30602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Medium Armor 2", 40602 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Magic Weapons 1", 10603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Magic Weapons 2", 20603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Magic Weapons 3", 30603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Magic Weapons 1", 40603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Magic Weapons 2", 50603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Magic Weapons 3", 60603 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Light Armor 1", 10604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Luxury Shop Light Armor 2", 20604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Light Armor 1", 30604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Upgraded Luxury Shop Light Armor 2", 40604 + shop_index_offset, lambda state: state.has(PROGRESSIVE_LUXURY_PASS, player, 2)),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 1", 10500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 2", 20500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 3", 30500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 4", 40500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 5", 50500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 6", 60500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 7", 70500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 8", 80500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 9", 90500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 10", 100500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 11", 110500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 12", 120500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 13", 130500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 14", 140500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 15", 150500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 16", 160500 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Martial Weapons R Us 17", 170500 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 1", 10501 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 2", 20501 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 3", 30501 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 4", 40501 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 5", 50501 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Exotic Weapons R Us 6", 60501 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Milk Man", 10579 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 1", 10416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 2", 20416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 3", 30416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 4", 40416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 5", 50416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 6", 60416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 7", 70416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 8", 80416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 9", 90416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 10", 100416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 11", 110416 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Heavy Armor 4 All 12", 120416 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 1", 10417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 2", 20417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 3", 30417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 4", 40417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 5", 50417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 6", 60417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 7", 70417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 8", 80417 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Medium Armor 4 All 9", 90417 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 1", 10455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 2", 20455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 3", 30455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 4", 40455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 5", 50455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 6", 60455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 7", 70455 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Light Armor 8", 80455 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 1", 10499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 2", 20499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 3", 30499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 4", 40499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 5", 50499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 6", 60499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 7", 70499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 8", 80499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 9", 90499 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Magic and Magic Things Weapons 10", 100499 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 1", 10398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 2", 20398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 3", 30398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 4", 40398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 5", 50398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 6", 60398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 7", 70398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 8", 80398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 9", 90398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 10", 100398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 11", 110398 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Accessory Merchant 12", 120398 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 1", 10456 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 2", 20456 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 3", 30456 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 4", 40456 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 5", 50456 + shop_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Item Merchant 6", 60456 + shop_index_offset),
+
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Shop - Old Nans Stew", 10423 + shop_index_offset),
+
+ #Salmon River
+ #Poseidon Shrine Proper
+ LocationData(POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION + " Shop - Attendant 1", 10631 + shop_index_offset),
+ LocationData(POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION + " Shop - Attendant 2", 20631 + shop_index_offset),
+ LocationData(POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION + " Shop - Attendant 3", 30631 + shop_index_offset),
+
+ #Sara Sara Bazaar
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Old Nans Stew Subsidiary", 10957 + shop_index_offset),
+
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 1", 11386 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 2", 21386 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 3", 31386 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 4", 41386 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 5", 51386 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Accessory Merchant 6", 61386 + shop_index_offset),
+
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 1", 11193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 2", 21193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 3", 31193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 4", 41193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 5", 51193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 6", 61193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 7", 71193 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Item Merchant 8", 81193 + shop_index_offset),
+
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Fish Merchant 1", 10942 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Fish Merchant 2", 20942 + shop_index_offset),
+
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 1", 11603 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 2", 21603 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 3", 31603 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 4", 41603 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 5", 51603 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 6", 61603 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Armor Merchant 7", 71603 + shop_index_offset),
+
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 1", 11604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 2", 21604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 3", 31604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 4", 41604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 5", 51604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 6", 61604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 7", 71604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 8", 81604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 9", 91604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 10", 101604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 11", 111604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 12", 121604 + shop_index_offset),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Shop - Weapon Merchant 13", 131604 + shop_index_offset),
+
+ #Zones (Expert)
+ #Shoudu Province
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 1", 10951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 2", 20951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 3", 30951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 4", 40951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 5", 50951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 6", 60951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 7", 70951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 8", 80951 + shop_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Item Merchant 9", 90951 + shop_index_offset),
+
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - No Shoudu Stew for you 1", 11199 + shop_index_offset),
+
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 1", 11614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 2", 21614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 3", 31614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 4", 41614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 5", 51614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Accessory Merchant 6", 61614 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 1", 11535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 2", 21535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 3", 31535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 4", 41535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 5", 51535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 6", 61535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 7", 71535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 8", 81535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 9", 91535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Armor Merchant 10", 101535 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 1", 11544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 2", 21544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 3", 31544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 4", 41544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 5", 51544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 6", 61544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 7", 71544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 8", 81544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 9", 91544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 10", 101544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 11", 111544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 12", 121544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 13", 131544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 14", 141544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 15", 151544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Shop - Weapon Merchant 16", 161544 + shop_index_offset, lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)),
+
+ #Ganymede Shrine
+ LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Shop - Attendant 1", 11574 + shop_index_offset),
+ LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Shop - Attendant 2", 21574 + shop_index_offset),
+ LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Shop - Attendant 3", 31574 + shop_index_offset),
+
+ #Quintar Reserve
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 1", 10470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 2", 20470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 3", 30470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 4", 40470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Shop - Babel to this Quintar 5", 50470 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+
+ #Dione Shrine
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Attendant 1", 12253 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Attendant 2", 22253 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 1", 12227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 2", 22227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 3", 32227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 4", 42227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 5", 52227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 6", 62227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 7", 72227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 8", 82227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Shop - Quintar Raising Supplies 9", 92227 + shop_index_offset, lambda state: state.has(BABEL_QUINTAR, player)),
+
+ #Tall Tall Heights
+ LocationData(UPPER_ICE_LAKES_AP_REGION, "Triton Shrine Shop - Attendant 1", 11165 + shop_index_offset),
+ LocationData(UPPER_ICE_LAKES_AP_REGION, "Triton Shrine Shop - Attendant 2", 21165 + shop_index_offset),
+
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 1", 12746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 2", 22746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 3", 32746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 4", 42746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 5", 52746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 6", 62746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 7", 72746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 8", 82746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 9", 92746 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Armor Merchant 10", 102746 + shop_index_offset),
+
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 1", 10540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 2", 20540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 3", 30540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 4", 40540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 5", 50540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 6", 60540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 7", 70540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 8", 80540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 9", 90540 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Weapon Merchant 10", 100540 + shop_index_offset),
+
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 1", 12918 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 2", 22918 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 3", 32918 + shop_index_offset),
+ LocationData(SOUVENIR_SHOP_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Shop - Souvenir Merchant 4", 42918 + shop_index_offset),
+
+ #Jidamba Tangle
+ LocationData(EUROPA_SHRINE_AP_REGION, EUROPA_SHRINE_AP_REGION + " Shop - Attendant 1", 11163 + shop_index_offset),
+ LocationData(EUROPA_SHRINE_AP_REGION, EUROPA_SHRINE_AP_REGION + " Shop - Attendant 2", 21163 + shop_index_offset),
+
+ #Neptune Shrine
+ LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Shop - Attendant 1", 13164 + shop_index_offset),
+ LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Shop - Attendant 2", 23164 + shop_index_offset),
+ LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Shop - Attendant 3", 33164 + shop_index_offset),
+
+ #Zones (End-Game)
+ #The New World
+ LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Shrine Shop - Attendant 1", 11877 + shop_index_offset),
+ LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Shrine Shop - Attendant 2", 21877 + shop_index_offset),
+ ]
+ return location_table
+
+def get_region_completion_locations(player: int, options: CrystalProjectOptions) -> List[LocationData]:
+ logic = CrystalProjectLogic(player, options)
+ location_table: List[LocationData] = [
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion", 6001 + regionsanity_index_offset, regionsanity=True),
+ LocationData(DELENDE_PLAINS_AP_REGION, DELENDE_DISPLAY_NAME + " Region Completion", 6002 + regionsanity_index_offset, regionsanity=True),
+ LocationData(MERCURY_SHRINE_AP_REGION, MERCURY_SHRINE_DISPLAY_NAME + " Region Completion", 6007 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SOILED_DENLENDE_AP_REGION, SOILED_DEN_DISPLAY_NAME + " Region Completion", 6003 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_PALE_GROTTO_AP_REGION, THE_PALE_GROTTO_DISPLAY_NAME + " Region Completion", 6004 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " Region Completion", 6005 + regionsanity_index_offset, regionsanity=True),
+ LocationData(DRAFT_SHAFT_CONDUIT_AP_REGION, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME + " Region Completion", 6006 + regionsanity_index_offset, regionsanity=True),
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " Region Completion", 6008 + regionsanity_index_offset, regionsanity=True),
+ LocationData(PROVING_MEADOWS_AP_REGION, PROVING_MEADOWS_DISPLAY_NAME + " Region Completion", 6009 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SKUMPARADISE_AP_REGION, SKUMPARADISE_DISPLAY_NAME + " Region Completion", 6010 + regionsanity_index_offset, regionsanity=True),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " Region Completion", 6011 + regionsanity_index_offset, regionsanity=True),
+ LocationData(JOJO_SEWERS_AP_REGION, JOJO_SEWERS_DISPLAY_NAME + " Region Completion", 6012 + regionsanity_index_offset, regionsanity=True),
+ LocationData(BOOMER_SOCIETY_AP_REGION, BOOMER_SOCIETY_DISPLAY_NAME + " Region Completion", 6013 + regionsanity_index_offset, regionsanity=True),
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " Region Completion", 6014 + regionsanity_index_offset, regionsanity=True),
+ LocationData(QUINTAR_NEST_AP_REGION, QUINTAR_NEST_DISPLAY_NAME + " Region Completion", 6015 + regionsanity_index_offset, regionsanity=True),
+ LocationData(QUINTAR_SANCTUM_AP_REGION, QUINTAR_SANCTUM_DISPLAY_NAME + " Region Completion", 6016 + regionsanity_index_offset, regionsanity=True),
+ LocationData(CAPITAL_JAIL_AP_REGION, CAPITAL_JAIL_DISPLAY_NAME + " Region Completion", 6017 + regionsanity_index_offset, regionsanity=True),
+ LocationData(PIPELINE_SOUTH_AP_REGION, CAPITAL_PIPELINE_DISPLAY_NAME + " Region Completion", 6018 + regionsanity_index_offset, regionsanity=True),
+ LocationData(COBBLESTONE_CRAG_AP_REGION, COBBLESTONE_CRAG_DISPLAY_NAME + " Region Completion", 6019 + regionsanity_index_offset, regionsanity=True),
+ LocationData(OKIMOTO_NS_AP_REGION, OKIMOTO_NS_DISPLAY_NAME + " Region Completion", 6020 + regionsanity_index_offset, regionsanity=True),
+ LocationData(GREENSHIRE_REPRISE_AP_REGION, GREENSHIRE_REPRISE_DISPLAY_NAME + " Region Completion", 6021 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SALMON_PASS_EAST_AP_REGION, SALMON_PASS_DISPLAY_NAME + " Region Completion", 6022 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SALMON_RIVER_AP_REGION, SALMON_RIVER_DISPLAY_NAME + " Region Completion", 6023 + regionsanity_index_offset, regionsanity=True),
+ LocationData(POKO_POKO_DESERT_AP_REGION, POKO_POKO_DESERT_DISPLAY_NAME + " Region Completion", 6024 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SARA_SARA_BAZAAR_AP_REGION, SARA_SARA_BAZAAR_DISPLAY_NAME + " Region Completion", 6025 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_EAST_DISPLAY_NAME + " Region Completion", 6026 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " Region Completion", 6027 + regionsanity_index_offset, regionsanity=True),
+ LocationData(ANCIENT_RESERVOIR_AP_REGION, ANCIENT_RESERVOIR_DISPLAY_NAME + " Region Completion", 6028 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SALMON_BAY_AP_REGION, SALMON_BAY_DISPLAY_NAME + " Region Completion", 6030 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_OPEN_SEA_AP_REGION, THE_OPEN_SEA_DISPLAY_NAME + " Region Completion", 6031 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SHOUDU_WATERFRONT_AP_REGION, SHOUDU_WATERFRONT_DISPLAY_NAME + " Region Completion", 6032 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " Region Completion", 6033 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_UNDERCITY_AP_REGION, THE_UNDERCITY_DISPLAY_NAME + " Region Completion", 6034 + regionsanity_index_offset, regionsanity=True),
+ LocationData(GANYMEDE_SHRINE_AP_REGION, GANYMEDE_SHRINE_DISPLAY_NAME + " Region Completion", 6035 + regionsanity_index_offset, regionsanity=True),
+ LocationData(BEAURIOR_VOLCANO_AP_REGION, BEAURIOR_VOLCANO_DISPLAY_NAME + " Region Completion", 6036 + regionsanity_index_offset, regionsanity=True),
+ LocationData(BEAURIOR_ROCK_AP_REGION, BEAURIOR_ROCK_DISPLAY_NAME + " Region Completion", 6037 + regionsanity_index_offset, regionsanity=True),
+ LocationData(LAKE_DELENDE_AP_REGION, LAKE_DELENDE_DISPLAY_NAME + " Region Completion", 6038 + regionsanity_index_offset, regionsanity=True),
+ LocationData(QUINTAR_RESERVE_AP_REGION, QUINTAR_RESERVE_DISPLAY_NAME + " Region Completion", 6039 + regionsanity_index_offset, regionsanity=True),
+ LocationData(DIONE_SHRINE_AP_REGION, DIONE_SHRINE_DISPLAY_NAME + " Region Completion", 6040 + regionsanity_index_offset, regionsanity=True),
+ LocationData(QUINTAR_MAUSOLEUM_AP_REGION, QUINTAR_MAUSOLEUM_DISPLAY_NAME + " Region Completion", 6041 + regionsanity_index_offset, regionsanity=True),
+ LocationData(EASTERN_CHASM_AP_REGION, EASTERN_CHASM_DISPLAY_NAME + " Region Completion", 6042 + regionsanity_index_offset, regionsanity=True),
+ LocationData(LOWER_ICE_LAKES_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " Region Completion", 6043 + regionsanity_index_offset, regionsanity=True),
+ LocationData(UPPER_NORTHERN_CAVE_AP_REGION, NORTHERN_CAVE_DISPLAY_NAME + " Region Completion", 6044 + regionsanity_index_offset, regionsanity=True),
+ LocationData(LANDS_END_AP_REGION, LANDS_END_DISPLAY_NAME + " Region Completion", 6045 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, SLIP_GLIDE_RIDE_DISPLAY_NAME + " Region Completion", 6046 + regionsanity_index_offset, regionsanity=True),
+ LocationData(SEQUOIA_ATHENAEUM_AP_REGION, SEQUOIA_ATHENAEUM_DISPLAY_NAME + " Region Completion", 6047 + regionsanity_index_offset, regionsanity=True),
+ LocationData(NORTHERN_STRETCH_RACE_START_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " Region Completion", 6048 + regionsanity_index_offset, regionsanity=True),
+ LocationData(PEAK_RAMPARTS_AP_REGION, CASTLE_RAMPARTS_DISPLAY_NAME + " Region Completion", 6049 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " Region Completion", 6050 + regionsanity_index_offset, regionsanity=True),
+ LocationData(FLYERS_CRAG_AP_REGION, FLYERS_CRAG_DISPLAY_NAME + " Region Completion", 6051 + regionsanity_index_offset, regionsanity=True),
+ LocationData(EUROPA_SHRINE_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " Region Completion", 6052 + regionsanity_index_offset, regionsanity=True),
+ LocationData(JIDAMBA_EACLANEYA_AP_REGION, JIDAMBA_EACLANEYA_DISPLAY_NAME + " Region Completion", 6053 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_DEEP_SEA_AP_REGION, THE_DEEP_SEA_DISPLAY_NAME + " Region Completion", 6054 + regionsanity_index_offset, regionsanity=True),
+ LocationData(NEPTUNE_SHRINE_AP_REGION, NEPTUNE_SHRINE_DISPLAY_NAME + " Region Completion", 6055 + regionsanity_index_offset, regionsanity=True),
+ LocationData(JADE_CAVERN_AP_REGION, JADE_CAVERN_DISPLAY_NAME + " Region Completion", 6056 + regionsanity_index_offset, regionsanity=True),
+ LocationData(CONTINENTAL_TRAM_AP_REGION, CONTINENTAL_TRAM_DISPLAY_NAME + " Region Completion", 6057 + regionsanity_index_offset, regionsanity=True),
+ LocationData(ANCIENT_LABYRINTH_AP_REGION, ANCIENT_LABYRINTH_DISPLAY_NAME + " Region Completion", 6058 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_SEQUOIA_AP_REGION, THE_SEQUOIA_DISPLAY_NAME + " Region Completion", 6059 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_DEPTHS_AP_REGION, THE_DEPTHS_DISPLAY_NAME + " Region Completion", 6060 + regionsanity_index_offset, regionsanity=True),
+ LocationData(CASTLE_SEQUOIA_AP_REGION, CASTLE_SEQUOIA_DISPLAY_NAME + " Region Completion", 6061 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_NEW_WORLD_AP_REGION, THE_NEW_WORLD_DISPLAY_NAME + " Region Completion", 6063 + regionsanity_index_offset, regionsanity=True),
+ LocationData(THE_OLD_WORLD_AP_REGION, THE_OLD_WORLD_DISPLAY_NAME + " Region Completion", 6064 + regionsanity_index_offset, regionsanity=True),
+ ]
+ return location_table
+
+def get_location_names_per_category() -> Dict[str, Set[str]]:
+ categories: Dict[str, Set[str]] = {}
+
+ for location in get_crystal_locations(-1, None):
+ categories.setdefault("Crystals", set()).add(location.name)
+
+ for location in get_shop_locations(-1, None):
+ categories.setdefault("Shops", set()).add(location.name)
+
+ for location in get_boss_locations(-1, None):
+ categories.setdefault("Bosses", set()).add(location.name)
+
+ for location in get_region_completion_locations(-1, None):
+ categories.setdefault("Region Completions", set()).add(location.name)
+
return categories
\ No newline at end of file
diff --git a/worlds/crystal_project/mod_helper.py b/worlds/crystal_project/mod_helper.py
index 549f44cbe409..8238bf5795ab 100644
--- a/worlds/crystal_project/mod_helper.py
+++ b/worlds/crystal_project/mod_helper.py
@@ -1,747 +1,747 @@
-import logging
-import os.path
-from os import listdir, getcwd
-from os.path import isfile, join
-from typing import Optional, Callable, TYPE_CHECKING
-from BaseClasses import Item, ItemClassification, CollectionState
-from typing import List, NamedTuple
-from .options import CrystalProjectOptions
-from .items import item_table, equipment_index_offset, item_index_offset, job_index_offset
-from .locations import LocationData, get_treasure_and_npc_locations, get_shop_locations, get_boss_locations, npc_index_offset, treasure_index_offset, crystal_index_offset, \
- boss_index_offset, shop_index_offset, get_crystal_locations
-from .unused_locations import get_unused_locations
-from .constants.biomes import get_display_region_by_id
-from .constants.display_regions import *
-from .constants.keys import *
-from .constants.key_items import *
-from .rules import CrystalProjectLogic
-import json
-
-if TYPE_CHECKING:
- from . import CrystalProjectWorld
-
-MAX_SUPPORTED_EDITOR_VERSION: int = 30
-
-class ModDataModel(object):
- def __init__(self, json_data):
- self.ID = None
- self.Title = None
- self.EditorVersion = None
- self.System = None
- self.Equipment = None
- self.Items = None
- self.Jobs = None
- self.Entities = None
- self.Sparks = None
- self.Troops = None
- self.Monsters = None
- self.__dict__ = json.loads(json_data)
-
-class ModLocationData(NamedTuple):
- display_region: str
- name: str
- code: int
- offsetless_code: int
- coordinates: str
- biomeId: int
- rule_condition: str | None
-
-class ModIncrementedIdData(NamedTuple):
- original_id: int
- new_id: int
- mod_guid: str
-
-class IdsExcludedFromRandomization(NamedTuple):
- excluded_equipment_ids : List[int]
- excluded_item_ids : List[int]
- excluded_job_ids : List[int]
-
-class ModInfoModel(NamedTuple):
- mod_id: str
- mod_name: str
- load_order: int
- data_model: ModDataModel
- shifted_equipment_ids: List[ModIncrementedIdData]
- shifted_item_ids: List[ModIncrementedIdData]
- shifted_job_ids: List[ModIncrementedIdData]
- shifted_entity_ids: List[ModIncrementedIdData]
- shifted_spark_ids: List[ModIncrementedIdData]
- boss_troop_ids: List[int]
- excluded_ids: IdsExcludedFromRandomization
-
-def get_mod_info() -> List[ModInfoModel]:
- data: List[ModInfoModel] = []
- file_directory = get_mod_directory()
-
- if not os.path.isdir(file_directory):
- return data
-
- only_files = [f for f in listdir(file_directory) if
- isfile(join(file_directory, f))]
-
- equipment_ids_in_use: List[int] = [591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610]
- item_ids_in_use: List[int] = [229, 230, 231, 232]
- job_ids_in_use: List[int] = []
- entity_ids_in_use: List[int] = [5000, 5001, 5002, 5003, 5004]
- spark_ids_in_use: List[int] = []
- order_loaded = 1
-
- for file_name in only_files:
- file = (open(join(file_directory, file_name)))
- file_text = file.read()
- file_data = ModDataModel(file_text)
-
- if file_data.EditorVersion <= MAX_SUPPORTED_EDITOR_VERSION:
- excluded_ids = get_excluded_ids(file_data)
-
- shifted_equipment_ids: List[ModIncrementedIdData] = []
- for item in file_data.Equipment:
- item_id = item['ID']
-
- #Biggest vanilla ID is 590, we only want non-vanilla items
- if item_id > 590:
- next_id = get_next_mod_id(item_id, equipment_ids_in_use)
- shifted_equipment_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
- equipment_ids_in_use.append(next_id)
-
- shifted_item_ids: List[ModIncrementedIdData] = []
- for item in file_data.Items:
- item_id = item['ID']
-
- #Biggest vanilla ID is 228
- if item_id > 228:
- next_id = get_next_mod_id(item_id, item_ids_in_use)
- shifted_item_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
- item_ids_in_use.append(next_id)
-
- shifted_job_ids: List[ModIncrementedIdData] = []
- for item in file_data.Jobs:
- item_id = item['ID']
-
- #Biggest vanilla ID is 23
- if item_id > 23:
- next_id = get_next_mod_id(item_id, job_ids_in_use)
- shifted_job_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
- job_ids_in_use.append(next_id)
-
- shifted_entity_ids: List[ModIncrementedIdData] = []
- for item in file_data.Entities:
- item_id = item['ID']
-
- #Biggest vanilla ID is 4999
- if item_id > 4999:
- next_id = get_next_mod_id(item_id, entity_ids_in_use)
- shifted_entity_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
- entity_ids_in_use.append(next_id)
-
- shifted_spark_ids: List[ModIncrementedIdData] = []
- for item in file_data.Sparks:
- item_id = item['ID']
-
- #Biggest vanilla ID is 224
- if item_id > 224:
- next_id = get_next_mod_id(item_id, spark_ids_in_use)
- shifted_spark_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
- spark_ids_in_use.append(next_id)
-
- boss_monster_ids: List[int] = []
- boss_troop_ids: List[int] = []
-
- for monster in file_data.Monsters:
- if monster['IsBoss']:
- boss_monster_ids.append(monster['ID'])
-
- for troop in file_data.Troops:
- for monster in troop['Members']:
- if monster['MonsterID'] in boss_monster_ids:
- boss_troop_ids.append(troop['ID'])
-
- data.append(ModInfoModel(file_data.ID, file_data.Title, order_loaded, file_data, shifted_equipment_ids, shifted_item_ids, shifted_job_ids, shifted_entity_ids, shifted_spark_ids, boss_troop_ids, excluded_ids))
- order_loaded = order_loaded + 1
- else:
- message = f"Mod {file_data.Title} was skipped because the editor version was {file_data.EditorVersion}. Archipelago currently only supports mods with editor version {MAX_SUPPORTED_EDITOR_VERSION} or lower."
- logging.getLogger().info(message)
-
- file.close()
-
- return data
-
-def get_modded_items(mod_info: List[ModInfoModel]) -> List[Item]:
- empty_player_value = -1
- items: List[Item] = []
-
- for mod in mod_info:
- for item in mod.data_model.Equipment:
- item_id = item['ID']
- excluded = any(item_id == excluded_id for excluded_id in mod.excluded_ids.excluded_equipment_ids)
-
- if excluded:
- continue
-
- new_id = item_id
- for incremented_id in mod.shifted_equipment_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- offset_id = new_id + equipment_index_offset
- item_in_pool = any(offset_id == data.code for name, data in item_table.items())
- name = 'Equipment - ' + item['Name'] + ' - ' + str(offset_id)
-
- if not item_in_pool:
- mod_item = Item(name, ItemClassification.useful, offset_id, empty_player_value)
- items.append(mod_item)
-
- for item in mod.data_model.Items:
- item_id = item['ID']
- excluded = any(item_id == excluded_id for excluded_id in mod.excluded_ids.excluded_item_ids)
-
- if excluded:
- continue
-
- new_id = item_id
- for incremented_id in mod.shifted_item_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- offset_id = new_id + item_index_offset
- item_in_pool = any(offset_id == data.code for name, data in item_table.items())
- name = 'Item - ' + item['Name'] + ' - ' + str(offset_id)
-
- if not item_in_pool:
- mod_item = Item(name, ItemClassification.filler, offset_id, empty_player_value)
- items.append(mod_item)
-
- for item in mod.data_model.Jobs:
- item_id = item['ID']
- excluded = any(item_id == excluded_id for excluded_id in mod.excluded_ids.excluded_job_ids)
-
- if excluded:
- continue
-
- new_id = item_id
- for incremented_id in mod.shifted_job_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- offset_id = new_id + job_index_offset
- item_in_pool = any(offset_id == data.code for name, data in item_table.items())
-
- is_unselectable = False
- if 'IsUnselectableJob' in item and 'IsUnselectableSubJob' in item:
- is_unselectable = item['IsUnselectableJob'] and item['IsUnselectableSubJob']
- name = 'Job - ' + item['Name'] + ' - ' + str(offset_id)
-
- if not item_in_pool and not is_unselectable:
- mod_item = Item(name, ItemClassification.progression, offset_id, empty_player_value)
- items.append(mod_item)
-
- return items
-
-def update_item_classification(item: Item, rule_condition, world: "CrystalProjectWorld") -> None:
- for condition in rule_condition:
- if condition is not None:
- loot_type = condition['Data']['LootType']
- loot_id = condition['Data']['LootValue']
- else:
- continue
-
- if loot_type == 1:
- archipelago_loot_id = loot_id + item_index_offset
- elif loot_type == 2:
- archipelago_loot_id = loot_id + equipment_index_offset
- else:
- continue
-
- if archipelago_loot_id in world.item_id_to_name:
- item_name = world.item_id_to_name[archipelago_loot_id]
- if item.name == item_name:
- item.classification = ItemClassification.progression
- else:
- continue
-
- return None
-
-def get_modded_locations(mod_info: List[ModInfoModel]) -> List[ModLocationData]:
- locations: List[ModLocationData] = []
-
- for mod in mod_info:
- for location in mod.data_model.Entities:
- entity_type = location['EntityType']
- #Entity type 0 is NPC
- if entity_type == 0:
- location = build_npc_location(location, mod.shifted_entity_ids, mod.excluded_ids)
- if location is not None:
- locations.append(location)
-
- #Entity type 5 is Treasure
- if entity_type == 5:
- location = build_treasure_location(location, mod.shifted_entity_ids, mod.excluded_ids)
- if location is not None:
- locations.append(location)
-
- # Entity type 6 is Crystal
- if entity_type == 6:
- location = build_crystal_location(location, mod.shifted_entity_ids, mod.excluded_ids)
- if location is not None:
- locations.append(location)
-
- return locations
-
-def get_modded_shopsanity_locations(mod_info: List[ModInfoModel]) -> List[ModLocationData]:
- locations: List[ModLocationData] = []
-
- for mod in mod_info:
- for location in mod.data_model.Entities:
- entity_type = location['EntityType']
- # Entity type 0 is NPC
- if entity_type == 0:
- npc_locations = build_shop_locations(location, mod.shifted_entity_ids, mod.excluded_ids)
- locations.extend(npc_locations)
-
- return locations
-
-def get_modded_bosses(mod_info: List[ModInfoModel]) -> List[ModLocationData]:
- locations: List[ModLocationData] = []
-
- for mod in mod_info:
- for location in mod.data_model.Entities:
- entity_type = location['EntityType']
-
- #Entity type 0 is NPC
- if entity_type == 0:
- location = build_boss_npc(location, mod.boss_troop_ids, mod.shifted_entity_ids)
- if location is not None:
- locations.append(location)
-
- #Entity type 2 is Spark
- if entity_type == 2:
- location = build_spark_location(location, mod.shifted_entity_ids)
- if location is not None:
- locations.append(location)
-
- return locations
-
-def get_removed_locations(mod_info: List[ModInfoModel]) -> List[LocationData]:
- removed_locations: List[LocationData] = []
- vanilla_treasures_and_npcs = get_treasure_and_npc_locations(-1, None)
- vanilla_crystals = get_crystal_locations(-1, None)
- vanilla_bosses = get_boss_locations(-1, None)
- vanilla_shops = get_shop_locations(-1, None)
-
- for mod in mod_info:
- for location in mod.data_model.Entities:
- location_id = location['ID']
- has_no_npc_info = location['NpcData'] is None or not location['NpcData']['Pages']
-
- if has_no_npc_info and location['SignData'] is None and location['SparkData'] is None and location['DoorData'] is None and location['HomePointData'] is None and location['TreasureData'] is None and location['CrystalData'] is None and location['MarkerData'] is None:
- treasure_id = location_id + treasure_index_offset
- npc_id = location_id + npc_index_offset
- crystal_id = location_id + crystal_index_offset
- boss_id = location_id + boss_index_offset
- shop_id = location_id + shop_index_offset
-
- for treasure_or_npc in vanilla_treasures_and_npcs:
- if treasure_or_npc.code == treasure_id or treasure_or_npc.code == npc_id:
- removed_locations.append(LocationData(treasure_or_npc.ap_region, treasure_or_npc.name, location_id))
-
- for crystal in vanilla_crystals:
- if crystal.code == crystal_id:
- removed_locations.append(LocationData(crystal.ap_region, crystal.name, location_id))
-
- for boss in vanilla_bosses:
- if boss.code == boss_id:
- removed_locations.append(LocationData(boss.ap_region, boss.name, location_id))
-
- for shop in vanilla_shops:
- if shop.code == shop_id:
- removed_locations.append(LocationData(shop.ap_region, shop.name, location_id))
-
- return removed_locations
-
-def get_mod_directory() -> str:
- current_directory = getcwd()
- mod_directory = join(current_directory, 'crystal_project_mods')
-
- return mod_directory
-
-def build_npc_location(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> Optional[ModLocationData]:
- options: CrystalProjectOptions
- biome_id = location['BiomeID']
- display_region = get_display_region_by_id(biome_id)
- item_id = location['ID']
-
- new_id = item_id
- for incremented_id in shifted_entity_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- id_with_offset = new_id + npc_index_offset
- name = display_region + ' NPC - Modded NPC ' + str(new_id)
- coord = location['Coord']
- coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
- has_add_inventory = False
- rule_condition = None
-
- pages = location['NpcData']['Pages']
- for page in pages:
- actions = page['Actions']
-
- for action in actions:
- # 3 is Condition, conditions can have sub actions so check those
- if action['ActionType'] == 3:
- condition = action['Data']['Condition']
- actions_true = action['Data']['ConditionActionsTrue']
- actions_false = action['Data']['ConditionActionsFalse']
-
- for action_true in actions_true:
- # 8 is Add Inventory 74 is Add Job, this means it's a check
- if action_true['ActionType'] == 8 or action_true['ActionType'] == 74:
- if action_true['ActionType'] == 8:
- is_excluded = is_item_at_location_excluded(action_true['Data'], excluded_ids)
- else:
- is_excluded = is_job_at_location_excluded(action_true['Data'], excluded_ids)
-
- has_add_inventory = not is_excluded
-
- # Condition Type 5 is Check Inventory
- if has_add_inventory and condition['ConditionType'] == 5 and not condition['IsNegation']:
- rule_condition = condition
-
- # Condition Type 11 is Job present and 21 is Job Mastered
- if has_add_inventory and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
- rule_condition = condition
-
- for action_false in actions_false:
- # 8 is Add Inventory 74 is Add Job, this means it's a check
- if action_false['ActionType'] == 8 or action_false['ActionType'] == 74:
- if action_false['ActionType'] == 8:
- is_excluded = is_item_at_location_excluded(action_false['Data'], excluded_ids)
- else:
- is_excluded = is_job_at_location_excluded(action_false['Data'], excluded_ids)
-
- has_add_inventory = not is_excluded
-
- # Condition Type 5 is Check Inventory
- if has_add_inventory and condition['ConditionType'] == 5 and condition['IsNegation']:
- rule_condition = condition
-
- # Condition Type 11 is Job present and 21 is Job Mastered
- if has_add_inventory and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
- rule_condition = condition
-
- # 8 is Add Inventory 74 is Add Job, this means it's a check
- if action['ActionType'] == 8 or action['ActionType'] == 74:
- if action['ActionType'] == 8:
- is_excluded = is_item_at_location_excluded(action['Data'], excluded_ids)
- else:
- is_excluded = is_job_at_location_excluded(action['Data'], excluded_ids)
-
- has_add_inventory = not is_excluded
-
- if has_add_inventory:
- location_in_pool = any(location.code == id_with_offset for location in get_treasure_and_npc_locations(-1, None))
- location_unused = any(location.code == id_with_offset for location in get_unused_locations())
-
- if not location_in_pool and not location_unused and not coordinates == "0,0,0":
- location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, rule_condition)
- return location
-
- return None
-
-def build_shop_locations(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> List[ModLocationData]:
- locations: List[ModLocationData] = []
- location_codes: List[int] = []
- options: CrystalProjectOptions
- biome_id = location['BiomeID']
- display_region = get_display_region_by_id(biome_id)
- item_id = location['ID']
- coord = location['Coord']
- coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
-
- new_id = item_id
- for incremented_id in shifted_entity_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- pages = location['NpcData']['Pages']
- for page in pages:
- actions = page['Actions']
-
- for action in actions:
- # 5 is Shop, only add if shopsanity is on
- if action['ActionType'] == 5:
- stock = action['Data']['Stock']
- id_offset = 10000
-
- for shop_item in stock:
- shop_item_id = new_id + id_offset
- id_with_offset = shop_item_id + shop_index_offset
- shop_name = display_region + ' Shop - Modded Shop ' + str(shop_item_id)
- shop_item_excluded = is_item_at_location_excluded(shop_item, excluded_ids)
- shop_item_in_pool = any(location.code == id_with_offset for location in get_shop_locations(-1, None))
-
- if not shop_item_in_pool and not shop_item_excluded:
- condition = shop_item['Condition']
- if condition['ConditionType'] == 5 and not condition['IsNegation']:
- rule_condition = condition
- else:
- rule_condition = None
-
- location = ModLocationData(display_region, shop_name, id_with_offset, shop_item_id, coordinates, biome_id, rule_condition)
- if not location.code in location_codes:
- locations.append(location)
- location_codes.append(location.code)
-
- id_offset += 10000
-
- return locations
-
-def build_treasure_location(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> Optional[ModLocationData]:
- #Chests always add an item and never have conditions, so nice and easy
- biome_id = location['BiomeID']
- display_region = get_display_region_by_id(biome_id)
- item_id = location['ID']
-
- new_id = item_id
- for incremented_id in shifted_entity_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- id_with_offset = new_id + treasure_index_offset
- name = display_region + ' Chest - Modded Chest ' + str(new_id)
- coord = location['Coord']
- coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
- is_excluded = is_item_at_location_excluded(location['TreasureData'], excluded_ids)
-
- location_in_pool = any(location.code == id_with_offset for location in get_treasure_and_npc_locations(-1, None))
- location_unused = any(location.code == id_with_offset for location in get_unused_locations())
-
- if not location_in_pool and not location_unused and not is_excluded:
- location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, None)
- return location
-
- return None
-
-def build_crystal_location(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> Optional[ModLocationData]:
- # Crystals always add a job and never have conditions, so nice and easy
- biome_id = location['BiomeID']
- display_region = get_display_region_by_id(biome_id)
- item_id = location['ID']
-
- new_id = item_id
- for incremented_id in shifted_entity_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- id_with_offset = new_id + crystal_index_offset
- name = display_region + ' Crystal - Modded Job ' + str(new_id)
- coord = location['Coord']
- coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
- job_id = location['CrystalData']['JobID']
- is_excluded = job_id in excluded_ids.excluded_job_ids
-
- location_in_pool = any(location.code == id_with_offset for location in get_crystal_locations(-1, None))
- location_unused = any(location.code == id_with_offset for location in get_unused_locations())
-
- if not location_in_pool and not location_unused and not is_excluded:
- location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, None)
-
- return location
-
- return None
-
-def build_boss_npc(location, boss_troop_ids: List[int], shifted_entity_ids: List[ModIncrementedIdData]) -> Optional[ModLocationData]:
- options: CrystalProjectOptions
- biome_id = location['BiomeID']
- display_region = get_display_region_by_id(biome_id)
- item_id = location['ID']
- troop_id = None
-
- new_id = item_id
- for incremented_id in shifted_entity_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- id_with_offset = new_id + boss_index_offset
- name = display_region + ' Boss - Modded Boss ' + str(new_id)
- coord = location['Coord']
- coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
- has_battle = False
- rule_condition = None
-
- pages = location['NpcData']['Pages']
- for page in pages:
- actions = page['Actions']
-
- for action in actions:
- # 3 is Condition, conditions can have sub actions so check those
- if action['ActionType'] == 3:
- condition = action['Data']['Condition']
- actions_true = action['Data']['ConditionActionsTrue']
- actions_false = action['Data']['ConditionActionsFalse']
-
- for action_true in actions_true:
- # 27 is Battle
- if action_true['ActionType'] == 27:
- troop_id = action_true['Data']['TroopID']
- has_battle = True
-
- # Condition Type 5 is Check Inventory
- if has_battle and condition['ConditionType'] == 5 and not condition['IsNegation']:
- rule_condition = condition
-
- # Condition Type 11 is Job present and 21 is Job Mastered
- if has_battle and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
- rule_condition = condition
-
- for action_false in actions_false:
- # 8 is Add Inventory 74 is Add Job, this means it's a check
- if action_false['ActionType'] == 27:
- troop_id = action_false['Data']['TroopID']
- has_battle = True
-
- # Condition Type 5 is Check Inventory
- if has_battle and condition['ConditionType'] == 5 and condition['IsNegation']:
- rule_condition = condition
-
- # Condition Type 11 is Job present and 21 is Job Mastered
- if has_battle and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
- rule_condition = condition
-
- # 27 is battle
- if action['ActionType'] == 27:
- troop_id = action['Data']['TroopID']
- has_battle = True
-
- if has_battle:
- is_boss = troop_id in boss_troop_ids
- location_in_pool = any(location.code == id_with_offset for location in get_boss_locations(-1, None))
-
- if is_boss and not location_in_pool:
- location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, rule_condition)
- return location
-
- return None
-
-def build_spark_location(location, shifted_entity_ids: List[ModIncrementedIdData]) -> Optional[ModLocationData]:
- if not location['SparkData']:
- return None
-
- is_unique = location['SparkData']['IsUnique']
-
- if not is_unique:
- #not a boss
- return None
-
- options: CrystalProjectOptions
- biome_id = location['BiomeID']
- display_region = get_display_region_by_id(biome_id)
- item_id = location['ID']
-
- new_id = item_id
- for incremented_id in shifted_entity_ids:
- if incremented_id.original_id == item_id:
- new_id = incremented_id.new_id
-
- id_with_offset = new_id + boss_index_offset
- name = display_region + ' Boss - Modded Boss ' + str(new_id)
- coord = location['Coord']
- coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
-
- location_in_pool = any(location.code == id_with_offset for location in get_boss_locations(-1, None))
-
- if not location_in_pool:
- location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, None)
- return location
-
- return None
-
-def build_condition_rule(region, condition, world: "CrystalProjectWorld") -> Optional[Callable[[CollectionState], bool]]:
- logic = CrystalProjectLogic(world.player, world.options)
- job_id = None
- loot_type = None
- loot_id = None
-
- region_rule = build_region_specific_rules(region, world.player, logic)
-
- if condition is not None:
- if 'Number' in condition['Data']:
- job_id = condition['Data']['Number']
-
- if 'LootType' in condition['Data']:
- loot_type = condition['Data']['LootType']
- loot_id = condition['Data']['LootValue']
- else:
- return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and region_rule
-
- if loot_type is None and job_id is not None:
- archipelago_loot_id = job_id + job_index_offset
- elif loot_type == 1:
- archipelago_loot_id = loot_id + item_index_offset
- elif loot_type == 2:
- archipelago_loot_id = loot_id + equipment_index_offset
- else:
- return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and region_rule
-
- if archipelago_loot_id in world.item_id_to_name:
- item_name = world.item_id_to_name[archipelago_loot_id]
- return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and state.has(item_name, world.player) and region_rule
- else:
- return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and region_rule
-
-def build_region_specific_rules(region, player:int, logic: CrystalProjectLogic) -> Optional[Callable[[CollectionState], bool]]:
- if region == CAPITAL_SEQUOIA_DISPLAY_NAME:
- return lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player) and logic.has_key(state, GARDENERS_KEY)
- elif region == SARA_SARA_BAZAAR_DISPLAY_NAME:
- return lambda state: logic.has_key(state, ROOM_ONE_KEY)
- elif region == CAPITAL_JAIL_DISPLAY_NAME:
- return lambda state: logic.has_key(state, SOUTH_WING_KEY) and logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, DARK_WING_KEY) and logic.has_key(state, CELL_KEY, 6)
- elif region == BEAURIOR_ROCK_DISPLAY_NAME:
- return lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)
- elif region == SLIP_GLIDE_RIDE_DISPLAY_NAME:
- return lambda state: logic.has_key(state, RED_DOOR_KEY, 3)
- elif region == SEQUOIA_ATHENAEUM_DISPLAY_NAME:
- return lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 6)
- elif region == CASTLE_RAMPARTS_DISPLAY_NAME:
- return lambda state: logic.has_key(state, RAMPART_KEY)
- else:
- return lambda state: True
-
-def get_excluded_ids(mod: ModDataModel) -> IdsExcludedFromRandomization:
- if mod.System is not None and mod.System["Randomizer"] is not None:
- excluded_item_ids = mod.System['Randomizer']['ExcludeItemIDs']
- excluded_job_ids = mod.System['Randomizer']['ExcludeJobIDs']
- excluded_equipment_ids = mod.System['Randomizer']['ExcludeEquipmentIDs']
-
- if excluded_item_ids is None:
- excluded_item_ids = []
- if excluded_job_ids is None:
- excluded_job_ids = []
- if excluded_equipment_ids is None:
- excluded_equipment_ids = []
-
- return IdsExcludedFromRandomization(excluded_equipment_ids, excluded_item_ids, excluded_job_ids)
- else:
- return IdsExcludedFromRandomization([], [], [])
-
-def get_next_mod_id(base_mod_id: int, ids_in_use: List[int]) -> int:
- while base_mod_id in ids_in_use:
- base_mod_id += 1
-
- return base_mod_id
-
-def is_item_at_location_excluded(data, excluded_ids: IdsExcludedFromRandomization) -> bool:
- loot_type = data['LootType']
- item_id = data['LootValue']
-
- #Loot Type 1 is item and 2 is equipment
- if loot_type == 1:
- return item_id in excluded_ids.excluded_item_ids
- elif loot_type == 2:
- return item_id in excluded_ids.excluded_equipment_ids
-
- return False
-
-def is_job_at_location_excluded(data, excluded_ids: IdsExcludedFromRandomization) -> bool:
- job_id = data['Value']
+import logging
+import os.path
+from os import listdir, getcwd
+from os.path import isfile, join
+from typing import Optional, Callable, TYPE_CHECKING
+from BaseClasses import Item, ItemClassification, CollectionState
+from typing import List, NamedTuple
+from .options import CrystalProjectOptions
+from .items import item_table, equipment_index_offset, item_index_offset, job_index_offset
+from .locations import LocationData, get_treasure_and_npc_locations, get_shop_locations, get_boss_locations, npc_index_offset, treasure_index_offset, crystal_index_offset, \
+ boss_index_offset, shop_index_offset, get_crystal_locations
+from .unused_locations import get_unused_locations
+from .constants.biomes import get_display_region_by_id
+from .constants.display_regions import *
+from .constants.keys import *
+from .constants.key_items import *
+from .rules import CrystalProjectLogic
+import json
+
+if TYPE_CHECKING:
+ from . import CrystalProjectWorld
+
+MAX_SUPPORTED_EDITOR_VERSION: int = 30
+
+class ModDataModel(object):
+ def __init__(self, json_data):
+ self.ID = None
+ self.Title = None
+ self.EditorVersion = None
+ self.System = None
+ self.Equipment = None
+ self.Items = None
+ self.Jobs = None
+ self.Entities = None
+ self.Sparks = None
+ self.Troops = None
+ self.Monsters = None
+ self.__dict__ = json.loads(json_data)
+
+class ModLocationData(NamedTuple):
+ display_region: str
+ name: str
+ code: int
+ offsetless_code: int
+ coordinates: str
+ biomeId: int
+ rule_condition: str | None
+
+class ModIncrementedIdData(NamedTuple):
+ original_id: int
+ new_id: int
+ mod_guid: str
+
+class IdsExcludedFromRandomization(NamedTuple):
+ excluded_equipment_ids : List[int]
+ excluded_item_ids : List[int]
+ excluded_job_ids : List[int]
+
+class ModInfoModel(NamedTuple):
+ mod_id: str
+ mod_name: str
+ load_order: int
+ data_model: ModDataModel
+ shifted_equipment_ids: List[ModIncrementedIdData]
+ shifted_item_ids: List[ModIncrementedIdData]
+ shifted_job_ids: List[ModIncrementedIdData]
+ shifted_entity_ids: List[ModIncrementedIdData]
+ shifted_spark_ids: List[ModIncrementedIdData]
+ boss_troop_ids: List[int]
+ excluded_ids: IdsExcludedFromRandomization
+
+def get_mod_info() -> List[ModInfoModel]:
+ data: List[ModInfoModel] = []
+ file_directory = get_mod_directory()
+
+ if not os.path.isdir(file_directory):
+ return data
+
+ only_files = [f for f in listdir(file_directory) if
+ isfile(join(file_directory, f))]
+
+ equipment_ids_in_use: List[int] = [591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610]
+ item_ids_in_use: List[int] = [229, 230, 231, 232]
+ job_ids_in_use: List[int] = []
+ entity_ids_in_use: List[int] = [5000, 5001, 5002, 5003, 5004]
+ spark_ids_in_use: List[int] = []
+ order_loaded = 1
+
+ for file_name in only_files:
+ file = (open(join(file_directory, file_name)))
+ file_text = file.read()
+ file_data = ModDataModel(file_text)
+
+ if file_data.EditorVersion <= MAX_SUPPORTED_EDITOR_VERSION:
+ excluded_ids = get_excluded_ids(file_data)
+
+ shifted_equipment_ids: List[ModIncrementedIdData] = []
+ for item in file_data.Equipment:
+ item_id = item['ID']
+
+ #Biggest vanilla ID is 590, we only want non-vanilla items
+ if item_id > 590:
+ next_id = get_next_mod_id(item_id, equipment_ids_in_use)
+ shifted_equipment_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
+ equipment_ids_in_use.append(next_id)
+
+ shifted_item_ids: List[ModIncrementedIdData] = []
+ for item in file_data.Items:
+ item_id = item['ID']
+
+ #Biggest vanilla ID is 228
+ if item_id > 228:
+ next_id = get_next_mod_id(item_id, item_ids_in_use)
+ shifted_item_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
+ item_ids_in_use.append(next_id)
+
+ shifted_job_ids: List[ModIncrementedIdData] = []
+ for item in file_data.Jobs:
+ item_id = item['ID']
+
+ #Biggest vanilla ID is 23
+ if item_id > 23:
+ next_id = get_next_mod_id(item_id, job_ids_in_use)
+ shifted_job_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
+ job_ids_in_use.append(next_id)
+
+ shifted_entity_ids: List[ModIncrementedIdData] = []
+ for item in file_data.Entities:
+ item_id = item['ID']
+
+ #Biggest vanilla ID is 4999
+ if item_id > 4999:
+ next_id = get_next_mod_id(item_id, entity_ids_in_use)
+ shifted_entity_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
+ entity_ids_in_use.append(next_id)
+
+ shifted_spark_ids: List[ModIncrementedIdData] = []
+ for item in file_data.Sparks:
+ item_id = item['ID']
+
+ #Biggest vanilla ID is 224
+ if item_id > 224:
+ next_id = get_next_mod_id(item_id, spark_ids_in_use)
+ shifted_spark_ids.append(ModIncrementedIdData(item_id, next_id, file_data.ID))
+ spark_ids_in_use.append(next_id)
+
+ boss_monster_ids: List[int] = []
+ boss_troop_ids: List[int] = []
+
+ for monster in file_data.Monsters:
+ if monster['IsBoss']:
+ boss_monster_ids.append(monster['ID'])
+
+ for troop in file_data.Troops:
+ for monster in troop['Members']:
+ if monster['MonsterID'] in boss_monster_ids:
+ boss_troop_ids.append(troop['ID'])
+
+ data.append(ModInfoModel(file_data.ID, file_data.Title, order_loaded, file_data, shifted_equipment_ids, shifted_item_ids, shifted_job_ids, shifted_entity_ids, shifted_spark_ids, boss_troop_ids, excluded_ids))
+ order_loaded = order_loaded + 1
+ else:
+ message = f"Mod {file_data.Title} was skipped because the editor version was {file_data.EditorVersion}. Archipelago currently only supports mods with editor version {MAX_SUPPORTED_EDITOR_VERSION} or lower."
+ logging.getLogger().info(message)
+
+ file.close()
+
+ return data
+
+def get_modded_items(mod_info: List[ModInfoModel]) -> List[Item]:
+ empty_player_value = -1
+ items: List[Item] = []
+
+ for mod in mod_info:
+ for item in mod.data_model.Equipment:
+ item_id = item['ID']
+ excluded = any(item_id == excluded_id for excluded_id in mod.excluded_ids.excluded_equipment_ids)
+
+ if excluded:
+ continue
+
+ new_id = item_id
+ for incremented_id in mod.shifted_equipment_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ offset_id = new_id + equipment_index_offset
+ item_in_pool = any(offset_id == data.code for name, data in item_table.items())
+ name = 'Equipment - ' + item['Name'] + ' - ' + str(offset_id)
+
+ if not item_in_pool:
+ mod_item = Item(name, ItemClassification.useful, offset_id, empty_player_value)
+ items.append(mod_item)
+
+ for item in mod.data_model.Items:
+ item_id = item['ID']
+ excluded = any(item_id == excluded_id for excluded_id in mod.excluded_ids.excluded_item_ids)
+
+ if excluded:
+ continue
+
+ new_id = item_id
+ for incremented_id in mod.shifted_item_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ offset_id = new_id + item_index_offset
+ item_in_pool = any(offset_id == data.code for name, data in item_table.items())
+ name = 'Item - ' + item['Name'] + ' - ' + str(offset_id)
+
+ if not item_in_pool:
+ mod_item = Item(name, ItemClassification.filler, offset_id, empty_player_value)
+ items.append(mod_item)
+
+ for item in mod.data_model.Jobs:
+ item_id = item['ID']
+ excluded = any(item_id == excluded_id for excluded_id in mod.excluded_ids.excluded_job_ids)
+
+ if excluded:
+ continue
+
+ new_id = item_id
+ for incremented_id in mod.shifted_job_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ offset_id = new_id + job_index_offset
+ item_in_pool = any(offset_id == data.code for name, data in item_table.items())
+
+ is_unselectable = False
+ if 'IsUnselectableJob' in item and 'IsUnselectableSubJob' in item:
+ is_unselectable = item['IsUnselectableJob'] and item['IsUnselectableSubJob']
+ name = 'Job - ' + item['Name'] + ' - ' + str(offset_id)
+
+ if not item_in_pool and not is_unselectable:
+ mod_item = Item(name, ItemClassification.progression, offset_id, empty_player_value)
+ items.append(mod_item)
+
+ return items
+
+def update_item_classification(item: Item, rule_condition, world: "CrystalProjectWorld") -> None:
+ for condition in rule_condition:
+ if condition is not None:
+ loot_type = condition['Data']['LootType']
+ loot_id = condition['Data']['LootValue']
+ else:
+ continue
+
+ if loot_type == 1:
+ archipelago_loot_id = loot_id + item_index_offset
+ elif loot_type == 2:
+ archipelago_loot_id = loot_id + equipment_index_offset
+ else:
+ continue
+
+ if archipelago_loot_id in world.item_id_to_name:
+ item_name = world.item_id_to_name[archipelago_loot_id]
+ if item.name == item_name:
+ item.classification = ItemClassification.progression
+ else:
+ continue
+
+ return None
+
+def get_modded_locations(mod_info: List[ModInfoModel]) -> List[ModLocationData]:
+ locations: List[ModLocationData] = []
+
+ for mod in mod_info:
+ for location in mod.data_model.Entities:
+ entity_type = location['EntityType']
+ #Entity type 0 is NPC
+ if entity_type == 0:
+ location = build_npc_location(location, mod.shifted_entity_ids, mod.excluded_ids)
+ if location is not None:
+ locations.append(location)
+
+ #Entity type 5 is Treasure
+ if entity_type == 5:
+ location = build_treasure_location(location, mod.shifted_entity_ids, mod.excluded_ids)
+ if location is not None:
+ locations.append(location)
+
+ # Entity type 6 is Crystal
+ if entity_type == 6:
+ location = build_crystal_location(location, mod.shifted_entity_ids, mod.excluded_ids)
+ if location is not None:
+ locations.append(location)
+
+ return locations
+
+def get_modded_shopsanity_locations(mod_info: List[ModInfoModel]) -> List[ModLocationData]:
+ locations: List[ModLocationData] = []
+
+ for mod in mod_info:
+ for location in mod.data_model.Entities:
+ entity_type = location['EntityType']
+ # Entity type 0 is NPC
+ if entity_type == 0:
+ npc_locations = build_shop_locations(location, mod.shifted_entity_ids, mod.excluded_ids)
+ locations.extend(npc_locations)
+
+ return locations
+
+def get_modded_bosses(mod_info: List[ModInfoModel]) -> List[ModLocationData]:
+ locations: List[ModLocationData] = []
+
+ for mod in mod_info:
+ for location in mod.data_model.Entities:
+ entity_type = location['EntityType']
+
+ #Entity type 0 is NPC
+ if entity_type == 0:
+ location = build_boss_npc(location, mod.boss_troop_ids, mod.shifted_entity_ids)
+ if location is not None:
+ locations.append(location)
+
+ #Entity type 2 is Spark
+ if entity_type == 2:
+ location = build_spark_location(location, mod.shifted_entity_ids)
+ if location is not None:
+ locations.append(location)
+
+ return locations
+
+def get_removed_locations(mod_info: List[ModInfoModel]) -> List[LocationData]:
+ removed_locations: List[LocationData] = []
+ vanilla_treasures_and_npcs = get_treasure_and_npc_locations(-1, None)
+ vanilla_crystals = get_crystal_locations(-1, None)
+ vanilla_bosses = get_boss_locations(-1, None)
+ vanilla_shops = get_shop_locations(-1, None)
+
+ for mod in mod_info:
+ for location in mod.data_model.Entities:
+ location_id = location['ID']
+ has_no_npc_info = location['NpcData'] is None or not location['NpcData']['Pages']
+
+ if has_no_npc_info and location['SignData'] is None and location['SparkData'] is None and location['DoorData'] is None and location['HomePointData'] is None and location['TreasureData'] is None and location['CrystalData'] is None and location['MarkerData'] is None:
+ treasure_id = location_id + treasure_index_offset
+ npc_id = location_id + npc_index_offset
+ crystal_id = location_id + crystal_index_offset
+ boss_id = location_id + boss_index_offset
+ shop_id = location_id + shop_index_offset
+
+ for treasure_or_npc in vanilla_treasures_and_npcs:
+ if treasure_or_npc.code == treasure_id or treasure_or_npc.code == npc_id:
+ removed_locations.append(LocationData(treasure_or_npc.ap_region, treasure_or_npc.name, location_id))
+
+ for crystal in vanilla_crystals:
+ if crystal.code == crystal_id:
+ removed_locations.append(LocationData(crystal.ap_region, crystal.name, location_id))
+
+ for boss in vanilla_bosses:
+ if boss.code == boss_id:
+ removed_locations.append(LocationData(boss.ap_region, boss.name, location_id))
+
+ for shop in vanilla_shops:
+ if shop.code == shop_id:
+ removed_locations.append(LocationData(shop.ap_region, shop.name, location_id))
+
+ return removed_locations
+
+def get_mod_directory() -> str:
+ current_directory = getcwd()
+ mod_directory = join(current_directory, 'crystal_project_mods')
+
+ return mod_directory
+
+def build_npc_location(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> Optional[ModLocationData]:
+ options: CrystalProjectOptions
+ biome_id = location['BiomeID']
+ display_region = get_display_region_by_id(biome_id)
+ item_id = location['ID']
+
+ new_id = item_id
+ for incremented_id in shifted_entity_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ id_with_offset = new_id + npc_index_offset
+ name = display_region + ' NPC - Modded NPC ' + str(new_id)
+ coord = location['Coord']
+ coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
+ has_add_inventory = False
+ rule_condition = None
+
+ pages = location['NpcData']['Pages']
+ for page in pages:
+ actions = page['Actions']
+
+ for action in actions:
+ # 3 is Condition, conditions can have sub actions so check those
+ if action['ActionType'] == 3:
+ condition = action['Data']['Condition']
+ actions_true = action['Data']['ConditionActionsTrue']
+ actions_false = action['Data']['ConditionActionsFalse']
+
+ for action_true in actions_true:
+ # 8 is Add Inventory 74 is Add Job, this means it's a check
+ if action_true['ActionType'] == 8 or action_true['ActionType'] == 74:
+ if action_true['ActionType'] == 8:
+ is_excluded = is_item_at_location_excluded(action_true['Data'], excluded_ids)
+ else:
+ is_excluded = is_job_at_location_excluded(action_true['Data'], excluded_ids)
+
+ has_add_inventory = not is_excluded
+
+ # Condition Type 5 is Check Inventory
+ if has_add_inventory and condition['ConditionType'] == 5 and not condition['IsNegation']:
+ rule_condition = condition
+
+ # Condition Type 11 is Job present and 21 is Job Mastered
+ if has_add_inventory and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
+ rule_condition = condition
+
+ for action_false in actions_false:
+ # 8 is Add Inventory 74 is Add Job, this means it's a check
+ if action_false['ActionType'] == 8 or action_false['ActionType'] == 74:
+ if action_false['ActionType'] == 8:
+ is_excluded = is_item_at_location_excluded(action_false['Data'], excluded_ids)
+ else:
+ is_excluded = is_job_at_location_excluded(action_false['Data'], excluded_ids)
+
+ has_add_inventory = not is_excluded
+
+ # Condition Type 5 is Check Inventory
+ if has_add_inventory and condition['ConditionType'] == 5 and condition['IsNegation']:
+ rule_condition = condition
+
+ # Condition Type 11 is Job present and 21 is Job Mastered
+ if has_add_inventory and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
+ rule_condition = condition
+
+ # 8 is Add Inventory 74 is Add Job, this means it's a check
+ if action['ActionType'] == 8 or action['ActionType'] == 74:
+ if action['ActionType'] == 8:
+ is_excluded = is_item_at_location_excluded(action['Data'], excluded_ids)
+ else:
+ is_excluded = is_job_at_location_excluded(action['Data'], excluded_ids)
+
+ has_add_inventory = not is_excluded
+
+ if has_add_inventory:
+ location_in_pool = any(location.code == id_with_offset for location in get_treasure_and_npc_locations(-1, None))
+ location_unused = any(location.code == id_with_offset for location in get_unused_locations())
+
+ if not location_in_pool and not location_unused and not coordinates == "0,0,0":
+ location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, rule_condition)
+ return location
+
+ return None
+
+def build_shop_locations(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> List[ModLocationData]:
+ locations: List[ModLocationData] = []
+ location_codes: List[int] = []
+ options: CrystalProjectOptions
+ biome_id = location['BiomeID']
+ display_region = get_display_region_by_id(biome_id)
+ item_id = location['ID']
+ coord = location['Coord']
+ coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
+
+ new_id = item_id
+ for incremented_id in shifted_entity_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ pages = location['NpcData']['Pages']
+ for page in pages:
+ actions = page['Actions']
+
+ for action in actions:
+ # 5 is Shop, only add if shopsanity is on
+ if action['ActionType'] == 5:
+ stock = action['Data']['Stock']
+ id_offset = 10000
+
+ for shop_item in stock:
+ shop_item_id = new_id + id_offset
+ id_with_offset = shop_item_id + shop_index_offset
+ shop_name = display_region + ' Shop - Modded Shop ' + str(shop_item_id)
+ shop_item_excluded = is_item_at_location_excluded(shop_item, excluded_ids)
+ shop_item_in_pool = any(location.code == id_with_offset for location in get_shop_locations(-1, None))
+
+ if not shop_item_in_pool and not shop_item_excluded:
+ condition = shop_item['Condition']
+ if condition['ConditionType'] == 5 and not condition['IsNegation']:
+ rule_condition = condition
+ else:
+ rule_condition = None
+
+ location = ModLocationData(display_region, shop_name, id_with_offset, shop_item_id, coordinates, biome_id, rule_condition)
+ if not location.code in location_codes:
+ locations.append(location)
+ location_codes.append(location.code)
+
+ id_offset += 10000
+
+ return locations
+
+def build_treasure_location(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> Optional[ModLocationData]:
+ #Chests always add an item and never have conditions, so nice and easy
+ biome_id = location['BiomeID']
+ display_region = get_display_region_by_id(biome_id)
+ item_id = location['ID']
+
+ new_id = item_id
+ for incremented_id in shifted_entity_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ id_with_offset = new_id + treasure_index_offset
+ name = display_region + ' Chest - Modded Chest ' + str(new_id)
+ coord = location['Coord']
+ coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
+ is_excluded = is_item_at_location_excluded(location['TreasureData'], excluded_ids)
+
+ location_in_pool = any(location.code == id_with_offset for location in get_treasure_and_npc_locations(-1, None))
+ location_unused = any(location.code == id_with_offset for location in get_unused_locations())
+
+ if not location_in_pool and not location_unused and not is_excluded:
+ location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, None)
+ return location
+
+ return None
+
+def build_crystal_location(location, shifted_entity_ids: List[ModIncrementedIdData], excluded_ids: IdsExcludedFromRandomization) -> Optional[ModLocationData]:
+ # Crystals always add a job and never have conditions, so nice and easy
+ biome_id = location['BiomeID']
+ display_region = get_display_region_by_id(biome_id)
+ item_id = location['ID']
+
+ new_id = item_id
+ for incremented_id in shifted_entity_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ id_with_offset = new_id + crystal_index_offset
+ name = display_region + ' Crystal - Modded Job ' + str(new_id)
+ coord = location['Coord']
+ coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
+ job_id = location['CrystalData']['JobID']
+ is_excluded = job_id in excluded_ids.excluded_job_ids
+
+ location_in_pool = any(location.code == id_with_offset for location in get_crystal_locations(-1, None))
+ location_unused = any(location.code == id_with_offset for location in get_unused_locations())
+
+ if not location_in_pool and not location_unused and not is_excluded:
+ location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, None)
+
+ return location
+
+ return None
+
+def build_boss_npc(location, boss_troop_ids: List[int], shifted_entity_ids: List[ModIncrementedIdData]) -> Optional[ModLocationData]:
+ options: CrystalProjectOptions
+ biome_id = location['BiomeID']
+ display_region = get_display_region_by_id(biome_id)
+ item_id = location['ID']
+ troop_id = None
+
+ new_id = item_id
+ for incremented_id in shifted_entity_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ id_with_offset = new_id + boss_index_offset
+ name = display_region + ' Boss - Modded Boss ' + str(new_id)
+ coord = location['Coord']
+ coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
+ has_battle = False
+ rule_condition = None
+
+ pages = location['NpcData']['Pages']
+ for page in pages:
+ actions = page['Actions']
+
+ for action in actions:
+ # 3 is Condition, conditions can have sub actions so check those
+ if action['ActionType'] == 3:
+ condition = action['Data']['Condition']
+ actions_true = action['Data']['ConditionActionsTrue']
+ actions_false = action['Data']['ConditionActionsFalse']
+
+ for action_true in actions_true:
+ # 27 is Battle
+ if action_true['ActionType'] == 27:
+ troop_id = action_true['Data']['TroopID']
+ has_battle = True
+
+ # Condition Type 5 is Check Inventory
+ if has_battle and condition['ConditionType'] == 5 and not condition['IsNegation']:
+ rule_condition = condition
+
+ # Condition Type 11 is Job present and 21 is Job Mastered
+ if has_battle and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
+ rule_condition = condition
+
+ for action_false in actions_false:
+ # 8 is Add Inventory 74 is Add Job, this means it's a check
+ if action_false['ActionType'] == 27:
+ troop_id = action_false['Data']['TroopID']
+ has_battle = True
+
+ # Condition Type 5 is Check Inventory
+ if has_battle and condition['ConditionType'] == 5 and condition['IsNegation']:
+ rule_condition = condition
+
+ # Condition Type 11 is Job present and 21 is Job Mastered
+ if has_battle and (condition['ConditionType'] == 11 or condition['ConditionType'] == 21):
+ rule_condition = condition
+
+ # 27 is battle
+ if action['ActionType'] == 27:
+ troop_id = action['Data']['TroopID']
+ has_battle = True
+
+ if has_battle:
+ is_boss = troop_id in boss_troop_ids
+ location_in_pool = any(location.code == id_with_offset for location in get_boss_locations(-1, None))
+
+ if is_boss and not location_in_pool:
+ location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, rule_condition)
+ return location
+
+ return None
+
+def build_spark_location(location, shifted_entity_ids: List[ModIncrementedIdData]) -> Optional[ModLocationData]:
+ if not location['SparkData']:
+ return None
+
+ is_unique = location['SparkData']['IsUnique']
+
+ if not is_unique:
+ #not a boss
+ return None
+
+ options: CrystalProjectOptions
+ biome_id = location['BiomeID']
+ display_region = get_display_region_by_id(biome_id)
+ item_id = location['ID']
+
+ new_id = item_id
+ for incremented_id in shifted_entity_ids:
+ if incremented_id.original_id == item_id:
+ new_id = incremented_id.new_id
+
+ id_with_offset = new_id + boss_index_offset
+ name = display_region + ' Boss - Modded Boss ' + str(new_id)
+ coord = location['Coord']
+ coordinates = str(coord['X']) + ',' + str(coord['Y']) + ',' + str(coord['Z'])
+
+ location_in_pool = any(location.code == id_with_offset for location in get_boss_locations(-1, None))
+
+ if not location_in_pool:
+ location = ModLocationData(display_region, name, id_with_offset, new_id, coordinates, biome_id, None)
+ return location
+
+ return None
+
+def build_condition_rule(region, condition, world: "CrystalProjectWorld") -> Optional[Callable[[CollectionState], bool]]:
+ logic = CrystalProjectLogic(world.player, world.options)
+ job_id = None
+ loot_type = None
+ loot_id = None
+
+ region_rule = build_region_specific_rules(region, world.player, logic)
+
+ if condition is not None:
+ if 'Number' in condition['Data']:
+ job_id = condition['Data']['Number']
+
+ if 'LootType' in condition['Data']:
+ loot_type = condition['Data']['LootType']
+ loot_id = condition['Data']['LootValue']
+ else:
+ return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and region_rule
+
+ if loot_type is None and job_id is not None:
+ archipelago_loot_id = job_id + job_index_offset
+ elif loot_type == 1:
+ archipelago_loot_id = loot_id + item_index_offset
+ elif loot_type == 2:
+ archipelago_loot_id = loot_id + equipment_index_offset
+ else:
+ return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and region_rule
+
+ if archipelago_loot_id in world.item_id_to_name:
+ item_name = world.item_id_to_name[archipelago_loot_id]
+ return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and state.has(item_name, world.player) and region_rule
+ else:
+ return lambda state: logic.has_swimming(state) and logic.has_glide(state) and logic.has_vertical_movement(state) and region_rule
+
+def build_region_specific_rules(region, player:int, logic: CrystalProjectLogic) -> Optional[Callable[[CollectionState], bool]]:
+ if region == CAPITAL_SEQUOIA_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, LUXURY_KEY) and state.has(PROGRESSIVE_LUXURY_PASS, player) and logic.has_key(state, GARDENERS_KEY)
+ elif region == SARA_SARA_BAZAAR_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, ROOM_ONE_KEY)
+ elif region == CAPITAL_JAIL_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, SOUTH_WING_KEY) and logic.has_key(state, WEST_WING_KEY) and logic.has_key(state, EAST_WING_KEY) and logic.has_key(state, DARK_WING_KEY) and logic.has_key(state, CELL_KEY, 6)
+ elif region == BEAURIOR_ROCK_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, SMALL_KEY, 4) and logic.has_key(state, BEAURIOR_BOSS_KEY)
+ elif region == SLIP_GLIDE_RIDE_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, RED_DOOR_KEY, 3)
+ elif region == SEQUOIA_ATHENAEUM_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, ICE_PUZZLE_KEY, 6)
+ elif region == CASTLE_RAMPARTS_DISPLAY_NAME:
+ return lambda state: logic.has_key(state, RAMPART_KEY)
+ else:
+ return lambda state: True
+
+def get_excluded_ids(mod: ModDataModel) -> IdsExcludedFromRandomization:
+ if mod.System is not None and mod.System["Randomizer"] is not None:
+ excluded_item_ids = mod.System['Randomizer']['ExcludeItemIDs']
+ excluded_job_ids = mod.System['Randomizer']['ExcludeJobIDs']
+ excluded_equipment_ids = mod.System['Randomizer']['ExcludeEquipmentIDs']
+
+ if excluded_item_ids is None:
+ excluded_item_ids = []
+ if excluded_job_ids is None:
+ excluded_job_ids = []
+ if excluded_equipment_ids is None:
+ excluded_equipment_ids = []
+
+ return IdsExcludedFromRandomization(excluded_equipment_ids, excluded_item_ids, excluded_job_ids)
+ else:
+ return IdsExcludedFromRandomization([], [], [])
+
+def get_next_mod_id(base_mod_id: int, ids_in_use: List[int]) -> int:
+ while base_mod_id in ids_in_use:
+ base_mod_id += 1
+
+ return base_mod_id
+
+def is_item_at_location_excluded(data, excluded_ids: IdsExcludedFromRandomization) -> bool:
+ loot_type = data['LootType']
+ item_id = data['LootValue']
+
+ #Loot Type 1 is item and 2 is equipment
+ if loot_type == 1:
+ return item_id in excluded_ids.excluded_item_ids
+ elif loot_type == 2:
+ return item_id in excluded_ids.excluded_equipment_ids
+
+ return False
+
+def is_job_at_location_excluded(data, excluded_ids: IdsExcludedFromRandomization) -> bool:
+ job_id = data['Value']
return job_id in excluded_ids.excluded_job_ids
\ No newline at end of file
diff --git a/worlds/crystal_project/options.py b/worlds/crystal_project/options.py
index 4b589c8eef62..238b224d3ce6 100644
--- a/worlds/crystal_project/options.py
+++ b/worlds/crystal_project/options.py
@@ -1,469 +1,469 @@
-from dataclasses import dataclass
-from typing import Type, Any, List
-from typing import Dict
-from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Visibility, Option, OptionGroup
-from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions, StartInventoryPool
-
-
-def create_option_groups() -> List[OptionGroup]:
- option_group_list: List[OptionGroup] = []
- for name, options in crystal_project_option_groups.items():
- option_group_list.append(OptionGroup(name=name, options=options))
-
- return option_group_list
-#"""Goal Options"""
-class Goal(Choice):
- """
- Choose what is required to complete the game.
-
- Checking the first sign in the Spawning Meadows will tell you the selected goal. A goal items counter is displayed on the in-game party screen in the menu.
-
- Astley: Defeat Astley in the New World. A New World Stone will be given to the player after obtaining a certain number of jobs (starting Jobs don't count).
-
- True Astley: A saga awaits you! Collect 4 Deity Eyes and the STEM WARD to challenge Gabriel for the Old World Stone. Then travel to the Old World to defeat Periculum and earn the Proof of Merit.
- Along the way, gather enough Jobs to unlock the New World Stone. Then you can venture to the New World to defeat true Astley to win!
-
- Clamshells: Collect enough clamshells for Mañana Man in Seaside Cliffs.
- """
- display_name = "Goal"
- option_astley = 0
- option_true_astley = 1
- option_clamshells = 2
- default = 0
-
-class NewWorldStoneJobQuantity(Range):
- """
- If your goal is Astley, select how many Jobs you need to find before being sent the New World Stone for the final fight.
-
- This option is validated at generation time, and if it is higher than the number of jobs you can obtain, it will be capped to what is possible. By default, the maximum you can obtain is 18.
-
- Picking Job Rando: Full and decreasing the Starting Job Quantity will increase the maximum by that much. It will also increase if you enable mods that give you more.
-
- Picking Job Rando: None and Included Regions: Beginner or Advanced will reduce the maximum number available to 4 or 11, respectively.
- """
- display_name = "Job count that locks the New World Stone"
- range_start = 1
- range_end = 99
- default = 18
-
-class ClamshellGoalQuantity(Range):
- """
- If your goal is Clamshells, select how many you need to win.
-
- (If your goal is not Clamshells, this setting does nothing. 3 Clamshells will be placed in your pool and the Mañana Man will ask for 2. Don't worry, he's cool with it.)
- """
- display_name = "Clamshells needed to win"
- range_start = 2
- range_end = 99
- default = 13
-
-class ExtraClamshellsInPool(Range):
- """
- If your goal is Clamshells, pick how many more Clamshells than your goal value to have in the item pool.
- This option makes it so that you don't have to find every single Clamshell in your pool to win.
-
- (If your goal is not Clamshells, this setting does nothing.)
- """
- display_name = "Extra Clamshells in the pool"
- range_start = 0
- range_end = 99
- default = 6
-
-#"""Location Options"""
-class IncludedRegions(Choice):
- """
- Choose which regions to play in! Only locations within your chosen regions will give you checks; the rest will be empty.
- This can be used to adjust the difficulty and length of your game.
-
- NOTE: Make sure to include the region that has the finish line of your chosen goal in it!
- Example: if your goal is Astley, Advanced is considered to have that finish line, so you can pick Advanced, Expert, or All.
-
- Beginner: Only regions up through the end of Skumparadise will have checks. Finish Line: Clamshells
-
- Advanced: Regions up to Salmon Bay will have checks. Finish Line: Astley (Note: Level Gating's Level Catch-Up Option is recommended for Astley on Advanced.)
-
- Expert: Regions up to The Deep Sea will have checks. (Note: The Depths will not be included.)
-
- All: Every region is included. Finish Line: True Astley
- """
- display_name = "Regions to include in game"
- option_beginner = 0
- option_advanced = 1
- option_expert = 2
- option_all = 3
- default = 3
-
-class JobRando(Choice):
- """
- None: all Jobs are at their normal crystal locations in the vanilla game.
-
- Crystal: all vanilla Jobs normally received from crystals are chucked into the item pool.
-
- Full: your starting jobs are randomized, and the rest of the vanilla Jobs are added to the item pool.
- Adjust the Starting Job Quantity (see below) for how many random Jobs you start with.
- """
- display_name = "Job Rando"
- option_none = 0
- option_crystal = 1
- option_full = 2
- default = 2
-
-class StartingJobQuantity(Range):
- """
- Select how many Jobs you start with. (This setting only does anything if JobRando is set to Full.)
- """
- display_name = "Starting Job Quantity"
- range_start = 1
- range_end = 6
- default = 6
-
-class DisableSparks(Toggle):
- """
- When enabled, enemy sparks are disabled: enemies will completely ignore the player, and touching them does not start combat.
- An Enable/Disable Sparks button will appear in the game's Archipelago menu that you can use to toggle spark disabling on and off.
- Explore unbothered by fiery plebeians, re-enable them when you deign to grind XP or earn money, and re-disable them when you've had enough of their nonsense!
-
- However: if Kill Bosses is enabled, then boss sparks still have your number (a.k.a. chase you and start combat). They can sense your murderous intent.
- """
- display_name = "Disable Sparks"
-
-class KillBossesMode(Toggle):
- """
- When enabled, defeating a boss will provide checks.
-
- WARNING: If you restrict the Included Regions to Beginner or Advanced, checks will require defeating level 50+ bosses in some of the early areas!
- You may want to set the Level Gating option to Level Catch-Up. Without it, you are signing up to either grind or fight the bosses under-leveled.
- """
- display_name = "Kill Bosses Mode"
-
-class Shopsanity(Choice):
- """
- When enabled, all shop inventories will be replaced with checks. Be prepared, adventurer.
-
- Choosing "enabled and hint" will, when you CLOSE the store, automatically create a hint for every item you did not purchase
- so other players will realize you have betrayed them by refusing to purchase their key progression item!
- """
- display_name = "Shopsanity"
- option_disabled = 0
- option_enabled = 1
- option_enabled_and_hint = 2
- default = 0
-
-class Regionsanity(Choice):
- """
- Nothing I have tried has been able to drive the citizens of Sequoia to collect enough crystals!
- Adventurers have had TOO MUCH freedom! From now on, you will adventure where I tell you to.
- Unless you have a pass from ME, you won't be able to do any adventuring!
- - Grandmaster
-
- When enabled, the grandmaster will not permit you to interact with anything in a region
- without receiving his "official permission" (i.e. "Item - Spawning Meadows Pass").
- However, the Overpass and Underpass are regions of lawlessness where the Grandmaster has no authority! ;)
- (You're also still allowed to use the save points, we won't tell.)
-
- If you put any region pass items in your starting inventory, the first one will be chosen as your starting region.
-
- If regionsanity is set to extreme, the Grandmaster won't even let you walk through regions you don't have the pass for.
- Spend more than 10 seconds in a region without a pass and the Grandmaster will teleport you!
-
- You will start the game with a pass for one reachable region.
- """
- display_name = "Regionsanity"
- option_disabled = 0
- option_enabled = 1
- option_extreme = 2
- default = 0
-
-#"""Progression Options"""
-class ProgressiveMountMode(DefaultOnToggle):
- """
- When enabled, all mount items are combined into a Progressive Mount Instrument and will be received in the approximate order you would receive them in the vanilla game:
- Quintar Pass -> Quintar Flute -> Ibek Bell -> Owl Drum -> Salmon Violin -> Salmon Cello -> Quintar Ocarina
-
- When disabled, different mount types are separated:
- Progressive Quintar Flute (Quintar Pass -> Quintar Flute -> Quintar Ocarina),
- Progressive Salmon Violin (Salmon Violin -> Salmon Cello),
- Ibek Bell,
- and Owl Drum.
- """
- display_name = "Progressive Mount Mode"
-
-class LevelGating(Choice):
- """
- When enabled, the party's level is considered for Archipelago logic, and Progressive Level items are added to the pool. (This won't stop you from beating the game at level 3. ^_^)
-
- None: Level gating is disabled. No Progressive Levels in the pool.
-
- Level Passes: Progressive Levels are added to the pool. They do not affect the party's actual level or level cap. Archipelago will expect you to collect them to access level-gated areas,
- and in-game tracking will light up checks that you have access to based on that level.
-
- Level Capped: The party's maximum level is hard capped. Progressive Levels are added to the pool. Collecting them allows your party to gain more levels, and in-game tracking will light up
- checks that you have access to based on that max level.
-
- Level Catch-Up: Progressive Levels are added to the pool. Collecting them will help your party catch up in levels (and LP!) based on the number you've collected. For example, if the Progressive Level
- Size Setting is 6 (see below), 1 Progressive Level will bring your party's level up to 6, 2 Progressive Levels will bring your party's level up to 12, etc. If your party
- has already reached that level, no extra levels will be granted. 2 LP are granted per 1 level granted. In-game tracking will light up checks that you have access to based on the number you have collected.
-
- Level Set: A combination of Level Catch-Up and Capped. The player will always be at the level set by the Progressive Level. There is no escape.
- """
- display_name = "Level Gating"
- option_none = 0
- option_level_passes = 1
- option_level_capped = 2
- option_level_catch_up = 3
- option_level_set = 4
- default = 1
-
-class LevelComparedToEnemies(Range):
- """
- If Level Gating is on, this option changes what level you're expected to fight enemies.
- Set it higher if you want to be a higher level than enemies when you enter a region, or lower if you want to be lower.
-
- For example, if this is set to 5, and the enemy level of a region is 12, then the Level Gating options would require you to unlock level 17 (or for Level Capped, max level 17) for that region.
- If it's set to -5, and the enemy level of a region is 12, then the Level Gating options would require you to unlock level 7 (or for Level Capped, max level 7) for that region.
-
- Default is 0, or on-level for the enemy level of a region.
-
- Note: Remember to increase your Max Level (see below) if you want regions with high-level enemies to still be lower level than you.
- Note #2: Spark color changes: red at -10 levels, orange at -5, green at +3, and grey +10. (Though enemies can be 3-5 levels above the min enemy level for a region.)
- """
- display_name = "Level compared to enemies"
- range_start = -10
- range_end = 10
- default = 0
-
-
-class ProgressiveLevelSize(Range):
- """
- If Level Gating is on, Progressive Levels will be added to the item pool. This sets the number of levels that an individual Progressive Level will grant, as well as the starting level expectation.
-
- For example, if Progressive Level Size is 6, the three Level Gating options would behave like this:
- Level Passes - At the start, areas up to level 6 are considered in logic. The first Progressive Level you collect will signal that areas up to 12 are now logic, the second up to 18, etc.
- Level Capped - Your party's level cap starts at 6. The first Progressive Level you collect will increase your party's level cap to 12, the second to 18, and so on.
- Level Catch-Up - Your party is leveled up to 6 at the start. The first Progressive Level you collect will bring your party's level up to 12 if it isn't already, the second up to 18, and so on.
- Level Set - Your party's level and level cap start at 6. The first Progressive Level you collect will increase your party's level and level cap to 12, the second to 18, and so on.
-
- This setting will only increase your party's starting level (3 by default) if you pick Level Catch-Up.
- """
- display_name = "Progressive Level Size"
- range_start = 3
- range_end = 10
- default = 6
-
-class MaxLevel(Range):
- """
- This allows your party to level past the default maximum level of 60. If Level Gating is on, increasing this number will also put more Progressive Levels in your pool.
-
- Set it below 60 at your own risk <3
- """
- display_name = "Max Level"
- range_start = 3
- range_end = 99
- default = 60
-
-class KeyMode(Choice):
- """
- Skeleton Key: Only the Skeleton Key, which can open any locked door, will be in your item pool.
-
- Key Ring: Keys for individual dungeons will be grouped into Key Rings. Each Key Ring will give all keys needed to complete one dungeon. The Skeleton Key will still be in the pool.
-
- Vanilla: All vanilla keys will be in the item pool.
-
- Skelefree: Same as the named mode, but removes the Skeleton Key.
- """
- display_name = "Key Mode"
- option_skeleton = 0
- option_key_ring = 1
- option_vanilla = 2
- option_key_ring_skelefree = 3
- option_vanilla_skelefree = 4
- default = 2
-
-class ObscureRoutes(Toggle):
- """
- When enabled, connections between regions that are difficult to find will be expected in logic.
- """
- display_name = "Obscure Routes"
-
-class PrioritizeCrystals(DefaultOnToggle):
- """
- When enabled, crystals will be prioritized when placing progression items.
-
- While you're here, did you know that all YAMLs for all worlds include a priority_locations and an exclude_locations option?
- You can use these to flag any individual location or a location group as priority or excluded.
- Priority locations will be chosen first when placing progression items, and excluded locations will be chosen last.
-
- Here's an example:
- priority_locations = ["Crystals", "Region Completions", "Bosses", "Salmon River NPC - Win the Salmon Race"]
-
- Crystal Project's location groups: Crystals, Bosses, Region Completions, and Shops
- """
- display_name = "Prioritize Crystals"
-
-class AutoSpendLP(Toggle):
- """
- When enabled, every time a character earns LP, it will automatically spend LP on abilities or passives.
- Extra LP earned on a job that is maxed will be sent to your sub job or, if that is also maxed, a random un-maxed job.
- """
- display_name = "Automatically Spend LP"
-
-class AutoEquipPassives(Toggle):
- """
- When enabled, every time a character unlocks a new passive, it will equip it immediately if enough passive points are available.
- Passives with drawbacks, that enable equipping more gear types, or that modify threat are not automatically equipped.
- If mods are enabled, all passives are auto-equipped with no exclusions.
- """
- display_name = "Automatically Equip Passives"
-
-class EasyLeveling(Toggle):
- """
- When enabled, characters will not receive reduced experience for being dead or for being a higher level than the fought enemy.
- """
- display_name = "Easy Leveling"
-
-#"""Item Pool Options"""
-class ProgressiveEquipmentMode(DefaultOnToggle):
- """
- When enabled, each category of weapon and armor will be sent to the player in progressive order from weakest to strongest.
-
- When disabled, weapons and armor are fully randomized.
- """
- display_name = "Progressive Equipment"
-
-class StartWithTreasureFinder(DefaultOnToggle):
- """
- When enabled, the player will start with the Treasure Finder in their inventory. When disabled, it will be in the item pool.
-
- NOTE: Having the Treasure Finder is required for the in-game minimap to display nearby checks.
- """
- display_name = "Begin with Treasure Finder"
-
-class StartWithMaps(DefaultOnToggle):
- """
- When enabled, the player will start with all maps in their inventory.
-
- Note: The in-game minimap hides when in an area without its map. The world map will display check location icons but will not draw an area without its map.
- """
- display_name = "Begin with Area Maps"
-
-class FillFullMap(Toggle):
- """
- When enabled, the world map will start filled in for areas that the player has a map item for.
- """
- display_name = "Fill Full Map"
-
-class IncludeSummonAbilities(DefaultOnToggle):
- """
- When enabled, Summons are added to the item pool.
-
- Note: It is not recommended to turn this off with Job Rando: Full and a low number of starting Jobs. You don't want to get stuck with just a Summoner who can only summon Pinga!
- (I'm sorry, Pinga.)
- """
- display_name = "Include Summons in the item pool"
-
-class IncludeScholarAbilities(DefaultOnToggle):
- """
- When enabled, Scholar abilities are added to the item pool. They can still be learned as normal from enemies.
- """
- display_name = "Include Scholar Abilities in the item pool"
-
-#"""Bonus Fun"""
-class TrapLikelihood(Range):
- """
- This is the likelihood that a trap will replace a filler check. A value of 0 means no traps.
- """
- display_name = "Trap Likelihood"
- range_start = 0
- range_end = 100
- default = 0
-
-class ItemInfoMode(Choice):
- """
- For Full, all treasure and store icons on the map will display if they are progression, useful, or filler items.
-
- For Earned, all treasure and store icons on the map will display as mimics until you complete 50% of your checks.
-
- For Obscured, all treasure and store icons on the map will display as mimics permanently.
- If you find skipping treasures is distasteful but part of your brain always wants to be efficient, this option is for you!
- It's also good for a race environment.
- """
- display_name = "Item Info Mode"
- option_full = 0
- option_earned = 1
- option_obscured = 2
- default = 0
-
-class RandomizeMusic(Toggle):
- """
- When enabled, music will be randomized upon connecting to the AP World. (This toggles the base game's Randomize Music function.)
- """
- display_name = "Randomize Music"
-
-class UseMods(Toggle):
- """
- WARNING: This setting is very in beta right now! Enabling it is not recommended for: multiworlds that do not allow releasing items or with Regionsanity enabled
- (some mods add items to regions but don't place them anywhere near that region).
-
- When enabled, items and locations added by other Crystal Project mods will be added to the item and location pools at generation. Mods newer than Editor version 30 are incompatible.
-
- Multiworld host instructions:
- 1. In order to select the mods you'd like to include in randomization, make a folder named "crystal_project_mods" inside your root Archipelago directory.
- 2. Go to your Steam installation folder for Crystal Project (/steamapps/workshop/content/1637730) and find the individual folders for the mods you'd like to include.
- 3. Inside each mod's folder is a mod json. Copy that json to the crystal_project_mods folder you made inside the Archipelago directory.
- 4. If you have a specific order you want to apply the mods, rename the jsons such that they are in alphabetical order in the order you want them to be applied.
- E.g. name the first mod a_modname, the second b_modname, etc.
-
- NOTE: When this setting is on, all Crystal Project players in the multiworld with this setting enabled MUST use the same mods.
-
- The in-game tracking will use special icons for modded locations that will not display their accessibility (as we can only guess at how accessible they are based on coordinates,
- and would prefer the tracking to be as accurate as possible).
-
- When disabled, only base game locations and items will be randomized. You can still use other mods - at your own risk, adventurer - they just won't add checks.
-
- The game will warn you if you open a game with mods that don't match the mods used to generate the multiworld. It will warn you even if this setting is disabled for you, if you start playing with mods.
- """
- display_name = "Use Mods"
-
-@dataclass
-class CrystalProjectOptions(PerGameCommonOptions):
- start_inventory_from_pool: StartInventoryPool
- goal: Goal
- new_world_stone_job_quantity: NewWorldStoneJobQuantity
- clamshell_goal_quantity: ClamshellGoalQuantity
- extra_clamshells_in_pool: ExtraClamshellsInPool
- job_rando: JobRando
- starting_job_quantity: StartingJobQuantity
- disable_sparks: DisableSparks
- kill_bosses_mode: KillBossesMode
- shopsanity: Shopsanity
- regionsanity: Regionsanity
- included_regions: IncludedRegions
- progressive_mount_mode: ProgressiveMountMode
- level_gating: LevelGating
- level_compared_to_enemies: LevelComparedToEnemies
- progressive_level_size: ProgressiveLevelSize
- max_level: MaxLevel
- key_mode: KeyMode
- obscure_routes: ObscureRoutes
- prioritize_crystals: PrioritizeCrystals
- auto_spend_lp: AutoSpendLP
- auto_equip_passives: AutoEquipPassives
- easy_leveling: EasyLeveling
- progressive_equipment_mode: ProgressiveEquipmentMode
- start_with_treasure_finder: StartWithTreasureFinder
- start_with_maps: StartWithMaps
- fill_full_map: FillFullMap
- include_summon_abilities: IncludeSummonAbilities
- include_scholar_abilities: IncludeScholarAbilities
- trap_likelihood: TrapLikelihood
- item_info_mode: ItemInfoMode
- randomize_music: RandomizeMusic
- use_mods: UseMods
-
-crystal_project_option_groups: Dict[str, List[Any]] = {
- "Goal Options": [Goal, ClamshellGoalQuantity, ExtraClamshellsInPool, NewWorldStoneJobQuantity],
- "Location Options": [IncludedRegions, JobRando, StartingJobQuantity, DisableSparks, KillBossesMode, Shopsanity, Regionsanity],
- "Progression Options": [ProgressiveMountMode, LevelGating, LevelComparedToEnemies, ProgressiveLevelSize, MaxLevel, KeyMode, ObscureRoutes, PrioritizeCrystals, AutoSpendLP, AutoEquipPassives, EasyLeveling],
- "Item Pool Options": [ProgressiveEquipmentMode, StartWithTreasureFinder, StartWithMaps, FillFullMap, IncludeSummonAbilities, IncludeScholarAbilities],
- "Bonus Fun": [ItemInfoMode, RandomizeMusic, UseMods]
+from dataclasses import dataclass
+from typing import Type, Any, List
+from typing import Dict
+from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Visibility, Option, OptionGroup
+from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions, StartInventoryPool
+
+
+def create_option_groups() -> List[OptionGroup]:
+ option_group_list: List[OptionGroup] = []
+ for name, options in crystal_project_option_groups.items():
+ option_group_list.append(OptionGroup(name=name, options=options))
+
+ return option_group_list
+#"""Goal Options"""
+class Goal(Choice):
+ """
+ Choose what is required to complete the game.
+
+ Checking the first sign in the Spawning Meadows will tell you the selected goal. A goal items counter is displayed on the in-game party screen in the menu.
+
+ Astley: Defeat Astley in the New World. A New World Stone will be given to the player after obtaining a certain number of jobs (starting Jobs don't count).
+
+ True Astley: A saga awaits you! Collect 4 Deity Eyes and the STEM WARD to challenge Gabriel for the Old World Stone. Then travel to the Old World to defeat Periculum and earn the Proof of Merit.
+ Along the way, gather enough Jobs to unlock the New World Stone. Then you can venture to the New World to defeat true Astley to win!
+
+ Clamshells: Collect enough clamshells for Mañana Man in Seaside Cliffs.
+ """
+ display_name = "Goal"
+ option_astley = 0
+ option_true_astley = 1
+ option_clamshells = 2
+ default = 0
+
+class NewWorldStoneJobQuantity(Range):
+ """
+ If your goal is Astley, select how many Jobs you need to find before being sent the New World Stone for the final fight.
+
+ This option is validated at generation time, and if it is higher than the number of jobs you can obtain, it will be capped to what is possible. By default, the maximum you can obtain is 18.
+
+ Picking Job Rando: Full and decreasing the Starting Job Quantity will increase the maximum by that much. It will also increase if you enable mods that give you more.
+
+ Picking Job Rando: None and Included Regions: Beginner or Advanced will reduce the maximum number available to 4 or 11, respectively.
+ """
+ display_name = "Job count that locks the New World Stone"
+ range_start = 1
+ range_end = 99
+ default = 18
+
+class ClamshellGoalQuantity(Range):
+ """
+ If your goal is Clamshells, select how many you need to win.
+
+ (If your goal is not Clamshells, this setting does nothing. 3 Clamshells will be placed in your pool and the Mañana Man will ask for 2. Don't worry, he's cool with it.)
+ """
+ display_name = "Clamshells needed to win"
+ range_start = 2
+ range_end = 99
+ default = 13
+
+class ExtraClamshellsInPool(Range):
+ """
+ If your goal is Clamshells, pick how many more Clamshells than your goal value to have in the item pool.
+ This option makes it so that you don't have to find every single Clamshell in your pool to win.
+
+ (If your goal is not Clamshells, this setting does nothing.)
+ """
+ display_name = "Extra Clamshells in the pool"
+ range_start = 0
+ range_end = 99
+ default = 6
+
+#"""Location Options"""
+class IncludedRegions(Choice):
+ """
+ Choose which regions to play in! Only locations within your chosen regions will give you checks; the rest will be empty.
+ This can be used to adjust the difficulty and length of your game.
+
+ NOTE: Make sure to include the region that has the finish line of your chosen goal in it!
+ Example: if your goal is Astley, Advanced is considered to have that finish line, so you can pick Advanced, Expert, or All.
+
+ Beginner: Only regions up through the end of Skumparadise will have checks. Finish Line: Clamshells
+
+ Advanced: Regions up to Salmon Bay will have checks. Finish Line: Astley (Note: Level Gating's Level Catch-Up Option is recommended for Astley on Advanced.)
+
+ Expert: Regions up to The Deep Sea will have checks. (Note: The Depths will not be included.)
+
+ All: Every region is included. Finish Line: True Astley
+ """
+ display_name = "Regions to include in game"
+ option_beginner = 0
+ option_advanced = 1
+ option_expert = 2
+ option_all = 3
+ default = 3
+
+class JobRando(Choice):
+ """
+ None: all Jobs are at their normal crystal locations in the vanilla game.
+
+ Crystal: all vanilla Jobs normally received from crystals are chucked into the item pool.
+
+ Full: your starting jobs are randomized, and the rest of the vanilla Jobs are added to the item pool.
+ Adjust the Starting Job Quantity (see below) for how many random Jobs you start with.
+ """
+ display_name = "Job Rando"
+ option_none = 0
+ option_crystal = 1
+ option_full = 2
+ default = 2
+
+class StartingJobQuantity(Range):
+ """
+ Select how many Jobs you start with. (This setting only does anything if JobRando is set to Full.)
+ """
+ display_name = "Starting Job Quantity"
+ range_start = 1
+ range_end = 6
+ default = 6
+
+class DisableSparks(Toggle):
+ """
+ When enabled, enemy sparks are disabled: enemies will completely ignore the player, and touching them does not start combat.
+ An Enable/Disable Sparks button will appear in the game's Archipelago menu that you can use to toggle spark disabling on and off.
+ Explore unbothered by fiery plebeians, re-enable them when you deign to grind XP or earn money, and re-disable them when you've had enough of their nonsense!
+
+ However: if Kill Bosses is enabled, then boss sparks still have your number (a.k.a. chase you and start combat). They can sense your murderous intent.
+ """
+ display_name = "Disable Sparks"
+
+class KillBossesMode(Toggle):
+ """
+ When enabled, defeating a boss will provide checks.
+
+ WARNING: If you restrict the Included Regions to Beginner or Advanced, checks will require defeating level 50+ bosses in some of the early areas!
+ You may want to set the Level Gating option to Level Catch-Up. Without it, you are signing up to either grind or fight the bosses under-leveled.
+ """
+ display_name = "Kill Bosses Mode"
+
+class Shopsanity(Choice):
+ """
+ When enabled, all shop inventories will be replaced with checks. Be prepared, adventurer.
+
+ Choosing "enabled and hint" will, when you CLOSE the store, automatically create a hint for every item you did not purchase
+ so other players will realize you have betrayed them by refusing to purchase their key progression item!
+ """
+ display_name = "Shopsanity"
+ option_disabled = 0
+ option_enabled = 1
+ option_enabled_and_hint = 2
+ default = 0
+
+class Regionsanity(Choice):
+ """
+ Nothing I have tried has been able to drive the citizens of Sequoia to collect enough crystals!
+ Adventurers have had TOO MUCH freedom! From now on, you will adventure where I tell you to.
+ Unless you have a pass from ME, you won't be able to do any adventuring!
+ - Grandmaster
+
+ When enabled, the grandmaster will not permit you to interact with anything in a region
+ without receiving his "official permission" (i.e. "Item - Spawning Meadows Pass").
+ However, the Overpass and Underpass are regions of lawlessness where the Grandmaster has no authority! ;)
+ (You're also still allowed to use the save points, we won't tell.)
+
+ If you put any region pass items in your starting inventory, the first one will be chosen as your starting region.
+
+ If regionsanity is set to extreme, the Grandmaster won't even let you walk through regions you don't have the pass for.
+ Spend more than 10 seconds in a region without a pass and the Grandmaster will teleport you!
+
+ You will start the game with a pass for one reachable region.
+ """
+ display_name = "Regionsanity"
+ option_disabled = 0
+ option_enabled = 1
+ option_extreme = 2
+ default = 0
+
+#"""Progression Options"""
+class ProgressiveMountMode(DefaultOnToggle):
+ """
+ When enabled, all mount items are combined into a Progressive Mount Instrument and will be received in the approximate order you would receive them in the vanilla game:
+ Quintar Pass -> Quintar Flute -> Ibek Bell -> Owl Drum -> Salmon Violin -> Salmon Cello -> Quintar Ocarina
+
+ When disabled, different mount types are separated:
+ Progressive Quintar Flute (Quintar Pass -> Quintar Flute -> Quintar Ocarina),
+ Progressive Salmon Violin (Salmon Violin -> Salmon Cello),
+ Ibek Bell,
+ and Owl Drum.
+ """
+ display_name = "Progressive Mount Mode"
+
+class LevelGating(Choice):
+ """
+ When enabled, the party's level is considered for Archipelago logic, and Progressive Level items are added to the pool. (This won't stop you from beating the game at level 3. ^_^)
+
+ None: Level gating is disabled. No Progressive Levels in the pool.
+
+ Level Passes: Progressive Levels are added to the pool. They do not affect the party's actual level or level cap. Archipelago will expect you to collect them to access level-gated areas,
+ and in-game tracking will light up checks that you have access to based on that level.
+
+ Level Capped: The party's maximum level is hard capped. Progressive Levels are added to the pool. Collecting them allows your party to gain more levels, and in-game tracking will light up
+ checks that you have access to based on that max level.
+
+ Level Catch-Up: Progressive Levels are added to the pool. Collecting them will help your party catch up in levels (and LP!) based on the number you've collected. For example, if the Progressive Level
+ Size Setting is 6 (see below), 1 Progressive Level will bring your party's level up to 6, 2 Progressive Levels will bring your party's level up to 12, etc. If your party
+ has already reached that level, no extra levels will be granted. 2 LP are granted per 1 level granted. In-game tracking will light up checks that you have access to based on the number you have collected.
+
+ Level Set: A combination of Level Catch-Up and Capped. The player will always be at the level set by the Progressive Level. There is no escape.
+ """
+ display_name = "Level Gating"
+ option_none = 0
+ option_level_passes = 1
+ option_level_capped = 2
+ option_level_catch_up = 3
+ option_level_set = 4
+ default = 1
+
+class LevelComparedToEnemies(Range):
+ """
+ If Level Gating is on, this option changes what level you're expected to fight enemies.
+ Set it higher if you want to be a higher level than enemies when you enter a region, or lower if you want to be lower.
+
+ For example, if this is set to 5, and the enemy level of a region is 12, then the Level Gating options would require you to unlock level 17 (or for Level Capped, max level 17) for that region.
+ If it's set to -5, and the enemy level of a region is 12, then the Level Gating options would require you to unlock level 7 (or for Level Capped, max level 7) for that region.
+
+ Default is 0, or on-level for the enemy level of a region.
+
+ Note: Remember to increase your Max Level (see below) if you want regions with high-level enemies to still be lower level than you.
+ Note #2: Spark color changes: red at -10 levels, orange at -5, green at +3, and grey +10. (Though enemies can be 3-5 levels above the min enemy level for a region.)
+ """
+ display_name = "Level compared to enemies"
+ range_start = -10
+ range_end = 10
+ default = 0
+
+
+class ProgressiveLevelSize(Range):
+ """
+ If Level Gating is on, Progressive Levels will be added to the item pool. This sets the number of levels that an individual Progressive Level will grant, as well as the starting level expectation.
+
+ For example, if Progressive Level Size is 6, the three Level Gating options would behave like this:
+ Level Passes - At the start, areas up to level 6 are considered in logic. The first Progressive Level you collect will signal that areas up to 12 are now logic, the second up to 18, etc.
+ Level Capped - Your party's level cap starts at 6. The first Progressive Level you collect will increase your party's level cap to 12, the second to 18, and so on.
+ Level Catch-Up - Your party is leveled up to 6 at the start. The first Progressive Level you collect will bring your party's level up to 12 if it isn't already, the second up to 18, and so on.
+ Level Set - Your party's level and level cap start at 6. The first Progressive Level you collect will increase your party's level and level cap to 12, the second to 18, and so on.
+
+ This setting will only increase your party's starting level (3 by default) if you pick Level Catch-Up.
+ """
+ display_name = "Progressive Level Size"
+ range_start = 3
+ range_end = 10
+ default = 6
+
+class MaxLevel(Range):
+ """
+ This allows your party to level past the default maximum level of 60. If Level Gating is on, increasing this number will also put more Progressive Levels in your pool.
+
+ Set it below 60 at your own risk <3
+ """
+ display_name = "Max Level"
+ range_start = 3
+ range_end = 99
+ default = 60
+
+class KeyMode(Choice):
+ """
+ Skeleton Key: Only the Skeleton Key, which can open any locked door, will be in your item pool.
+
+ Key Ring: Keys for individual dungeons will be grouped into Key Rings. Each Key Ring will give all keys needed to complete one dungeon. The Skeleton Key will still be in the pool.
+
+ Vanilla: All vanilla keys will be in the item pool.
+
+ Skelefree: Same as the named mode, but removes the Skeleton Key.
+ """
+ display_name = "Key Mode"
+ option_skeleton = 0
+ option_key_ring = 1
+ option_vanilla = 2
+ option_key_ring_skelefree = 3
+ option_vanilla_skelefree = 4
+ default = 2
+
+class ObscureRoutes(Toggle):
+ """
+ When enabled, connections between regions that are difficult to find will be expected in logic.
+ """
+ display_name = "Obscure Routes"
+
+class PrioritizeCrystals(DefaultOnToggle):
+ """
+ When enabled, crystals will be prioritized when placing progression items.
+
+ While you're here, did you know that all YAMLs for all worlds include a priority_locations and an exclude_locations option?
+ You can use these to flag any individual location or a location group as priority or excluded.
+ Priority locations will be chosen first when placing progression items, and excluded locations will be chosen last.
+
+ Here's an example:
+ priority_locations = ["Crystals", "Region Completions", "Bosses", "Salmon River NPC - Win the Salmon Race"]
+
+ Crystal Project's location groups: Crystals, Bosses, Region Completions, and Shops
+ """
+ display_name = "Prioritize Crystals"
+
+class AutoSpendLP(Toggle):
+ """
+ When enabled, every time a character earns LP, it will automatically spend LP on abilities or passives.
+ Extra LP earned on a job that is maxed will be sent to your sub job or, if that is also maxed, a random un-maxed job.
+ """
+ display_name = "Automatically Spend LP"
+
+class AutoEquipPassives(Toggle):
+ """
+ When enabled, every time a character unlocks a new passive, it will equip it immediately if enough passive points are available.
+ Passives with drawbacks, that enable equipping more gear types, or that modify threat are not automatically equipped.
+ If mods are enabled, all passives are auto-equipped with no exclusions.
+ """
+ display_name = "Automatically Equip Passives"
+
+class EasyLeveling(Toggle):
+ """
+ When enabled, characters will not receive reduced experience for being dead or for being a higher level than the fought enemy.
+ """
+ display_name = "Easy Leveling"
+
+#"""Item Pool Options"""
+class ProgressiveEquipmentMode(DefaultOnToggle):
+ """
+ When enabled, each category of weapon and armor will be sent to the player in progressive order from weakest to strongest.
+
+ When disabled, weapons and armor are fully randomized.
+ """
+ display_name = "Progressive Equipment"
+
+class StartWithTreasureFinder(DefaultOnToggle):
+ """
+ When enabled, the player will start with the Treasure Finder in their inventory. When disabled, it will be in the item pool.
+
+ NOTE: Having the Treasure Finder is required for the in-game minimap to display nearby checks.
+ """
+ display_name = "Begin with Treasure Finder"
+
+class StartWithMaps(DefaultOnToggle):
+ """
+ When enabled, the player will start with all maps in their inventory.
+
+ Note: The in-game minimap hides when in an area without its map. The world map will display check location icons but will not draw an area without its map.
+ """
+ display_name = "Begin with Area Maps"
+
+class FillFullMap(Toggle):
+ """
+ When enabled, the world map will start filled in for areas that the player has a map item for.
+ """
+ display_name = "Fill Full Map"
+
+class IncludeSummonAbilities(DefaultOnToggle):
+ """
+ When enabled, Summons are added to the item pool.
+
+ Note: It is not recommended to turn this off with Job Rando: Full and a low number of starting Jobs. You don't want to get stuck with just a Summoner who can only summon Pinga!
+ (I'm sorry, Pinga.)
+ """
+ display_name = "Include Summons in the item pool"
+
+class IncludeScholarAbilities(DefaultOnToggle):
+ """
+ When enabled, Scholar abilities are added to the item pool. They can still be learned as normal from enemies.
+ """
+ display_name = "Include Scholar Abilities in the item pool"
+
+#"""Bonus Fun"""
+class TrapLikelihood(Range):
+ """
+ This is the likelihood that a trap will replace a filler check. A value of 0 means no traps.
+ """
+ display_name = "Trap Likelihood"
+ range_start = 0
+ range_end = 100
+ default = 0
+
+class ItemInfoMode(Choice):
+ """
+ For Full, all treasure and store icons on the map will display if they are progression, useful, or filler items.
+
+ For Earned, all treasure and store icons on the map will display as mimics until you complete 50% of your checks.
+
+ For Obscured, all treasure and store icons on the map will display as mimics permanently.
+ If you find skipping treasures is distasteful but part of your brain always wants to be efficient, this option is for you!
+ It's also good for a race environment.
+ """
+ display_name = "Item Info Mode"
+ option_full = 0
+ option_earned = 1
+ option_obscured = 2
+ default = 0
+
+class RandomizeMusic(Toggle):
+ """
+ When enabled, music will be randomized upon connecting to the AP World. (This toggles the base game's Randomize Music function.)
+ """
+ display_name = "Randomize Music"
+
+class UseMods(Toggle):
+ """
+ WARNING: This setting is very in beta right now! Enabling it is not recommended for: multiworlds that do not allow releasing items or with Regionsanity enabled
+ (some mods add items to regions but don't place them anywhere near that region).
+
+ When enabled, items and locations added by other Crystal Project mods will be added to the item and location pools at generation. Mods newer than Editor version 30 are incompatible.
+
+ Multiworld host instructions:
+ 1. In order to select the mods you'd like to include in randomization, make a folder named "crystal_project_mods" inside your root Archipelago directory.
+ 2. Go to your Steam installation folder for Crystal Project (/steamapps/workshop/content/1637730) and find the individual folders for the mods you'd like to include.
+ 3. Inside each mod's folder is a mod json. Copy that json to the crystal_project_mods folder you made inside the Archipelago directory.
+ 4. If you have a specific order you want to apply the mods, rename the jsons such that they are in alphabetical order in the order you want them to be applied.
+ E.g. name the first mod a_modname, the second b_modname, etc.
+
+ NOTE: When this setting is on, all Crystal Project players in the multiworld with this setting enabled MUST use the same mods.
+
+ The in-game tracking will use special icons for modded locations that will not display their accessibility (as we can only guess at how accessible they are based on coordinates,
+ and would prefer the tracking to be as accurate as possible).
+
+ When disabled, only base game locations and items will be randomized. You can still use other mods - at your own risk, adventurer - they just won't add checks.
+
+ The game will warn you if you open a game with mods that don't match the mods used to generate the multiworld. It will warn you even if this setting is disabled for you, if you start playing with mods.
+ """
+ display_name = "Use Mods"
+
+@dataclass
+class CrystalProjectOptions(PerGameCommonOptions):
+ start_inventory_from_pool: StartInventoryPool
+ goal: Goal
+ new_world_stone_job_quantity: NewWorldStoneJobQuantity
+ clamshell_goal_quantity: ClamshellGoalQuantity
+ extra_clamshells_in_pool: ExtraClamshellsInPool
+ job_rando: JobRando
+ starting_job_quantity: StartingJobQuantity
+ disable_sparks: DisableSparks
+ kill_bosses_mode: KillBossesMode
+ shopsanity: Shopsanity
+ regionsanity: Regionsanity
+ included_regions: IncludedRegions
+ progressive_mount_mode: ProgressiveMountMode
+ level_gating: LevelGating
+ level_compared_to_enemies: LevelComparedToEnemies
+ progressive_level_size: ProgressiveLevelSize
+ max_level: MaxLevel
+ key_mode: KeyMode
+ obscure_routes: ObscureRoutes
+ prioritize_crystals: PrioritizeCrystals
+ auto_spend_lp: AutoSpendLP
+ auto_equip_passives: AutoEquipPassives
+ easy_leveling: EasyLeveling
+ progressive_equipment_mode: ProgressiveEquipmentMode
+ start_with_treasure_finder: StartWithTreasureFinder
+ start_with_maps: StartWithMaps
+ fill_full_map: FillFullMap
+ include_summon_abilities: IncludeSummonAbilities
+ include_scholar_abilities: IncludeScholarAbilities
+ trap_likelihood: TrapLikelihood
+ item_info_mode: ItemInfoMode
+ randomize_music: RandomizeMusic
+ use_mods: UseMods
+
+crystal_project_option_groups: Dict[str, List[Any]] = {
+ "Goal Options": [Goal, ClamshellGoalQuantity, ExtraClamshellsInPool, NewWorldStoneJobQuantity],
+ "Location Options": [IncludedRegions, JobRando, StartingJobQuantity, DisableSparks, KillBossesMode, Shopsanity, Regionsanity],
+ "Progression Options": [ProgressiveMountMode, LevelGating, LevelComparedToEnemies, ProgressiveLevelSize, MaxLevel, KeyMode, ObscureRoutes, PrioritizeCrystals, AutoSpendLP, AutoEquipPassives, EasyLeveling],
+ "Item Pool Options": [ProgressiveEquipmentMode, StartWithTreasureFinder, StartWithMaps, FillFullMap, IncludeSummonAbilities, IncludeScholarAbilities],
+ "Bonus Fun": [ItemInfoMode, RandomizeMusic, UseMods]
}
\ No newline at end of file
diff --git a/worlds/crystal_project/presets.py b/worlds/crystal_project/presets.py
index 23386af89b11..c71523224037 100644
--- a/worlds/crystal_project/presets.py
+++ b/worlds/crystal_project/presets.py
@@ -1,27 +1,27 @@
-from Options import ProgressionBalancing
-from .options import *
-from .constants.key_items import *
-
-explorer_mode_options = {
- "progression_balancing": ProgressionBalancing.default,
- "goal": Goal.option_clamshells,
- "clamshell_goal_quantity": 50,
- "extra_clamshells_in_pool": 10,
- "shopsanity": Shopsanity.option_disabled,
- "regionsanity": Regionsanity.option_extreme,
- "progressive_mount_mode": ProgressiveMountMode.option_false,
- "level_gating": LevelGating.option_level_catch_up,
- "progressive_level_size": 10,
- "max_level": 99,
- "key_mode": KeyMode.option_vanilla_skelefree,
- "obscure_routes": True,
- "start_inventory_from_pool": {PROGRESSIVE_LEVEL: 9},
- "auto_spend_lp": True,
- "auto_equip_passives": True,
- "item_info_mode": ItemInfoMode.option_obscured,
- "disable_sparks": True,
-}
-
-crystal_project_options_presets: Dict[str, Dict[str, Any]] = {
- "Explorer Mode": explorer_mode_options,
-}
+from Options import ProgressionBalancing
+from .options import *
+from .constants.key_items import *
+
+explorer_mode_options = {
+ "progression_balancing": ProgressionBalancing.default,
+ "goal": Goal.option_clamshells,
+ "clamshell_goal_quantity": 50,
+ "extra_clamshells_in_pool": 10,
+ "shopsanity": Shopsanity.option_disabled,
+ "regionsanity": Regionsanity.option_extreme,
+ "progressive_mount_mode": ProgressiveMountMode.option_false,
+ "level_gating": LevelGating.option_level_catch_up,
+ "progressive_level_size": 10,
+ "max_level": 99,
+ "key_mode": KeyMode.option_vanilla_skelefree,
+ "obscure_routes": True,
+ "start_inventory_from_pool": {PROGRESSIVE_LEVEL: 9},
+ "auto_spend_lp": True,
+ "auto_equip_passives": True,
+ "item_info_mode": ItemInfoMode.option_obscured,
+ "disable_sparks": True,
+}
+
+crystal_project_options_presets: Dict[str, Dict[str, Any]] = {
+ "Explorer Mode": explorer_mode_options,
+}
diff --git a/worlds/crystal_project/regions.py b/worlds/crystal_project/regions.py
index c920765af649..1e73a0ac9034 100644
--- a/worlds/crystal_project/regions.py
+++ b/worlds/crystal_project/regions.py
@@ -1,969 +1,969 @@
-from typing import List, Dict, Tuple, Callable, TYPE_CHECKING
-from BaseClasses import Region, Location, CollectionState
-from .options import CrystalProjectOptions
-from .locations import LocationData
-from .items import display_region_name_to_pass_dict
-from .rules import CrystalProjectLogic
-from .constants.keys import *
-from .constants.key_items import *
-from .constants.ap_regions import *
-from .constants.display_regions import *
-from .constants.level_requirements import *
-from .constants.teleport_stones import *
-from .constants.region_passes import *
-
-if TYPE_CHECKING:
- from . import CrystalProjectWorld
-
-ap_region_to_display_region_dictionary: Dict[str, str] = {}
-
-display_region_subregions_dictionary: Dict[str, List[str]] = {
- #Beginner
- MENU_DISPLAY_NAME: MENU_DISPLAY_SUBREGIONS,
- SPAWNING_MEADOWS_DISPLAY_NAME: SPAWNING_MEADOWS_DISPLAY_SUBREGIONS,
- DELENDE_DISPLAY_NAME: DELENDE_DISPLAY_SUBREGIONS,
- SOILED_DEN_DISPLAY_NAME: SOILED_DEN_DISPLAY_SUBREGIONS,
- THE_PALE_GROTTO_DISPLAY_NAME: THE_PALE_GROTTO_DISPLAY_SUBREGIONS,
- SEASIDE_CLIFFS_DISPLAY_NAME: SEASIDE_CLIFFS_DISPLAY_SUBREGIONS,
- DRAFT_SHAFT_CONDUIT_DISPLAY_NAME: DRAFT_SHAFT_CONDUIT_DISPLAY_SUBREGIONS,
- MERCURY_SHRINE_DISPLAY_NAME: MERCURY_SHRINE_DISPLAY_SUBREGIONS,
- YAMAGAWA_MA_DISPLAY_NAME: YAMAGAWA_MA_DISPLAY_SUBREGIONS,
- PROVING_MEADOWS_DISPLAY_NAME: PROVING_MEADOWS_DISPLAY_SUBREGIONS,
- SKUMPARADISE_DISPLAY_NAME: SKUMPARADISE_DISPLAY_SUBREGIONS,
- #Advanced
- CAPITAL_SEQUOIA_DISPLAY_NAME: CAPITAL_SEQUOIA_DISPLAY_SUBREGIONS,
- JOJO_SEWERS_DISPLAY_NAME: JOJO_SEWERS_DISPLAY_SUBREGIONS,
- BOOMER_SOCIETY_DISPLAY_NAME: BOOMER_SOCIETY_DISPLAY_SUBREGIONS,
- ROLLING_QUINTAR_FIELDS_DISPLAY_NAME: ROLLING_QUINTAR_FIELDS_DISPLAY_SUBREGIONS,
- QUINTAR_NEST_DISPLAY_NAME: QUINTAR_NEST_DISPLAY_SUBREGIONS,
- QUINTAR_SANCTUM_DISPLAY_NAME: QUINTAR_SANCTUM_DISPLAY_SUBREGIONS,
- CAPITAL_JAIL_DISPLAY_NAME: CAPITAL_JAIL_DISPLAY_SUBREGIONS,
- CAPITAL_PIPELINE_DISPLAY_NAME: CAPITAL_PIPELINE_DISPLAY_SUBREGIONS,
- COBBLESTONE_CRAG_DISPLAY_NAME: COBBLESTONE_CRAG_DISPLAY_SUBREGIONS,
- OKIMOTO_NS_DISPLAY_NAME: OKIMOTO_NS_DISPLAY_SUBREGIONS,
- GREENSHIRE_REPRISE_DISPLAY_NAME: GREENSHIRE_REPRISE_DISPLAY_SUBREGIONS,
- SALMON_PASS_DISPLAY_NAME: SALMON_PASS_DISPLAY_SUBREGIONS,
- SALMON_RIVER_DISPLAY_NAME: SALMON_RIVER_DISPLAY_SUBREGIONS,
- SHOUDU_WATERFRONT_DISPLAY_NAME: SHOUDU_WATERFRONT_DISPLAY_SUBREGIONS,
- POKO_POKO_DESERT_DISPLAY_NAME: POKO_POKO_DESERT_DISPLAY_SUBREGIONS,
- SARA_SARA_BAZAAR_DISPLAY_NAME: SARA_SARA_BAZAAR_DISPLAY_SUBREGIONS,
- SARA_SARA_BEACH_EAST_DISPLAY_NAME: SARA_SARA_BEACH_EAST_DISPLAY_SUBREGIONS,
- SARA_SARA_BEACH_WEST_DISPLAY_NAME: SARA_SARA_BEACH_WEST_DISPLAY_SUBREGIONS,
- ANCIENT_RESERVOIR_DISPLAY_NAME: ANCIENT_RESERVOIR_DISPLAY_SUBREGIONS,
- SALMON_BAY_DISPLAY_NAME: SALMON_BAY_DISPLAY_SUBREGIONS,
- #Expert
- THE_OPEN_SEA_DISPLAY_NAME: THE_OPEN_SEA_DISPLAY_SUBREGIONS,
- SHOUDU_PROVINCE_DISPLAY_NAME: SHOUDU_PROVINCE_DISPLAY_SUBREGIONS,
- THE_UNDERCITY_DISPLAY_NAME: THE_UNDERCITY_DISPLAY_SUBREGIONS,
- GANYMEDE_SHRINE_DISPLAY_NAME: GANYMEDE_SHRINE_DISPLAY_SUBREGIONS,
- BEAURIOR_VOLCANO_DISPLAY_NAME: BEAURIOR_VOLCANO_DISPLAY_SUBREGIONS,
- BEAURIOR_ROCK_DISPLAY_NAME: BEAURIOR_ROCK_DISPLAY_SUBREGIONS,
- LAKE_DELENDE_DISPLAY_NAME: LAKE_DELENDE_DISPLAY_SUBREGIONS,
- QUINTAR_RESERVE_DISPLAY_NAME: QUINTAR_RESERVE_DISPLAY_SUBREGIONS,
- DIONE_SHRINE_DISPLAY_NAME: DIONE_SHRINE_DISPLAY_SUBREGIONS,
- QUINTAR_MAUSOLEUM_DISPLAY_NAME: QUINTAR_MAUSOLEUM_DISPLAY_SUBREGIONS,
- EASTERN_CHASM_DISPLAY_NAME: EASTERN_CHASM_DISPLAY_SUBREGIONS,
- TALL_TALL_HEIGHTS_DISPLAY_NAME: TALL_TALL_HEIGHTS_DISPLAY_SUBREGIONS,
- NORTHERN_CAVE_DISPLAY_NAME: NORTHERN_CAVE_DISPLAY_SUBREGIONS,
- LANDS_END_DISPLAY_NAME: LANDS_END_DISPLAY_SUBREGIONS,
- SLIP_GLIDE_RIDE_DISPLAY_NAME: SLIP_GLIDE_RIDE_DISPLAY_SUBREGIONS,
- SEQUOIA_ATHENAEUM_DISPLAY_NAME: SEQUOIA_ATHENAEUM_DISPLAY_SUBREGIONS,
- NORTHERN_STRETCH_DISPLAY_NAME: NORTHERN_STRETCH_DISPLAY_SUBREGIONS,
- CASTLE_RAMPARTS_DISPLAY_NAME: CASTLE_RAMPARTS_DISPLAY_SUBREGIONS,
- THE_CHALICE_OF_TAR_DISPLAY_NAME: THE_CHALICE_OF_TAR_DISPLAY_SUBREGIONS,
- FLYERS_CRAG_DISPLAY_NAME: FLYERS_CRAG_DISPLAY_SUBREGIONS,
- JIDAMBA_TANGLE_DISPLAY_NAME: JIDAMBA_TANGLE_DISPLAY_SUBREGIONS,
- JIDAMBA_EACLANEYA_DISPLAY_NAME: JIDAMBA_EACLANEYA_DISPLAY_SUBREGIONS,
- THE_DEEP_SEA_DISPLAY_NAME: THE_DEEP_SEA_DISPLAY_SUBREGIONS,
- NEPTUNE_SHRINE_DISPLAY_NAME: NEPTUNE_SHRINE_DISPLAY_SUBREGIONS,
- JADE_CAVERN_DISPLAY_NAME: JADE_CAVERN_DISPLAY_SUBREGIONS,
- CONTINENTAL_TRAM_DISPLAY_NAME: CONTINENTAL_TRAM_DISPLAY_SUBREGIONS,
- #End Game
- ANCIENT_LABYRINTH_DISPLAY_NAME: ANCIENT_LABYRINTH_DISPLAY_SUBREGIONS,
- THE_SEQUOIA_DISPLAY_NAME: THE_SEQUOIA_DISPLAY_SUBREGIONS,
- THE_DEPTHS_DISPLAY_NAME: THE_DEPTHS_DISPLAY_SUBREGIONS,
- CASTLE_SEQUOIA_DISPLAY_NAME: CASTLE_SEQUOIA_DISPLAY_SUBREGIONS,
- THE_OLD_WORLD_DISPLAY_NAME: THE_OLD_WORLD_DISPLAY_SUBREGIONS,
- THE_NEW_WORLD_DISPLAY_NAME: THE_NEW_WORLD_DISPLAY_SUBREGIONS,
- MODDED_ZONE_DISPLAY_NAME: MODDED_ZONE_DISPLAY_SUBREGIONS,
-}
-
-display_region_levels_dictionary: Dict[str, Tuple[int, int]] = {
- #Beginner
- MENU_DISPLAY_NAME: (0, 0),
- SPAWNING_MEADOWS_DISPLAY_NAME: (3, 3),
- DELENDE_DISPLAY_NAME: (5, 10),
- SOILED_DEN_DISPLAY_NAME: (7, 7),
- THE_PALE_GROTTO_DISPLAY_NAME: (7, 10),
- SEASIDE_CLIFFS_DISPLAY_NAME: (10, 15),
- DRAFT_SHAFT_CONDUIT_DISPLAY_NAME: (12, 12),
- MERCURY_SHRINE_DISPLAY_NAME: (0, 0),
- YAMAGAWA_MA_DISPLAY_NAME: (15, 15),
- PROVING_MEADOWS_DISPLAY_NAME: (0, 0),
- SKUMPARADISE_DISPLAY_NAME: (15, 17),
- #Advanced
- CAPITAL_SEQUOIA_DISPLAY_NAME: (0, 0),
- JOJO_SEWERS_DISPLAY_NAME: (20, 22),
- BOOMER_SOCIETY_DISPLAY_NAME: (0, 0),
- ROLLING_QUINTAR_FIELDS_DISPLAY_NAME: (18, 21),
- QUINTAR_NEST_DISPLAY_NAME: (18, 22),
- QUINTAR_SANCTUM_DISPLAY_NAME: (21, 21),
- CAPITAL_JAIL_DISPLAY_NAME: (24, 27),
- CAPITAL_PIPELINE_DISPLAY_NAME: (50, 50),
- COBBLESTONE_CRAG_DISPLAY_NAME: (0, 0),
- OKIMOTO_NS_DISPLAY_NAME: (27, 31),
- GREENSHIRE_REPRISE_DISPLAY_NAME: (33, 33),
- SALMON_PASS_DISPLAY_NAME: (0, 0),
- SALMON_RIVER_DISPLAY_NAME: (26, 29),
- SHOUDU_WATERFRONT_DISPLAY_NAME: (0, 0),
- POKO_POKO_DESERT_DISPLAY_NAME: (POKO_POKO_ENEMY_LEVEL, 32),
- SARA_SARA_BAZAAR_DISPLAY_NAME: (0, 0),
- SARA_SARA_BEACH_EAST_DISPLAY_NAME: (30, 30),
- SARA_SARA_BEACH_WEST_DISPLAY_NAME: (38, 40),
- ANCIENT_RESERVOIR_DISPLAY_NAME: (33, 35),
- #IBEK_CAVE_AP_REGION: (35, 35),
- SALMON_BAY_DISPLAY_NAME: (0, 0),
- #Expert
- THE_OPEN_SEA_DISPLAY_NAME: (54, 56),
- SHOUDU_PROVINCE_DISPLAY_NAME: (36, 37),
- THE_UNDERCITY_DISPLAY_NAME: (37, 39),
- GANYMEDE_SHRINE_DISPLAY_NAME: (0, 0),
- BEAURIOR_VOLCANO_DISPLAY_NAME: (37, 37),
- BEAURIOR_ROCK_DISPLAY_NAME: (38, 40),
- LAKE_DELENDE_DISPLAY_NAME: (40, 40),
- QUINTAR_RESERVE_DISPLAY_NAME: (0, 0),
- DIONE_SHRINE_DISPLAY_NAME: (0, 0),
- QUINTAR_MAUSOLEUM_DISPLAY_NAME: (54, 56),
- EASTERN_CHASM_DISPLAY_NAME: (0, 0),
- TALL_TALL_HEIGHTS_DISPLAY_NAME: (41, 45),
- NORTHERN_CAVE_DISPLAY_NAME: (43, 44),
- LANDS_END_DISPLAY_NAME: (44, 47),
- SLIP_GLIDE_RIDE_DISPLAY_NAME: (46, 48),
- SEQUOIA_ATHENAEUM_DISPLAY_NAME: (0, 0),
- NORTHERN_STRETCH_DISPLAY_NAME: (0, 0),
- CASTLE_RAMPARTS_DISPLAY_NAME: (50, 50),
- THE_CHALICE_OF_TAR_DISPLAY_NAME: (60, 60),
- FLYERS_CRAG_DISPLAY_NAME: (0, 0),
- JIDAMBA_TANGLE_DISPLAY_NAME: (50, 54),
- JIDAMBA_EACLANEYA_DISPLAY_NAME: (54, 57),
- THE_DEEP_SEA_DISPLAY_NAME: (58, 64),
- NEPTUNE_SHRINE_DISPLAY_NAME: (0, 0),
- JADE_CAVERN_DISPLAY_NAME: (57, 57),
- CONTINENTAL_TRAM_DISPLAY_NAME: (0, 0),
- #End Game
- ANCIENT_LABYRINTH_DISPLAY_NAME: (62, 66),
- THE_SEQUOIA_DISPLAY_NAME: (60, 63),
- THE_DEPTHS_DISPLAY_NAME: (63, 65),
- CASTLE_SEQUOIA_DISPLAY_NAME: (56, 59),
- THE_OLD_WORLD_DISPLAY_NAME: (PERICULUM_FIGHT_LEVEL, PERICULUM_FIGHT_LEVEL),
- THE_NEW_WORLD_DISPLAY_NAME: (60, 60),
- MODDED_ZONE_DISPLAY_NAME: (30, 30),
-}
-
-rules_on_display_regions: Dict[str, Callable[[CollectionState], bool]] = {}
-
-class CrystalProjectLocation(Location):
- game: str = "CrystalProject"
-
- def __init__(self, player: int, name: str = " ", address = None, parent=None):
- super().__init__(player, name, address, parent)
-
-def init_ap_region_to_display_region_dictionary() -> None:
- for display_region in display_region_subregions_dictionary:
- for ap_region in display_region_subregions_dictionary[display_region]:
- ap_region_to_display_region_dictionary[ap_region] = display_region
-
-def init_areas(world: "CrystalProjectWorld", locations: List[LocationData], options: CrystalProjectOptions) -> None:
- multiworld = world.multiworld
- player = world.player
- logic = CrystalProjectLogic(player, options)
-
- rules_on_display_regions[MODDED_ZONE_AP_REGION] = lambda state: True
-
- for display_region in display_region_levels_dictionary:
- if world.options.regionsanity.value != world.options.regionsanity.option_disabled and display_region != MODDED_ZONE_AP_REGION and display_region != MENU_AP_REGION:
- rules_on_display_regions[display_region] = lambda state, lambda_region = display_region: (logic.is_area_in_level_range(state, display_region_levels_dictionary[lambda_region][0])
- and state.has(display_region_name_to_pass_dict[lambda_region], player))
-
- rules_on_display_regions[MODDED_ZONE_AP_REGION] = combine_callables(rules_on_display_regions[MODDED_ZONE_AP_REGION], rules_on_display_regions[display_region])
- else:
- rules_on_display_regions[display_region] = lambda state, lambda_region = display_region: (logic.is_area_in_level_range(state, display_region_levels_dictionary[lambda_region][0]))
-
- locations_per_region = get_locations_per_ap_region(locations)
-
- excluded = False
-
- beginner_regions = [
- create_display_region(world, player, locations_per_region, MENU_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SPAWNING_MEADOWS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, DELENDE_DISPLAY_NAME, excluded),
- # bumped up mercury shrine because region order influences shop price NOTE TO DRAGONS, DO NOT MOVE WITHOUT REASON!!
- create_display_region(world, player, locations_per_region, MERCURY_SHRINE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SOILED_DEN_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_PALE_GROTTO_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SEASIDE_CLIFFS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, YAMAGAWA_MA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, PROVING_MEADOWS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SKUMPARADISE_DISPLAY_NAME, excluded),
- ]
-
- if (options.included_regions == options.included_regions.option_advanced or
- options.included_regions == options.included_regions.option_expert or
- options.included_regions == options.included_regions.option_all):
- excluded = False
- else:
- excluded = True
-
- advanced_regions = [
- create_display_region(world, player, locations_per_region, CAPITAL_SEQUOIA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, JOJO_SEWERS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, BOOMER_SOCIETY_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, QUINTAR_NEST_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, QUINTAR_SANCTUM_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, CAPITAL_JAIL_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, CAPITAL_PIPELINE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, COBBLESTONE_CRAG_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, OKIMOTO_NS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, GREENSHIRE_REPRISE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SALMON_PASS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SALMON_RIVER_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SHOUDU_WATERFRONT_DISPLAY_NAME, excluded), #moved from Expert to Advanced
- create_display_region(world, player, locations_per_region, POKO_POKO_DESERT_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SARA_SARA_BAZAAR_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SARA_SARA_BEACH_EAST_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SARA_SARA_BEACH_WEST_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, ANCIENT_RESERVOIR_DISPLAY_NAME, excluded),
- #create_display_region(world, player, locations_per_region, IBEK_CAVE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SALMON_BAY_DISPLAY_NAME, excluded),
- ]
-
- if (options.included_regions == options.included_regions.option_expert or
- options.included_regions == options.included_regions.option_all):
- excluded = False
- else:
- excluded = True
-
- expert_regions = [
- create_display_region(world, player, locations_per_region, THE_OPEN_SEA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SHOUDU_PROVINCE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_UNDERCITY_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, GANYMEDE_SHRINE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, BEAURIOR_VOLCANO_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, BEAURIOR_ROCK_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, LAKE_DELENDE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, QUINTAR_RESERVE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, DIONE_SHRINE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, QUINTAR_MAUSOLEUM_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, EASTERN_CHASM_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, TALL_TALL_HEIGHTS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, NORTHERN_CAVE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, LANDS_END_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SLIP_GLIDE_RIDE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, SEQUOIA_ATHENAEUM_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, NORTHERN_STRETCH_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, CASTLE_RAMPARTS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_CHALICE_OF_TAR_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, FLYERS_CRAG_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, JIDAMBA_TANGLE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, JIDAMBA_EACLANEYA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_DEEP_SEA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, NEPTUNE_SHRINE_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, JADE_CAVERN_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, CONTINENTAL_TRAM_DISPLAY_NAME, excluded),
- ]
-
- if options.included_regions == options.included_regions.option_all:
- excluded = False
- else:
- excluded = True
-
- end_game_regions = [
- create_display_region(world, player, locations_per_region, ANCIENT_LABYRINTH_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_SEQUOIA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_DEPTHS_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, CASTLE_SEQUOIA_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_OLD_WORLD_DISPLAY_NAME, excluded),
- create_display_region(world, player, locations_per_region, THE_NEW_WORLD_DISPLAY_NAME, excluded),
- ]
-
- if options.use_mods.value == options.use_mods.option_true:
- excluded = False
- else:
- excluded = True
-
- modded_regions = [
- create_display_region(world, player, locations_per_region, MODDED_ZONE_DISPLAY_NAME, excluded),
- ]
-
- for beginner_display_region_subregions in beginner_regions:
- multiworld.regions += beginner_display_region_subregions
- for advanced_display_region_subregions in advanced_regions:
- multiworld.regions += advanced_display_region_subregions
- for expert_display_region_subregions in expert_regions:
- multiworld.regions += expert_display_region_subregions
- for end_game_display_region_subregions in end_game_regions:
- multiworld.regions += end_game_display_region_subregions
- for modded_display_region_subregions in modded_regions:
- multiworld.regions += modded_display_region_subregions
-
- connect_menu_region(world, options)
-
- fancy_add_exits(world, SPAWNING_MEADOWS_AP_REGION, [DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION],
- {CONTINENTAL_TRAM_AP_REGION: lambda state: logic.has_swimming(state) and logic.obscure_routes_on(state),
- MERCURY_SHRINE_AP_REGION: lambda state: logic.has_vertical_movement(state),
- POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.obscure_routes_on(state),
- BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state),
- YAMAGAWA_MA_AP_REGION: lambda state: logic.has_swimming(state) or logic.has_vertical_movement(state)})
- #Delende start
- fancy_add_exits(world, DELENDE_PLAINS_AP_REGION, [ATOP_FISH_HATCHERY_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, SPAWNING_MEADOWS_AP_REGION, MERCURY_SHRINE_AP_REGION, SOILED_DENLENDE_AP_REGION, THE_PALE_GROTTO_AP_REGION, SEASIDE_CLIFFS_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, SALMON_PASS_WEST_AP_REGION, SALMON_PASS_EAST_AP_REGION, LAKE_DELENDE_AP_REGION],
- #Atop Fish Hatchery's sole purpose is to indicate the obscure route from Delende Plains -> Greenshire without mounts, but Archipelago doesn't like it when you can't reach a region with default settings,
- # so ibek + owl have been added to its entrance. You can already get from Delende Plains -> Greenshire with those so I'm not adding those on the exit from Atop Fish Hatchery
- {ATOP_FISH_HATCHERY_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state) or logic.has_glide(state),
- DELENDE_HIGH_BRIDGES_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state) or logic.has_glide(state),
- GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or logic.has_swimming(state),
- SALMON_PASS_WEST_AP_REGION: lambda state: logic.has_swimming(state),
- SALMON_PASS_EAST_AP_REGION: lambda state: logic.has_swimming(state),
- LAKE_DELENDE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, ATOP_FISH_HATCHERY_AP_REGION, [DELENDE_PLAINS_AP_REGION, GREENSHIRE_REPRISE_AP_REGION],
- {GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.obscure_routes_on(state)})
- fancy_add_exits(world, DELENDE_HIGH_BRIDGES_AP_REGION, [DELENDE_PLAINS_AP_REGION, GRAN_AP_REGION, BELOW_GRAN_AP_REGION, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, DELENDE_PEAK_AP_REGION, HEART_TARN_AP_REGION, YAMAGAWA_MA_AP_REGION, FENCERS_KEEP_CHEST_AP_REGION, THE_PALE_GROTTO_AP_REGION, SEASIDE_CLIFFS_AP_REGION, CLIFF_OVER_SEASIDE_CAMP_AP_REGION, PROVING_MEADOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, LAKE_DELENDE_AP_REGION],
- {GRAN_AP_REGION: lambda state: logic.can_fight_gran(state),
- BELOW_GRAN_AP_REGION: lambda state: logic.obscure_routes_on(state),
- DELENDE_PEAK_AP_REGION: lambda state: logic.has_vertical_movement(state),
- HEART_TARN_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
- FENCERS_KEEP_CHEST_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- CLIFF_OVER_SEASIDE_CAMP_AP_REGION: lambda state: logic.obscure_routes_on(state),
- CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_swimming(state),
- LAKE_DELENDE_AP_REGION: lambda state: logic.obscure_routes_on(state)})
- fancy_add_exits(world, GRAN_AP_REGION, [DELENDE_HIGH_BRIDGES_AP_REGION, BELOW_GRAN_AP_REGION])
- fancy_add_exits(world, BELOW_GRAN_AP_REGION, [GRAN_AP_REGION, ANCIENT_RESERVOIR_AP_REGION, JADE_WATERWAYS_AP_REGION],
- {GRAN_AP_REGION: lambda state: logic.can_fight_gran(state),
- ANCIENT_RESERVOIR_AP_REGION: lambda state: logic.has_swimming(state),
- JADE_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, [DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION])
- fancy_add_exits(world, HEART_TARN_AP_REGION, [DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, DELENDE_PEAK_AP_REGION, GREENSHIRE_REPRISE_AP_REGION],
- {DELENDE_PEAK_AP_REGION: lambda state: logic.has_glide(state),
- GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, DELENDE_PEAK_AP_REGION, [DELENDE_HIGH_BRIDGES_AP_REGION, SEASIDE_CLIFFS_AP_REGION, CLIFF_OVER_SEASIDE_CAMP_AP_REGION])
-
- #Delende end
- fancy_add_exits(world, MERCURY_SHRINE_AP_REGION, [DELENDE_PLAINS_AP_REGION, SEASIDE_CLIFFS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION],
- {BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, SOILED_DENLENDE_AP_REGION, [DELENDE_PLAINS_AP_REGION, THE_BANGLER_AP_REGION, THE_PALE_GROTTO_AP_REGION, JADE_WATERWAYS_AP_REGION],
- {THE_BANGLER_AP_REGION: lambda state: logic.has_swimming(state),
- JADE_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state),
- THE_PALE_GROTTO_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, THE_BANGLER_AP_REGION, [SOILED_DENLENDE_AP_REGION, SEASIDE_CLIFFS_AP_REGION, DRAFT_SHAFT_CONDUIT_AP_REGION],
- {SOILED_DENLENDE_AP_REGION: lambda state: logic.has_swimming(state),
- DRAFT_SHAFT_CONDUIT_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, THE_PALE_GROTTO_AP_REGION, [DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, SOILED_DENLENDE_AP_REGION, PROVING_MEADOWS_AP_REGION, JOJO_SEWERS_AP_REGION, LOWER_ICE_LAKES_AP_REGION, SALMON_PASS_EAST_AP_REGION],
- {SOILED_DENLENDE_AP_REGION: lambda state: logic.has_swimming(state),
- JOJO_SEWERS_AP_REGION: lambda state: logic.has_swimming(state),
- LOWER_ICE_LAKES_AP_REGION: lambda state: logic.has_swimming(state),
- SALMON_PASS_EAST_AP_REGION: lambda state: logic.has_swimming(state)})
- #Seaside Cliffs start
- fancy_add_exits(world, SEASIDE_CLIFFS_AP_REGION, [CLIFF_OVER_SEASIDE_CAMP_AP_REGION, DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_PEAK_AP_REGION, MERCURY_SHRINE_AP_REGION, THE_BANGLER_AP_REGION, DRAFT_SHAFT_CONDUIT_AP_REGION, THE_OPEN_SEA_AP_REGION, BEAURIOR_VOLCANO_AP_REGION],
- {CLIFF_OVER_SEASIDE_CAMP_AP_REGION: lambda state: logic.has_vertical_movement(state),
- DELENDE_PEAK_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, CLIFF_OVER_SEASIDE_CAMP_AP_REGION, [SEASIDE_CLIFFS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_PEAK_AP_REGION],
- {DELENDE_PEAK_AP_REGION: lambda state: logic.obscure_routes_on(state)})
- #Seaside Cliffs end
- fancy_add_exits(world, DRAFT_SHAFT_CONDUIT_AP_REGION, [SEASIDE_CLIFFS_AP_REGION, THE_BANGLER_AP_REGION],
- {THE_BANGLER_AP_REGION: lambda state: logic.has_swimming(state)})
- #Yamagawa M.A. start
- fancy_add_exits(world, YAMAGAWA_MA_AP_REGION, [FENCERS_KEEP_CHEST_AP_REGION, SPAWNING_MEADOWS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, HEART_TARN_AP_REGION, LAKE_DELENDE_AP_REGION, ATOP_DAM_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION, TOWER_OF_ZOT_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION],
- {FENCERS_KEEP_CHEST_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state),
- HEART_TARN_AP_REGION: lambda state: (logic.obscure_routes_on(state) or logic.has_vertical_movement(state)) and logic.has_glide(state),
- LAKE_DELENDE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.obscure_routes_on(state),
- ATOP_DAM_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
- SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
- SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
- TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
- POKO_POKO_LAKE_DELENDE_PASS_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state)})
- fancy_add_exits(world, FENCERS_KEEP_CHEST_AP_REGION, [YAMAGAWA_MA_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION],
- {YAMAGAWA_MA_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- #Yamagawa M.A. end
- #Proving Meadows start
- fancy_add_exits(world, PROVING_MEADOWS_AP_REGION, [DELENDE_HIGH_BRIDGES_AP_REGION, THE_PALE_GROTTO_AP_REGION, PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, COBBLESTONE_CRAG_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {CAPITAL_SEQUOIA_AP_REGION: lambda state: logic.has_glide(state),
- CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_swimming(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- #A Proving Meadows <-> Skumparadise connector; untraversable w/o a region pass in Regionsanity
- fancy_add_exits(world, PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION, [PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION],
- {PROVING_MEADOWS_AP_REGION: lambda state: logic.has_jobs(state, 3),
- SKUMPARADISE_AP_REGION: lambda state: logic.has_jobs(state, 3)})
- #Proving Meadows end
- fancy_add_exits(world, SKUMPARADISE_AP_REGION, [PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION, CAPITAL_SEQUOIA_AP_REGION])
- #Capital Sequoia start
- fancy_add_exits(world, CAPITAL_SEQUOIA_AP_REGION, [MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BEATSMITH_DISCO_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION],
- # Obscure Routes: it is possible to jump from Rolling Quintar Fields onto the Capital Sequoia walls from the southeast and manage to bypass the guard and thus the job requirement
- {MOAT_SHALLOWS_AP_REGION: lambda state: logic.has_key(state, COURTYARD_KEY) or (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- CAPITAL_MOAT_AP_REGION: lambda state: (logic.has_jobs(state, 5) or state.has(GAEA_STONE, player) or (logic.obscure_routes_on(state) and logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)) and logic.has_swimming(state),
- BEATSMITH_DISCO_AP_REGION: lambda state: logic.has_vertical_movement(state),
- BOOMER_SOCIETY_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_key(state, COURTYARD_KEY) or logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state),
- GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_jobs(state, 5) or state.has(GAEA_STONE, player) or (logic.obscure_routes_on(state) and logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- RAMPART_ATOP_PORTCULLIS_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.has_vertical_movement(state),
- #note for eme: technically possible to get into the first dungeon with quintar instead of glide, but it's hard lol; come from Quintar Sanctum save point and go west up mountain and fall down through grate (that part's easy) then the quintar jump to the lamp is hard
- CASTLE_SEQUOIA_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, MOAT_SHALLOWS_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, JOJO_SEWERS_AP_REGION, COBBLESTONE_CRAG_AP_REGION],
- {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, CAPITAL_MOAT_AP_REGION, [MOAT_SHALLOWS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, JOJO_SEWERS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {MOAT_SHALLOWS_AP_REGION: lambda state: logic.has_swimming(state),
- DELENDE_HIGH_BRIDGES_AP_REGION: lambda state: logic.has_swimming(state),
- JOJO_SEWERS_AP_REGION: lambda state: logic.has_swimming(state),
- COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_swimming(state),
- GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_swimming(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, BEATSMITH_DISCO_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION])
- #Capital Sequoia end
- #Jojo Sewers section start
- fancy_add_exits(world, JOJO_SEWERS_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, SEWERS_SECRET_PASSWORD_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION, THE_PALE_GROTTO_AP_REGION, CAPITAL_JAIL_AP_REGION, QUINTAR_NEST_AP_REGION],
- #Swimming connection to Sewers to Boomer Society bypasses the Secret Password region
- {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- SEWERS_TO_BOOMER_SOCIETY_AP_REGION: lambda state: logic.has_swimming(state),
- CAPITAL_JAIL_AP_REGION: lambda state: logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_swimming(state),
- THE_PALE_GROTTO_AP_REGION: lambda state: logic.has_swimming(state),
- QUINTAR_NEST_AP_REGION: lambda state: (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_swimming(state))})
- #A Jojo Sewers <-> Sewers to Boomer Society connector; untraversable w/o a region pass in Regionsanity
- fancy_add_exits(world, SEWERS_SECRET_PASSWORD_AP_REGION, [JOJO_SEWERS_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION],
- {JOJO_SEWERS_AP_REGION: lambda state: state.has(JOJO_SEWERS_PASS, player) or options.regionsanity.value == options.regionsanity.option_disabled,
- SEWERS_TO_BOOMER_SOCIETY_AP_REGION: lambda state: state.has(JOJO_SEWERS_PASS, player) or options.regionsanity.value == options.regionsanity.option_disabled})
- fancy_add_exits(world, SEWERS_TO_BOOMER_SOCIETY_AP_REGION, [JOJO_SEWERS_AP_REGION, SEWERS_SECRET_PASSWORD_AP_REGION, BOOMER_SOCIETY_AP_REGION],
- #Swimming connection to Jojo Sewers Main (lol) bypasses the Secret Password region
- {JOJO_SEWERS_AP_REGION: lambda state: logic.has_swimming(state)})
- #Jojo Sewers section end
- fancy_add_exits(world, BOOMER_SOCIETY_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, BEATSMITH_DISCO_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, BOOMER_OVERLOOK_AP_REGION],
- {BEATSMITH_DISCO_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
- BOOMER_OVERLOOK_AP_REGION: lambda state: logic.has_horizontal_movement(state)})
- #Rolling Quintar Fields start
- fancy_add_exits(world, ROLLING_QUINTAR_FIELDS_AP_REGION, [ROLLING_TREETOP_HIGHWAY_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, QUINTAR_NEST_AP_REGION, OKIMOTO_NS_AP_REGION, QUINTAR_RESERVE_AP_REGION],
- #Obscure Route: Rolling Quintar Fields -> Sanctum Entrance; jump up to the "Pinnacle by short and tall box friends" check from the Quintar Enthusiast's House (auto-jump helps)
- {ROLLING_TREETOP_HIGHWAY_AP_REGION: lambda state: logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state),
- SANCTUM_ENTRANCE_AP_REGION: lambda state: logic.obscure_routes_on(state),
- OKIMOTO_NS_AP_REGION: lambda state: logic.has_vertical_movement(state),
- QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, ROLLING_TREETOP_HIGHWAY_AP_REGION, [ROLLING_QUINTAR_FIELDS_AP_REGION, SANCTUM_ENTRANCE_AP_REGION],
- {SANCTUM_ENTRANCE_AP_REGION: lambda state: (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state)),})
- fancy_add_exits(world, SANCTUM_ENTRANCE_AP_REGION, [ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_TREETOP_HIGHWAY_AP_REGION, HUNTERS_TOWER_AP_REGION, QUINTAR_SANCTUM_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION],
- {ROLLING_TREETOP_HIGHWAY_AP_REGION: lambda state: logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state),
- HUNTERS_TOWER_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state),
- SANCTUM_EXIT_CLIFFTOP_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)})
- fancy_add_exits(world, HUNTERS_TOWER_AP_REGION, [ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_TREETOP_HIGHWAY_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION],
- {SANCTUM_EXIT_CLIFFTOP_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)})
- #Rolling Quintar Fields end
- fancy_add_exits(world, QUINTAR_NEST_AP_REGION, [QUINTAR_SANCTUM_AP_REGION, COBBLESTONE_CRAG_AP_REGION, JOJO_SEWERS_AP_REGION],
- {QUINTAR_SANCTUM_AP_REGION: lambda state: logic.has_swimming(state)})
- #Quintar Sanctum start
- fancy_add_exits(world, QUINTAR_SANCTUM_AP_REGION, [SANCTUM_EXIT_CLIFFTOP_AP_REGION, QUINTAR_NEST_AP_REGION, QUINTAR_MAUSOLEUM_AP_REGION],
- {QUINTAR_MAUSOLEUM_AP_REGION: lambda state: logic.has_swimming(state),
- QUINTAR_NEST_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, SANCTUM_EXIT_CLIFFTOP_AP_REGION, [QUINTAR_SANCTUM_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, HUNTERS_TOWER_AP_REGION, OKIMOTO_NS_AP_REGION, QUINTAR_RESERVE_AP_REGION],
- {HUNTERS_TOWER_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- OKIMOTO_NS_AP_REGION: lambda state: logic.has_vertical_movement(state),
- QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- #Quintar Sanctum end
- fancy_add_exits(world, CAPITAL_JAIL_AP_REGION, [JOJO_SEWERS_AP_REGION, JAIL_SOUTH_WING_AP_REGION],
- {JAIL_SOUTH_WING_AP_REGION: lambda state: logic.has_key(state, SOUTH_WING_KEY)})
- fancy_add_exits(world, JAIL_SOUTH_WING_AP_REGION, [CAPITAL_JAIL_AP_REGION, JAIL_SOUTH_WING_RUBBLE_AP_REGION],
- {CAPITAL_JAIL_AP_REGION: lambda state: logic.has_key(state, SOUTH_WING_KEY),
- JAIL_SOUTH_WING_RUBBLE_AP_REGION: lambda state: logic.has_key(state, CELL_KEY, 6)})
- fancy_add_exits(world, JAIL_SOUTH_WING_RUBBLE_AP_REGION, [JAIL_SOUTH_WING_AP_REGION, PIPELINE_NORTH_AP_REGION],
- {JAIL_SOUTH_WING_AP_REGION: lambda state: logic.has_key(state, CELL_KEY, 6)})
- fancy_add_exits(world, PIPELINE_NORTH_AP_REGION, [PIPELINE_SOUTH_AP_REGION, JAIL_SOUTH_WING_RUBBLE_AP_REGION],
- {PIPELINE_SOUTH_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, PIPELINE_SOUTH_AP_REGION, [PIPELINE_NORTH_AP_REGION, CONTINENTAL_TRAM_AP_REGION, PIPELINE_JIDAMBA_CONNECTOR_AP_REGION])
- #A Pipeline <-> Jidamba connector; untraversable w/o region pass in Regionsanity
- fancy_add_exits(world, PIPELINE_JIDAMBA_CONNECTOR_AP_REGION, [PIPELINE_SOUTH_AP_REGION, JIDAMBA_CAVE_AP_REGION])
- fancy_add_exits(world, COBBLESTONE_CRAG_AP_REGION, [PROVING_MEADOWS_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, THE_OPEN_SEA_AP_REGION, SHOUDU_WATERFRONT_AP_REGION, OKIMOTO_NS_AP_REGION],
- {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- SHOUDU_WATERFRONT_AP_REGION: lambda state: logic.has_horizontal_movement(state),
- OKIMOTO_NS_AP_REGION: lambda state: logic.has_horizontal_movement(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, OKIMOTO_NS_AP_REGION, [COBBLESTONE_CRAG_AP_REGION, THE_OPEN_SEA_AP_REGION, FLYERS_CRAG_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- FLYERS_CRAG_AP_REGION: lambda state: (logic.has_glide(state) and logic.has_vertical_movement(state)) or logic.has_swimming(state)})
- fancy_add_exits(world, GREENSHIRE_REPRISE_AP_REGION, [DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, SALMON_PASS_EAST_AP_REGION, GREENSHIRE_OVERLOOK_AP_REGION],
- #Obscure Routes: it is possible to jump from Rolling Quintar Fields onto the Capital Sequoia walls from the southeast and manage to bypass the guard and thus the job requirement
- {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- SALMON_PASS_EAST_AP_REGION: lambda state: ((logic.has_jobs(state, 5) or logic.obscure_routes_on(state)) and logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME)) or logic.has_vertical_movement(state),
- GREENSHIRE_OVERLOOK_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, SALMON_PASS_EAST_AP_REGION, [GREENSHIRE_REPRISE_AP_REGION, SALMON_PASS_WEST_AP_REGION, DELENDE_PLAINS_AP_REGION],
- {SALMON_PASS_WEST_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state),
- DELENDE_PLAINS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, SALMON_PASS_WEST_AP_REGION, [SALMON_PASS_EAST_AP_REGION, DELENDE_PLAINS_AP_REGION, SALMON_RIVER_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION],
- {DELENDE_PLAINS_AP_REGION: lambda state: logic.has_swimming(state),
- SALMON_PASS_EAST_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state),
- SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_swimming(state)})
- #Salmon River start
- fancy_add_exits(world, SALMON_RIVER_AP_REGION, [POSEIDON_SHRINE_PROPER_AP_REGION, RIVER_CATS_EGO_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, SALMON_PASS_WEST_AP_REGION, SALMON_BAY_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
- #Note: connection to Tall Tall can be with goat (obscure), owl, (rental salmon + ibek (obscure), quintar + ibek) via poseidon shrine/salmon shack roof, or poseidon stone + ibek/owl
- {POSEIDON_SHRINE_PROPER_AP_REGION: lambda state: (logic.has_swimming(state) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state),
- RIVER_CATS_EGO_AP_REGION: lambda state: (logic.obscure_routes_on(state) and logic.has_rental_salmon(state)) or logic.has_swimming(state),
- SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- LOWER_ICE_LAKES_AP_REGION: lambda state: (logic.has_vertical_movement(state) and logic.obscure_routes_on(state)) or logic.has_glide(state)})
- fancy_add_exits(world, POSEIDON_SHRINE_PROPER_AP_REGION, [SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
- {POSEIDON_SHRINE_ROOF_AP_REGION: lambda state: logic.has_vertical_movement(state),
- MUSHROOM_MOUNTAIN_AP_REGION: lambda state: logic.has_vertical_movement(state),
- LOWER_ICE_LAKES_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, POSEIDON_SHRINE_ROOF_AP_REGION, [SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
- {MUSHROOM_MOUNTAIN_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- LOWER_ICE_LAKES_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, RIVER_CATS_EGO_AP_REGION, [SALMON_RIVER_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION],
- {SALMON_RIVER_AP_REGION: lambda state: logic.has_swimming(state),
- MUSHROOM_MOUNTAIN_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, MUSHROOM_MOUNTAIN_AP_REGION, [SALMON_RIVER_AP_REGION, RIVER_CATS_EGO_AP_REGION, SALMON_BAY_AP_REGION],
- {SALMON_RIVER_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or logic.has_swimming(state),
- RIVER_CATS_EGO_AP_REGION: lambda state: logic.has_glide(state) and logic.has_swimming(state)})
- fancy_add_exits(world, SOUTH_SALMON_RIVER_AP_REGION, [SALMON_RIVER_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION, SALMON_PASS_WEST_AP_REGION, ATOP_DAM_AP_REGION],
- {SALMON_RIVER_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
- SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_swimming(state) or logic.has_vertical_movement(state) or logic.has_glide(state),
- SALMON_PASS_WEST_AP_REGION: lambda state: logic.has_swimming(state)})
- #Salmon River end
- #Poko Poko Desert start
- fancy_add_exits(world, POKO_POKO_DESERT_AP_REGION, [POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_BEACH_WEST_PASS_AP_REGION, TOWER_OF_ZOT_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, ANCIENT_RESERVOIR_AP_REGION],
- {POKO_POKO_EAST_PLATEAU_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- POKO_POKO_BEACH_WEST_PASS_AP_REGION: lambda state: (logic.has_horizontal_movement(state) and logic.has_vertical_movement(state)) or logic.has_glide(state),
- TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
- ANCIENT_RESERVOIR_AP_REGION: lambda state: logic.has_key(state, PYRAMID_KEY)})
- fancy_add_exits(world, POKO_POKO_EAST_PLATEAU_AP_REGION, [POKO_POKO_DESERT_AP_REGION, TOWER_OF_ZOT_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, SARA_SARA_BAZAAR_AP_REGION],
- {TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
- POKO_POKO_LAKE_DELENDE_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state),
- POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, POKO_POKO_BEACH_WEST_PASS_AP_REGION, [POKO_POKO_DESERT_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION])
- fancy_add_exits(world, TOWER_OF_ZOT_AP_REGION, [POKO_POKO_DESERT_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION, ANCIENT_LABYRINTH_AP_REGION, SALMON_BAY_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION],
- {POKO_POKO_EAST_PLATEAU_AP_REGION: lambda state: logic.has_glide(state),
- ANCIENT_LABYRINTH_AP_REGION: lambda state: (state.has(POKO_POKO_DESERT_PASS, player) or options.regionsanity.value == options.regionsanity.option_disabled) and (state.has(ANCIENT_TABLET_A, player) or logic.obscure_routes_on(state)) and logic.has_vertical_movement(state) and logic.has_glide(state),
- SALMON_BAY_AP_REGION: lambda state: logic.has_vertical_movement(state),
- SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)})
- fancy_add_exits(world, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, [POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, LAKE_DELENDE_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION],
- {POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION: lambda state: logic.has_glide(state),
- SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)})
- fancy_add_exits(world, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, [POKO_POKO_EAST_PLATEAU_AP_REGION, SPAWNING_MEADOWS_AP_REGION])
- #Poko Poko Desert end
- fancy_add_exits(world, SARA_SARA_BAZAAR_AP_REGION, [POKO_POKO_DESERT_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION, SHOUDU_PROVINCE_AP_REGION, BAZAAR_COAST_AP_REGION, CONTINENTAL_TRAM_AP_REGION],
- #Swimming connection to both the Bazaar Coast and the Continental Tram bc you can get to the Tram without going into The Open Sea and vice versa
- {SARA_SARA_BEACH_WEST_AP_REGION: lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME),
- SHOUDU_PROVINCE_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and state.has(FERRY_PASS, player),
- BAZAAR_COAST_AP_REGION: lambda state: logic.has_swimming(state),
- CONTINENTAL_TRAM_AP_REGION: lambda state: logic.has_swimming(state) or logic.has_key(state, TRAM_KEY)})
- fancy_add_exits(world, SARA_SARA_BEACH_EAST_AP_REGION, [SARA_SARA_BAZAAR_AP_REGION, THE_OPEN_SEA_AP_REGION, IBEK_CAVE_AP_REGION, BEAURIOR_VOLCANO_AP_REGION],
- {IBEK_CAVE_AP_REGION: lambda state: logic.has_vertical_movement(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, SARA_SARA_BEACH_WEST_AP_REGION, [POKO_POKO_BEACH_WEST_PASS_AP_REGION, TOWER_OF_ZOT_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {POKO_POKO_BEACH_WEST_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state),
- TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.has_vertical_movement(state),
- SARA_SARA_BAZAAR_AP_REGION: lambda state: logic.has_horizontal_movement(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- #Ancient Reservoir start
- fancy_add_exits(world, ANCIENT_RESERVOIR_AP_REGION, [POKO_POKO_DESERT_AP_REGION, IBEK_CAVE_AP_REGION, BELOW_GRAN_AP_REGION],
- {POKO_POKO_DESERT_AP_REGION: lambda state: logic.has_key(state, PYRAMID_KEY),
- BELOW_GRAN_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, IBEK_CAVE_AP_REGION, [SARA_SARA_BEACH_EAST_AP_REGION],
- {SARA_SARA_BEACH_EAST_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- #Ancient Reservoir end
- #Salmon Bay start
- fancy_add_exits(world, SALMON_BAY_AP_REGION, [THE_OPEN_SEA_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, SALMON_RIVER_MOUTH_AP_REGION, [SALMON_BAY_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, TOWER_OF_ZOT_AP_REGION],
- {SOUTH_SALMON_RIVER_AP_REGION: lambda state: (logic.has_vertical_movement(state) and logic.has_glide(state)) or logic.has_swimming(state),
- TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_glide(state)})
- #Salmon Bay end
- #The Open Sea start
- fancy_add_exits(world, THE_OPEN_SEA_AP_REGION, [SEASIDE_CLIFFS_AP_REGION, PROVING_MEADOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, COBBLESTONE_CRAG_AP_REGION, OKIMOTO_NS_AP_REGION, SHOUDU_WATERFRONT_AP_REGION, BAZAAR_COAST_AP_REGION, SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION, SALMON_BAY_AP_REGION, SHOUDU_PROVINCE_AP_REGION, THE_UNDERCITY_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION, THE_DEEP_SEA_AP_REGION],
- {SEASIDE_CLIFFS_AP_REGION: lambda state: logic.has_swimming(state),
- PROVING_MEADOWS_AP_REGION: lambda state: logic.has_swimming(state),
- CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
- COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_swimming(state),
- OKIMOTO_NS_AP_REGION: lambda state: logic.has_swimming(state),
- SHOUDU_WATERFRONT_AP_REGION: lambda state: logic.has_swimming(state),
- THE_UNDERCITY_AP_REGION: lambda state: logic.has_swimming(state),
- BAZAAR_COAST_AP_REGION: lambda state: logic.has_swimming(state),
- SARA_SARA_BEACH_EAST_AP_REGION: lambda state: logic.has_swimming(state),
- SARA_SARA_BEACH_WEST_AP_REGION: lambda state: logic.has_swimming(state),
- SALMON_BAY_AP_REGION: lambda state: logic.has_swimming(state),
- SHOUDU_PROVINCE_AP_REGION: lambda state: logic.has_swimming(state),
- JIDAMBA_ATOLLS_AP_REGION: lambda state: logic.has_swimming(state),
- JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state),
- THE_DEEP_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, JIDAMBA_ATOLLS_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {JIDAMBA_FOREST_FLOOR_AP_REGION: lambda state: logic.has_glide(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, BAZAAR_COAST_AP_REGION, [THE_OPEN_SEA_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, CONTINENTAL_TRAM_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- SARA_SARA_BAZAAR_AP_REGION: lambda state: logic.has_swimming(state),
- CONTINENTAL_TRAM_AP_REGION: lambda state: logic.has_swimming(state)})
- #The Open Sea end
- fancy_add_exits(world, SHOUDU_WATERFRONT_AP_REGION, [THE_OPEN_SEA_AP_REGION, SHOUDU_PROVINCE_AP_REGION, COBBLESTONE_CRAG_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- SHOUDU_PROVINCE_AP_REGION: lambda state: logic.has_vertical_movement(state),
- COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_horizontal_movement(state)})
- fancy_add_exits(world, SHOUDU_PROVINCE_AP_REGION, [SARA_SARA_BAZAAR_AP_REGION, SHOUDU_WATERFRONT_AP_REGION, GANYMEDE_SHRINE_AP_REGION, THE_UNDERCITY_AP_REGION, QUINTAR_RESERVE_AP_REGION],
- {SARA_SARA_BAZAAR_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and state.has(FERRY_PASS, player),
- GANYMEDE_SHRINE_AP_REGION: lambda state: logic.has_vertical_movement(state),
- QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_vertical_movement(state) and state.has(ELEVATOR_PART, player, 10)})
- fancy_add_exits(world, THE_UNDERCITY_AP_REGION, [SHOUDU_PROVINCE_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, GANYMEDE_SHRINE_AP_REGION, [SHOUDU_PROVINCE_AP_REGION])
- fancy_add_exits(world, BEAURIOR_VOLCANO_AP_REGION, [SARA_SARA_BEACH_EAST_AP_REGION, BEAURIOR_ROCK_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {BEAURIOR_ROCK_AP_REGION: lambda state: logic.has_vertical_movement(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, BEAURIOR_ROCK_AP_REGION, [BEAURIOR_VOLCANO_AP_REGION])
- #Lake Delende start
- fancy_add_exits(world, LAKE_DELENDE_AP_REGION, [ATOP_DAM_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION],
- {ATOP_DAM_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
- POKO_POKO_LAKE_DELENDE_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state),
- DELENDE_PLAINS_AP_REGION: lambda state: logic.has_vertical_movement(state),
- DELENDE_HIGH_BRIDGES_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, ATOP_DAM_AP_REGION, [LAKE_DELENDE_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, DELENDE_PLAINS_AP_REGION])
- #Lake Delende end
- #Quintar Reserve start
- fancy_add_exits(world, QUINTAR_RESERVE_AP_REGION, [SHOUDU_PROVINCE_AP_REGION, DIONE_SHRINE_AP_REGION, QUINTAR_MAUSOLEUM_AP_REGION],
- {QUINTAR_MAUSOLEUM_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, RESERVE_TREETOPS_AP_REGION, [QUINTAR_RESERVE_AP_REGION])
- #Quintar Reserve end
- #Dione Shrine start
- fancy_add_exits(world, DIONE_SHRINE_AP_REGION, [QUINTAR_RESERVE_AP_REGION, DIONE_ROOF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
- {DIONE_ROOF_AP_REGION: lambda state: logic.has_glide(state) and logic.has_vertical_movement(state) and logic.obscure_routes_on(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state)})
- fancy_add_exits(world, DIONE_ROOF_AP_REGION, [DIONE_SHRINE_AP_REGION, QUINTAR_RESERVE_AP_REGION, RESERVE_TREETOPS_AP_REGION, THE_CHALICE_OF_TAR_AP_REGION],
- {THE_CHALICE_OF_TAR_AP_REGION: lambda state: logic.has_glide(state)})
- #Dione Shrine end
- fancy_add_exits(world, QUINTAR_MAUSOLEUM_AP_REGION, [QUINTAR_RESERVE_AP_REGION, QUINTAR_SANCTUM_AP_REGION],
- {QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_swimming(state),
- QUINTAR_SANCTUM_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, EASTERN_CHASM_AP_REGION, [QUINTAR_RESERVE_AP_REGION, DIONE_ROOF_AP_REGION, THE_OPEN_SEA_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state) and logic.obscure_routes_on(state)})
- #Tall, Tall Heights start
- fancy_add_exits(world, BOOMER_OVERLOOK_AP_REGION, [BOOMER_SOCIETY_AP_REGION, LONE_CHEST_RAMPART_AP_REGION, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, UPPER_ICE_LAKES_AP_REGION],
- {RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION: lambda state: logic.has_glide(state) or logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME),
- TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, [BOOMER_OVERLOOK_AP_REGION, UPPER_ICE_LAKES_AP_REGION])
- fancy_add_exits(world, GREENSHIRE_OVERLOOK_AP_REGION, [GREENSHIRE_REPRISE_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION],
- {TALL_TALL_SAVE_POINT_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, TALL_TALL_SAVE_POINT_AP_REGION, [BOOMER_OVERLOOK_AP_REGION, GREENSHIRE_OVERLOOK_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
- {BOOMER_OVERLOOK_AP_REGION: lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)})
- fancy_add_exits(world, LOWER_ICE_LAKES_AP_REGION, [THE_PALE_GROTTO_AP_REGION, SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, UPPER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, LANDS_END_AP_REGION, LOWER_NORTHERN_CAVE_AP_REGION],
- {THE_PALE_GROTTO_AP_REGION: lambda state: logic.has_swimming(state),
- TALL_TALL_SAVE_POINT_AP_REGION: lambda state: logic.has_vertical_movement(state),
- UPPER_ICE_LAKES_AP_REGION: lambda state: logic.has_vertical_movement(state),
- LANDS_END_COTTAGE_RIDGE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- LANDS_END_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, UPPER_ICE_LAKES_AP_REGION, [BOOMER_OVERLOOK_AP_REGION, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, LOWER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, SOUVENIR_SHOP_AP_REGION, SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, MIDDLE_NORTHERN_CAVE_AP_REGION],
- {BOOMER_OVERLOOK_AP_REGION: lambda state: logic.has_glide(state),
- TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION: lambda state: logic.has_vertical_movement(state),
- TALL_TALL_DIAMONDSMITH_AP_REGION: lambda state: logic.has_glide(state),
- SOUVENIR_SHOP_AP_REGION: lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state),
- SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, LANDS_END_COTTAGE_RIDGE_AP_REGION, [LOWER_ICE_LAKES_AP_REGION, LANDS_END_AP_REGION],
- {LANDS_END_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state)})
- fancy_add_exits(world, TALL_TALL_DIAMONDSMITH_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, LANDS_END_NORTHERN_PEAK_AP_REGION, NORTHERN_STRETCH_RACE_START_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {NORTHERN_STRETCH_RACE_START_AP_REGION: lambda state: logic.has_glide(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, SLIP_GLIDE_RIDE_EXIT_AP_REGION, [SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION])
- fancy_add_exits(world, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, SLIP_GLIDE_RIDE_EXIT_AP_REGION, SOUVENIR_SHOP_AP_REGION, UPPER_NORTHERN_CAVE_AP_REGION],
- {SLIP_GLIDE_RIDE_EXIT_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, SOUVENIR_SHOP_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION],
- {SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, ICY_SPIKES_MADNESS_AP_REGION, SEQUOIA_ATHENAEUM_AP_REGION],
- {SEQUOIA_ATHENAEUM_BALCONY_AP_REGION: lambda state: logic.has_glide(state),
- ICY_SPIKES_MADNESS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- SEQUOIA_ATHENAEUM_AP_REGION: lambda state: state.has(VERMILLION_BOOK, player) and state.has(VIRIDIAN_BOOK, player) and state.has(CERULEAN_BOOK, player)})
- fancy_add_exits(world, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, [SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, ICY_SPIKES_MADNESS_AP_REGION],
- {TALL_TALL_TALL_CHEST_AP_REGION: lambda state: (logic.has_vertical_movement(state) and logic.has_glide(state)) or logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME),
- ICY_SPIKES_MADNESS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
- fancy_add_exits(world, TALL_TALL_TALL_CHEST_AP_REGION, [SEQUOIA_ATHENAEUM_BALCONY_AP_REGION])
- fancy_add_exits(world, ICY_SPIKES_MADNESS_AP_REGION, [SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, PAMOA_TREE_AP_REGION],
- {SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION: lambda state: logic.has_glide(state),
- SEQUOIA_ATHENAEUM_BALCONY_AP_REGION: lambda state: logic.has_glide(state),
- PAMOA_TREE_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, PAMOA_TREE_AP_REGION, [ICY_SPIKES_MADNESS_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, HUNTERS_TOWER_AP_REGION, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, THE_CHALICE_OF_TAR_AP_REGION],
- {ICY_SPIKES_MADNESS_AP_REGION: lambda state: logic.has_glide(state),
- THE_CHALICE_OF_TAR_AP_REGION: lambda state: logic.has_glide(state)})
- #Tall, Tall Heights end
- #Northern Cave start
- fancy_add_exits(world, UPPER_NORTHERN_CAVE_AP_REGION, [SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, SLIP_GLIDE_RIDE_AP_REGION],
- {SLIP_GLIDE_RIDE_AP_REGION: lambda state: logic.has_glide(state) and logic.has_vertical_movement(state),
- SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, MIDDLE_NORTHERN_CAVE_AP_REGION, [ICE_CELL_AP_REGION, LOWER_NORTHERN_CAVE_AP_REGION])
- fancy_add_exits(world, ICE_CELL_AP_REGION, [LOWER_NORTHERN_CAVE_AP_REGION, SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION],
- {LOWER_NORTHERN_CAVE_AP_REGION: lambda state: logic.has_key(state, ICE_CELL_KEY),
- SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION: lambda state: logic.can_push_ice_block_and_goat(state, NORTHERN_CAVE_DISPLAY_NAME) or logic.has_glide(state)})
- fancy_add_exits(world, LOWER_NORTHERN_CAVE_AP_REGION, [ICE_CELL_AP_REGION, LOWER_ICE_LAKES_AP_REGION])
- #Northern Cave end
- #Land's End start
- fancy_add_exits(world, LANDS_END_AP_REGION, [LANDS_END_NORTHERN_PEAK_AP_REGION, OWL_TREE_AP_REGION, ATOP_FISH_HATCHERY_AP_REGION, HEART_TARN_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, ATOP_DAM_AP_REGION, LOWER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, THE_OPEN_SEA_AP_REGION],
- #From Callisto Shrine save point, you can drop down to Delende, or to South Salmon River or Atop Salmon River Dam with the quintar
- {LANDS_END_NORTHERN_PEAK_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- OWL_TREE_AP_REGION: lambda state: logic.has_vertical_movement(state),
- ATOP_FISH_HATCHERY_AP_REGION: lambda state: logic.obscure_routes_on(state),
- HEART_TARN_AP_REGION: lambda state: logic.has_glide(state),
- SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.obscure_routes_on(state),
- ATOP_DAM_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.obscure_routes_on(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, LANDS_END_NORTHERN_PEAK_AP_REGION, [LANDS_END_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {TALL_TALL_DIAMONDSMITH_AP_REGION: lambda state: logic.has_glide(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, OWL_TREE_AP_REGION, [LANDS_END_AP_REGION])
- #Land's End end
- #Slip Glide Ride start
- fancy_add_exits(world, SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, [ICE_CELL_AP_REGION, SLIP_GLIDE_RIDE_AP_REGION],
- {ICE_CELL_AP_REGION: lambda state: logic.has_glide(state),
- SLIP_GLIDE_RIDE_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, SLIP_GLIDE_RIDE_AP_REGION, [SLIP_GLIDE_RIDE_EXIT_AP_REGION],
- {SLIP_GLIDE_RIDE_EXIT_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state) and logic.has_key(state, RED_DOOR_KEY, 3)})
- #Slip Glide Ride end
- #Regionsanity: can't get out of Athenaeum because you can't reach the duck through the door
- fancy_add_exits(world, SEQUOIA_ATHENAEUM_AP_REGION, [MENU_AP_REGION])
- #Northern Stretch start
- fancy_add_exits(world, NORTHERN_STRETCH_RACE_START_AP_REGION, [NORTHERN_STRETCH_RACE_FINISH_AP_REGION, SLIP_GLIDE_RIDE_EXIT_AP_REGION, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, SOUVENIR_SHOP_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, [NORTHERN_STRETCH_RACE_START_AP_REGION, SUMMONERS_TOWER_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {NORTHERN_STRETCH_RACE_START_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state),
- SUMMONERS_TOWER_AP_REGION: lambda state: logic.has_vertical_movement(state),
- THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, SUMMONERS_TOWER_AP_REGION, [NORTHERN_STRETCH_RACE_START_AP_REGION, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, THE_OPEN_SEA_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- #Northern Stretch end
- #Castle Ramparts start
- fancy_add_exits(world, RAMPART_ATOP_PORTCULLIS_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, HUNTERS_TOWER_AP_REGION, LONE_CHEST_RAMPART_AP_REGION],
- {LONE_CHEST_RAMPART_AP_REGION: lambda state: logic.has_vertical_movement(state)})
- fancy_add_exits(world, LONE_CHEST_RAMPART_AP_REGION, [RAMPART_ATOP_PORTCULLIS_AP_REGION, BOOMER_OVERLOOK_AP_REGION, BEATSMITH_DISCO_AP_REGION],
- {BEATSMITH_DISCO_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state)})
- fancy_add_exits(world, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, [LONE_CHEST_RAMPART_AP_REGION, BOOMER_OVERLOOK_AP_REGION, PEAK_RAMPARTS_AP_REGION],
- {PEAK_RAMPARTS_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)})
- fancy_add_exits(world, PEAK_RAMPARTS_AP_REGION, [RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION],
- {TALL_TALL_TALL_CHEST_AP_REGION: lambda state: logic.has_glide(state)})
- #Castle Ramparts end
- fancy_add_exits(world, THE_CHALICE_OF_TAR_AP_REGION, [SANCTUM_ENTRANCE_AP_REGION, HUNTERS_TOWER_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, PAMOA_TREE_AP_REGION, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, QUINTAR_RESERVE_AP_REGION, DIONE_ROOF_AP_REGION, EASTERN_CHASM_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
- {TALL_TALL_TALL_CHEST_AP_REGION: lambda state: logic.has_glide(state),
- PAMOA_TREE_AP_REGION: lambda state: logic.has_glide(state),
- NORTHERN_STRETCH_RACE_FINISH_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
- QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_glide(state),
- DIONE_ROOF_AP_REGION: lambda state: logic.has_glide(state),
- EASTERN_CHASM_AP_REGION: lambda state: (state.has(DIONE_SHRINE_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state) and logic.has_vertical_movement(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state) and logic.obscure_routes_on(state)})
- fancy_add_exits(world, FLYERS_CRAG_AP_REGION, [OKIMOTO_NS_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
- {JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state)})
- #Jidamba Tangle section start
- fancy_add_exits(world, JIDAMBA_FOREST_FLOOR_AP_REGION, [JIDAMBA_DIAMONDSMITH_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_CANOPY_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
- #if we change the elevators to Pipeline to always be powered, then add an exit to Pipeline Jidamba Connector
- {JIDAMBA_DIAMONDSMITH_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_SOUTHWEST_BEACH_AP_REGION: lambda state: logic.has_glide(state),
- EUROPA_SHRINE_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_CANOPY_AP_REGION: lambda state: logic.has_vertical_movement(state),
- JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, JIDAMBA_DIAMONDSMITH_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, JIDAMBA_CANOPY_AP_REGION],
- {JIDAMBA_ATOLLS_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, JIDAMBA_SOUTHWEST_BEACH_AP_REGION,[JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
- {JIDAMBA_FOREST_FLOOR_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, EUROPA_SHRINE_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
- {JIDAMBA_CAVE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- JIDAMBA_SOUTH_CLIFF_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, JIDAMBA_CAVE_AP_REGION, [EUROPA_SHRINE_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
- {EUROPA_SHRINE_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_glide(state)})
- fancy_add_exits(world, JIDAMBA_SOUTH_CLIFF_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_DIAMONDSMITH_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
- {JIDAMBA_DIAMONDSMITH_AP_REGION: lambda state: logic.has_horizontal_movement(state),
- EUROPA_SHRINE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, [JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
- {JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
- JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, [JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
- {JIDAMBA_SOUTH_CLIFF_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state),
- JIDAMBA_EACLANEYA_COURTYARD_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state),
- JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_glide(state),
- JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- #The one-way Tangle -> Eaclaneya connector is logically connected from the Summit because the Canopy door you need to unlock & switch you need to press is up there, even though the physical door to the Eaclaneya is in the Courtyard
- fancy_add_exits(world, JIDAMBA_SUMMIT_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_CANOPY_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION, TANGLE_EACLANEYA_CONNECTOR_AP_REGION],
- {JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- #A one-way Tangle -> Eaclaneya connector; untraversable w/o region pass in Regionsanity
- fancy_add_exits(world, TANGLE_EACLANEYA_CONNECTOR_AP_REGION, [JIDAMBA_EACLANEYA_AP_REGION],
- {JIDAMBA_EACLANEYA_AP_REGION: lambda state: logic.has_jidamba_keys(state)})
- fancy_add_exits(world, JIDAMBA_CANOPY_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_DIAMONDSMITH_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
- {JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- #I am not listing has_swimming on all these exits; i already wrote a has_swimming rule on every entrance into the Waterways
- fancy_add_exits(world, JIDAMBA_WATERWAYS_AP_REGION, [THE_OPEN_SEA_AP_REGION, JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION])
- #Jidamba Tangle section end
- #Removed connection from Eaclaneya -> Tangle because you can't go through that door if you haven't hit the switches on the Tangle side
- fancy_add_exits(world, JIDAMBA_EACLANEYA_AP_REGION, [THE_OPEN_SEA_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, THE_DEEP_SEA_AP_REGION, [THE_OPEN_SEA_AP_REGION, NEPTUNE_SHRINE_AP_REGION, THE_DEPTHS_AP_REGION, THE_SEQUOIA_AP_REGION],
- {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
- NEPTUNE_SHRINE_AP_REGION: lambda state: logic.has_swimming(state),
- THE_DEPTHS_AP_REGION: lambda state: logic.has_swimming(state),
- THE_SEQUOIA_AP_REGION: lambda state: logic.has_golden_quintar(state)})
- fancy_add_exits(world, NEPTUNE_SHRINE_AP_REGION, [THE_DEEP_SEA_AP_REGION],
- {THE_DEEP_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, JADE_WATERWAYS_AP_REGION, [JADE_CAVERN_AP_REGION, SOILED_DENLENDE_AP_REGION, BELOW_GRAN_AP_REGION],
- {JADE_CAVERN_AP_REGION: lambda state: logic.has_golden_quintar(state),
- SOILED_DENLENDE_AP_REGION: lambda state: logic.has_swimming(state),
- BELOW_GRAN_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, JADE_CAVERN_AP_REGION, [JADE_WATERWAYS_AP_REGION],
- {JADE_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
- fancy_add_exits(world, CONTINENTAL_TRAM_AP_REGION, [PIPELINE_SOUTH_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, BAZAAR_COAST_AP_REGION],
- {SARA_SARA_BAZAAR_AP_REGION: lambda state: logic.has_swimming(state) or state.has(TRAM_KEY, player),
- BAZAAR_COAST_AP_REGION: lambda state: logic.has_swimming(state)})
- #Ancient Labyrinth section start
- fancy_add_exits(world, ANCIENT_LABYRINTH_AP_REGION, [TOWER_OF_ZOT_AP_REGION, LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION],
- #TODO: check if your starting point is Ancient Labyrinth if you can push the block out to Poko Poko Desert, with or without the Poko Poko Desert Pass
- {LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION: lambda state: state.has(ANCIENT_TABLET_B, player) or logic.obscure_routes_on(state)})
- fancy_add_exits(world, LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, [LABYRINTH_CORE_AP_REGION],
- {LABYRINTH_CORE_AP_REGION: lambda state: state.has(ANCIENT_TABLET_C, player) or logic.obscure_routes_on(state)})
- #only adding an exit here to make the jsonifier stop crying
- fancy_add_exits(world, LABYRINTH_CORE_AP_REGION,[MENU_AP_REGION])
- #Ancient Labyrinth section end
- fancy_add_exits(world, THE_SEQUOIA_AP_REGION, [THE_DEEP_SEA_AP_REGION])
- fancy_add_exits(world, THE_DEPTHS_AP_REGION, [THE_DEEP_SEA_AP_REGION])
- fancy_add_exits(world, CASTLE_SEQUOIA_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION])
- # regions without connections don't get parsed by Jsonifier
- fancy_add_exits(world, THE_NEW_WORLD_AP_REGION, [MENU_AP_REGION])
- fancy_add_exits(world, THE_OLD_WORLD_AP_REGION, [MENU_AP_REGION])
- fancy_add_exits(world, MODDED_ZONE_AP_REGION, [MENU_AP_REGION])
-
-def get_locations_per_ap_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]:
- per_region: Dict[str, List[LocationData]] = {}
-
- for location in locations:
- per_region.setdefault(location.ap_region, []).append(location)
-
- return per_region
-
-def create_ap_region(world: "CrystalProjectWorld", player: int, locations_per_region: Dict[str, List[LocationData]], name: str, excluded: bool) -> Tuple[Region, Location | None]:
- """
- Creates an AP Region and returns it, as well as the Location used for region completion
- """
- logic = CrystalProjectLogic(player, world.options)
- ap_region = Region(name, player, world.multiworld)
-
- region_completion: Location | None = None
-
- #if the region isn't part of the multiworld, we still make the region so that all the exits still work,
- #but we also don't fill it with locations
- if not excluded:
- if name in locations_per_region:
- for location_data in locations_per_region[name]:
- location = create_location(player, location_data, ap_region)
- ap_region.locations.append(location)
- if location_data.regionsanity:
- region_completion = location
-
- # This is for making sure players can earn money for required shop checks in shopsanity + regionsanity
- if world.options.regionsanity.value != world.options.regionsanity.option_disabled and world.options.shopsanity.value != world.options.shopsanity.option_disabled:
- for location in ap_region.locations:
- if "Shop -" in location.name:
- location.access_rule = combine_callables(location.access_rule, lambda state: logic.can_earn_money(state, ap_region.name))
-
- return ap_region, region_completion
-
-def create_display_region(world: "CrystalProjectWorld", player: int, locations_per_region: Dict[str, List[LocationData]], display_region_name: str, excluded: bool) -> List[Region]:
- ap_regions: List[Region] = []
- ap_region_names = display_region_subregions_dictionary[display_region_name]
-
- region_completion_location = None
-
- if not excluded:
- world.included_regions.append(display_region_name)
-
- for ap_region_name in ap_region_names:
- ap_region, region_completion_location_temp = create_ap_region(world, player, locations_per_region, ap_region_name, excluded)
- ap_regions.append(ap_region)
- if not excluded:
- if region_completion_location_temp is not None:
- if region_completion_location is not None:
- raise Exception(f"Two region completion locations exist inside {display_region_name}")
- region_completion_location = region_completion_location_temp
-
- #need to add the rule for every other location in the ap region, as well as every location in other ap regions, as well as a can_reach rule on other ap regions in this display region
- # This is for the region completion location
- if not excluded:
- if world.options.regionsanity.value != world.options.regionsanity.option_disabled and region_completion_location is not None:
- locations_in_display_region: List[Location] = []
- for ap_region_name in display_region_subregions_dictionary[display_region_name]:
- for location in world.get_locations():
- if location.parent_region.name == ap_region_name and location != region_completion_location:
- locations_in_display_region.append(location)
-
- region_completion_location.access_rule = lambda state: can_reach_all_locations(state, locations_in_display_region)
-
- return ap_regions
-
-def can_reach_all_locations(state: CollectionState, locations: List[Location]) -> bool:
- result: bool = True
-
- for location in locations:
- if not location.can_reach(state):
- result = False
- break
-
- return result
-
-def create_location(player: int, location_data: LocationData, region: Region) -> Location:
- location = CrystalProjectLocation(player, location_data.name, location_data.code, region)
-
- if location_data.rule:
- location.access_rule = location_data.rule
-
- return location
-
-def combine_callables(callable1: Callable[[CollectionState], bool], callable2: Callable[[CollectionState], bool]) -> Callable[[CollectionState], bool]:
- return lambda state, a=callable1, b=callable2: a(state) and b(state)
-
-def fancy_add_exits(self, region: str, exits: List[str],
- rules: Dict[str, Callable[[CollectionState], bool]] | None = None):
- if rules is not None:
- for region_rule in rules:
- if not region_rule in exits:
- raise Exception(f"A rule was defined for the entrance {region} -> {region_rule} but {region_rule} isn't in the list of exits from {region}")
-
- for destination_ap_region in exits:
- destination_display_region = ap_region_to_display_region_dictionary[destination_ap_region]
- if rules is None:
- rules = {}
- if destination_ap_region in rules:
- rules[destination_ap_region] = combine_callables(rules[destination_ap_region], rules_on_display_regions[destination_display_region])
- else:
- rules[destination_ap_region] = rules_on_display_regions[destination_display_region]
-
- # all regions except Menu have an exit to menu
- if region != MENU_AP_REGION:
- exits.append(MENU_AP_REGION)
-
- self.multiworld.get_region(region, self.player).add_exits(exits, rules)
-
-def connect_menu_region(world: "CrystalProjectWorld", options: CrystalProjectOptions) -> None:
- logic = CrystalProjectLogic(world.player, options)
-
- fancy_add_exits(world, MENU_AP_REGION, [SPAWNING_MEADOWS_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, MERCURY_SHRINE_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, POKO_POKO_DESERT_AP_REGION, GANYMEDE_SHRINE_AP_REGION, DIONE_ROOF_AP_REGION, UPPER_ICE_LAKES_AP_REGION, OWL_TREE_AP_REGION, EUROPA_SHRINE_AP_REGION, NEPTUNE_SHRINE_AP_REGION, THE_OLD_WORLD_AP_REGION, THE_NEW_WORLD_AP_REGION, MODDED_ZONE_AP_REGION],
- # If regionsanity is enabled it will set its own origin region, if it isn't we should connect menu to spawning meadows
- {SPAWNING_MEADOWS_AP_REGION: lambda state: options.regionsanity.value == options.regionsanity.option_disabled,
- CAPITAL_SEQUOIA_AP_REGION: lambda state: state.has(GAEA_STONE, world.player),
- MERCURY_SHRINE_AP_REGION: lambda state: state.has(MERCURY_STONE, world.player),
- POSEIDON_SHRINE_ROOF_AP_REGION: lambda state: state.has(POSEIDON_STONE, world.player),
- POKO_POKO_DESERT_AP_REGION: lambda state: state.has(MARS_STONE, world.player),
- GANYMEDE_SHRINE_AP_REGION: lambda state: state.has(GANYMEDE_STONE, world.player),
- DIONE_ROOF_AP_REGION: lambda state: state.has(DIONE_STONE, world.player),
- UPPER_ICE_LAKES_AP_REGION: lambda state: state.has(TRITON_STONE, world.player),
- OWL_TREE_AP_REGION: lambda state: state.has(CALLISTO_STONE, world.player),
- EUROPA_SHRINE_AP_REGION: lambda state: state.has(EUROPA_STONE, world.player),
- NEPTUNE_SHRINE_AP_REGION: lambda state: state.has(NEPTUNE_STONE, world.player),
- THE_OLD_WORLD_AP_REGION: lambda state: logic.old_world_requirements(state),
- THE_NEW_WORLD_AP_REGION: lambda state: logic.new_world_requirements(state)})
+from typing import List, Dict, Tuple, Callable, TYPE_CHECKING
+from BaseClasses import Region, Location, CollectionState
+from .options import CrystalProjectOptions
+from .locations import LocationData
+from .items import display_region_name_to_pass_dict
+from .rules import CrystalProjectLogic
+from .constants.keys import *
+from .constants.key_items import *
+from .constants.ap_regions import *
+from .constants.display_regions import *
+from .constants.level_requirements import *
+from .constants.teleport_stones import *
+from .constants.region_passes import *
+
+if TYPE_CHECKING:
+ from . import CrystalProjectWorld
+
+ap_region_to_display_region_dictionary: Dict[str, str] = {}
+
+display_region_subregions_dictionary: Dict[str, List[str]] = {
+ #Beginner
+ MENU_DISPLAY_NAME: MENU_DISPLAY_SUBREGIONS,
+ SPAWNING_MEADOWS_DISPLAY_NAME: SPAWNING_MEADOWS_DISPLAY_SUBREGIONS,
+ DELENDE_DISPLAY_NAME: DELENDE_DISPLAY_SUBREGIONS,
+ SOILED_DEN_DISPLAY_NAME: SOILED_DEN_DISPLAY_SUBREGIONS,
+ THE_PALE_GROTTO_DISPLAY_NAME: THE_PALE_GROTTO_DISPLAY_SUBREGIONS,
+ SEASIDE_CLIFFS_DISPLAY_NAME: SEASIDE_CLIFFS_DISPLAY_SUBREGIONS,
+ DRAFT_SHAFT_CONDUIT_DISPLAY_NAME: DRAFT_SHAFT_CONDUIT_DISPLAY_SUBREGIONS,
+ MERCURY_SHRINE_DISPLAY_NAME: MERCURY_SHRINE_DISPLAY_SUBREGIONS,
+ YAMAGAWA_MA_DISPLAY_NAME: YAMAGAWA_MA_DISPLAY_SUBREGIONS,
+ PROVING_MEADOWS_DISPLAY_NAME: PROVING_MEADOWS_DISPLAY_SUBREGIONS,
+ SKUMPARADISE_DISPLAY_NAME: SKUMPARADISE_DISPLAY_SUBREGIONS,
+ #Advanced
+ CAPITAL_SEQUOIA_DISPLAY_NAME: CAPITAL_SEQUOIA_DISPLAY_SUBREGIONS,
+ JOJO_SEWERS_DISPLAY_NAME: JOJO_SEWERS_DISPLAY_SUBREGIONS,
+ BOOMER_SOCIETY_DISPLAY_NAME: BOOMER_SOCIETY_DISPLAY_SUBREGIONS,
+ ROLLING_QUINTAR_FIELDS_DISPLAY_NAME: ROLLING_QUINTAR_FIELDS_DISPLAY_SUBREGIONS,
+ QUINTAR_NEST_DISPLAY_NAME: QUINTAR_NEST_DISPLAY_SUBREGIONS,
+ QUINTAR_SANCTUM_DISPLAY_NAME: QUINTAR_SANCTUM_DISPLAY_SUBREGIONS,
+ CAPITAL_JAIL_DISPLAY_NAME: CAPITAL_JAIL_DISPLAY_SUBREGIONS,
+ CAPITAL_PIPELINE_DISPLAY_NAME: CAPITAL_PIPELINE_DISPLAY_SUBREGIONS,
+ COBBLESTONE_CRAG_DISPLAY_NAME: COBBLESTONE_CRAG_DISPLAY_SUBREGIONS,
+ OKIMOTO_NS_DISPLAY_NAME: OKIMOTO_NS_DISPLAY_SUBREGIONS,
+ GREENSHIRE_REPRISE_DISPLAY_NAME: GREENSHIRE_REPRISE_DISPLAY_SUBREGIONS,
+ SALMON_PASS_DISPLAY_NAME: SALMON_PASS_DISPLAY_SUBREGIONS,
+ SALMON_RIVER_DISPLAY_NAME: SALMON_RIVER_DISPLAY_SUBREGIONS,
+ SHOUDU_WATERFRONT_DISPLAY_NAME: SHOUDU_WATERFRONT_DISPLAY_SUBREGIONS,
+ POKO_POKO_DESERT_DISPLAY_NAME: POKO_POKO_DESERT_DISPLAY_SUBREGIONS,
+ SARA_SARA_BAZAAR_DISPLAY_NAME: SARA_SARA_BAZAAR_DISPLAY_SUBREGIONS,
+ SARA_SARA_BEACH_EAST_DISPLAY_NAME: SARA_SARA_BEACH_EAST_DISPLAY_SUBREGIONS,
+ SARA_SARA_BEACH_WEST_DISPLAY_NAME: SARA_SARA_BEACH_WEST_DISPLAY_SUBREGIONS,
+ ANCIENT_RESERVOIR_DISPLAY_NAME: ANCIENT_RESERVOIR_DISPLAY_SUBREGIONS,
+ SALMON_BAY_DISPLAY_NAME: SALMON_BAY_DISPLAY_SUBREGIONS,
+ #Expert
+ THE_OPEN_SEA_DISPLAY_NAME: THE_OPEN_SEA_DISPLAY_SUBREGIONS,
+ SHOUDU_PROVINCE_DISPLAY_NAME: SHOUDU_PROVINCE_DISPLAY_SUBREGIONS,
+ THE_UNDERCITY_DISPLAY_NAME: THE_UNDERCITY_DISPLAY_SUBREGIONS,
+ GANYMEDE_SHRINE_DISPLAY_NAME: GANYMEDE_SHRINE_DISPLAY_SUBREGIONS,
+ BEAURIOR_VOLCANO_DISPLAY_NAME: BEAURIOR_VOLCANO_DISPLAY_SUBREGIONS,
+ BEAURIOR_ROCK_DISPLAY_NAME: BEAURIOR_ROCK_DISPLAY_SUBREGIONS,
+ LAKE_DELENDE_DISPLAY_NAME: LAKE_DELENDE_DISPLAY_SUBREGIONS,
+ QUINTAR_RESERVE_DISPLAY_NAME: QUINTAR_RESERVE_DISPLAY_SUBREGIONS,
+ DIONE_SHRINE_DISPLAY_NAME: DIONE_SHRINE_DISPLAY_SUBREGIONS,
+ QUINTAR_MAUSOLEUM_DISPLAY_NAME: QUINTAR_MAUSOLEUM_DISPLAY_SUBREGIONS,
+ EASTERN_CHASM_DISPLAY_NAME: EASTERN_CHASM_DISPLAY_SUBREGIONS,
+ TALL_TALL_HEIGHTS_DISPLAY_NAME: TALL_TALL_HEIGHTS_DISPLAY_SUBREGIONS,
+ NORTHERN_CAVE_DISPLAY_NAME: NORTHERN_CAVE_DISPLAY_SUBREGIONS,
+ LANDS_END_DISPLAY_NAME: LANDS_END_DISPLAY_SUBREGIONS,
+ SLIP_GLIDE_RIDE_DISPLAY_NAME: SLIP_GLIDE_RIDE_DISPLAY_SUBREGIONS,
+ SEQUOIA_ATHENAEUM_DISPLAY_NAME: SEQUOIA_ATHENAEUM_DISPLAY_SUBREGIONS,
+ NORTHERN_STRETCH_DISPLAY_NAME: NORTHERN_STRETCH_DISPLAY_SUBREGIONS,
+ CASTLE_RAMPARTS_DISPLAY_NAME: CASTLE_RAMPARTS_DISPLAY_SUBREGIONS,
+ THE_CHALICE_OF_TAR_DISPLAY_NAME: THE_CHALICE_OF_TAR_DISPLAY_SUBREGIONS,
+ FLYERS_CRAG_DISPLAY_NAME: FLYERS_CRAG_DISPLAY_SUBREGIONS,
+ JIDAMBA_TANGLE_DISPLAY_NAME: JIDAMBA_TANGLE_DISPLAY_SUBREGIONS,
+ JIDAMBA_EACLANEYA_DISPLAY_NAME: JIDAMBA_EACLANEYA_DISPLAY_SUBREGIONS,
+ THE_DEEP_SEA_DISPLAY_NAME: THE_DEEP_SEA_DISPLAY_SUBREGIONS,
+ NEPTUNE_SHRINE_DISPLAY_NAME: NEPTUNE_SHRINE_DISPLAY_SUBREGIONS,
+ JADE_CAVERN_DISPLAY_NAME: JADE_CAVERN_DISPLAY_SUBREGIONS,
+ CONTINENTAL_TRAM_DISPLAY_NAME: CONTINENTAL_TRAM_DISPLAY_SUBREGIONS,
+ #End Game
+ ANCIENT_LABYRINTH_DISPLAY_NAME: ANCIENT_LABYRINTH_DISPLAY_SUBREGIONS,
+ THE_SEQUOIA_DISPLAY_NAME: THE_SEQUOIA_DISPLAY_SUBREGIONS,
+ THE_DEPTHS_DISPLAY_NAME: THE_DEPTHS_DISPLAY_SUBREGIONS,
+ CASTLE_SEQUOIA_DISPLAY_NAME: CASTLE_SEQUOIA_DISPLAY_SUBREGIONS,
+ THE_OLD_WORLD_DISPLAY_NAME: THE_OLD_WORLD_DISPLAY_SUBREGIONS,
+ THE_NEW_WORLD_DISPLAY_NAME: THE_NEW_WORLD_DISPLAY_SUBREGIONS,
+ MODDED_ZONE_DISPLAY_NAME: MODDED_ZONE_DISPLAY_SUBREGIONS,
+}
+
+display_region_levels_dictionary: Dict[str, Tuple[int, int]] = {
+ #Beginner
+ MENU_DISPLAY_NAME: (0, 0),
+ SPAWNING_MEADOWS_DISPLAY_NAME: (3, 3),
+ DELENDE_DISPLAY_NAME: (5, 10),
+ SOILED_DEN_DISPLAY_NAME: (7, 7),
+ THE_PALE_GROTTO_DISPLAY_NAME: (7, 10),
+ SEASIDE_CLIFFS_DISPLAY_NAME: (10, 15),
+ DRAFT_SHAFT_CONDUIT_DISPLAY_NAME: (12, 12),
+ MERCURY_SHRINE_DISPLAY_NAME: (0, 0),
+ YAMAGAWA_MA_DISPLAY_NAME: (15, 15),
+ PROVING_MEADOWS_DISPLAY_NAME: (0, 0),
+ SKUMPARADISE_DISPLAY_NAME: (15, 17),
+ #Advanced
+ CAPITAL_SEQUOIA_DISPLAY_NAME: (0, 0),
+ JOJO_SEWERS_DISPLAY_NAME: (20, 22),
+ BOOMER_SOCIETY_DISPLAY_NAME: (0, 0),
+ ROLLING_QUINTAR_FIELDS_DISPLAY_NAME: (18, 21),
+ QUINTAR_NEST_DISPLAY_NAME: (18, 22),
+ QUINTAR_SANCTUM_DISPLAY_NAME: (21, 21),
+ CAPITAL_JAIL_DISPLAY_NAME: (24, 27),
+ CAPITAL_PIPELINE_DISPLAY_NAME: (50, 50),
+ COBBLESTONE_CRAG_DISPLAY_NAME: (0, 0),
+ OKIMOTO_NS_DISPLAY_NAME: (27, 31),
+ GREENSHIRE_REPRISE_DISPLAY_NAME: (33, 33),
+ SALMON_PASS_DISPLAY_NAME: (0, 0),
+ SALMON_RIVER_DISPLAY_NAME: (26, 29),
+ SHOUDU_WATERFRONT_DISPLAY_NAME: (0, 0),
+ POKO_POKO_DESERT_DISPLAY_NAME: (POKO_POKO_ENEMY_LEVEL, 32),
+ SARA_SARA_BAZAAR_DISPLAY_NAME: (0, 0),
+ SARA_SARA_BEACH_EAST_DISPLAY_NAME: (30, 30),
+ SARA_SARA_BEACH_WEST_DISPLAY_NAME: (38, 40),
+ ANCIENT_RESERVOIR_DISPLAY_NAME: (33, 35),
+ #IBEK_CAVE_AP_REGION: (35, 35),
+ SALMON_BAY_DISPLAY_NAME: (0, 0),
+ #Expert
+ THE_OPEN_SEA_DISPLAY_NAME: (54, 56),
+ SHOUDU_PROVINCE_DISPLAY_NAME: (36, 37),
+ THE_UNDERCITY_DISPLAY_NAME: (37, 39),
+ GANYMEDE_SHRINE_DISPLAY_NAME: (0, 0),
+ BEAURIOR_VOLCANO_DISPLAY_NAME: (37, 37),
+ BEAURIOR_ROCK_DISPLAY_NAME: (38, 40),
+ LAKE_DELENDE_DISPLAY_NAME: (40, 40),
+ QUINTAR_RESERVE_DISPLAY_NAME: (0, 0),
+ DIONE_SHRINE_DISPLAY_NAME: (0, 0),
+ QUINTAR_MAUSOLEUM_DISPLAY_NAME: (54, 56),
+ EASTERN_CHASM_DISPLAY_NAME: (0, 0),
+ TALL_TALL_HEIGHTS_DISPLAY_NAME: (41, 45),
+ NORTHERN_CAVE_DISPLAY_NAME: (43, 44),
+ LANDS_END_DISPLAY_NAME: (44, 47),
+ SLIP_GLIDE_RIDE_DISPLAY_NAME: (46, 48),
+ SEQUOIA_ATHENAEUM_DISPLAY_NAME: (0, 0),
+ NORTHERN_STRETCH_DISPLAY_NAME: (0, 0),
+ CASTLE_RAMPARTS_DISPLAY_NAME: (50, 50),
+ THE_CHALICE_OF_TAR_DISPLAY_NAME: (60, 60),
+ FLYERS_CRAG_DISPLAY_NAME: (0, 0),
+ JIDAMBA_TANGLE_DISPLAY_NAME: (50, 54),
+ JIDAMBA_EACLANEYA_DISPLAY_NAME: (54, 57),
+ THE_DEEP_SEA_DISPLAY_NAME: (58, 64),
+ NEPTUNE_SHRINE_DISPLAY_NAME: (0, 0),
+ JADE_CAVERN_DISPLAY_NAME: (57, 57),
+ CONTINENTAL_TRAM_DISPLAY_NAME: (0, 0),
+ #End Game
+ ANCIENT_LABYRINTH_DISPLAY_NAME: (62, 66),
+ THE_SEQUOIA_DISPLAY_NAME: (60, 63),
+ THE_DEPTHS_DISPLAY_NAME: (63, 65),
+ CASTLE_SEQUOIA_DISPLAY_NAME: (56, 59),
+ THE_OLD_WORLD_DISPLAY_NAME: (PERICULUM_FIGHT_LEVEL, PERICULUM_FIGHT_LEVEL),
+ THE_NEW_WORLD_DISPLAY_NAME: (60, 60),
+ MODDED_ZONE_DISPLAY_NAME: (30, 30),
+}
+
+rules_on_display_regions: Dict[str, Callable[[CollectionState], bool]] = {}
+
+class CrystalProjectLocation(Location):
+ game: str = "CrystalProject"
+
+ def __init__(self, player: int, name: str = " ", address = None, parent=None):
+ super().__init__(player, name, address, parent)
+
+def init_ap_region_to_display_region_dictionary() -> None:
+ for display_region in display_region_subregions_dictionary:
+ for ap_region in display_region_subregions_dictionary[display_region]:
+ ap_region_to_display_region_dictionary[ap_region] = display_region
+
+def init_areas(world: "CrystalProjectWorld", locations: List[LocationData], options: CrystalProjectOptions) -> None:
+ multiworld = world.multiworld
+ player = world.player
+ logic = CrystalProjectLogic(player, options)
+
+ rules_on_display_regions[MODDED_ZONE_AP_REGION] = lambda state: True
+
+ for display_region in display_region_levels_dictionary:
+ if world.options.regionsanity.value != world.options.regionsanity.option_disabled and display_region != MODDED_ZONE_AP_REGION and display_region != MENU_AP_REGION:
+ rules_on_display_regions[display_region] = lambda state, lambda_region = display_region: (logic.is_area_in_level_range(state, display_region_levels_dictionary[lambda_region][0])
+ and state.has(display_region_name_to_pass_dict[lambda_region], player))
+
+ rules_on_display_regions[MODDED_ZONE_AP_REGION] = combine_callables(rules_on_display_regions[MODDED_ZONE_AP_REGION], rules_on_display_regions[display_region])
+ else:
+ rules_on_display_regions[display_region] = lambda state, lambda_region = display_region: (logic.is_area_in_level_range(state, display_region_levels_dictionary[lambda_region][0]))
+
+ locations_per_region = get_locations_per_ap_region(locations)
+
+ excluded = False
+
+ beginner_regions = [
+ create_display_region(world, player, locations_per_region, MENU_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SPAWNING_MEADOWS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, DELENDE_DISPLAY_NAME, excluded),
+ # bumped up mercury shrine because region order influences shop price NOTE TO DRAGONS, DO NOT MOVE WITHOUT REASON!!
+ create_display_region(world, player, locations_per_region, MERCURY_SHRINE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SOILED_DEN_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_PALE_GROTTO_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SEASIDE_CLIFFS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, DRAFT_SHAFT_CONDUIT_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, YAMAGAWA_MA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, PROVING_MEADOWS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SKUMPARADISE_DISPLAY_NAME, excluded),
+ ]
+
+ if (options.included_regions == options.included_regions.option_advanced or
+ options.included_regions == options.included_regions.option_expert or
+ options.included_regions == options.included_regions.option_all):
+ excluded = False
+ else:
+ excluded = True
+
+ advanced_regions = [
+ create_display_region(world, player, locations_per_region, CAPITAL_SEQUOIA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, JOJO_SEWERS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, BOOMER_SOCIETY_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, QUINTAR_NEST_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, QUINTAR_SANCTUM_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, CAPITAL_JAIL_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, CAPITAL_PIPELINE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, COBBLESTONE_CRAG_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, OKIMOTO_NS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, GREENSHIRE_REPRISE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SALMON_PASS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SALMON_RIVER_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SHOUDU_WATERFRONT_DISPLAY_NAME, excluded), #moved from Expert to Advanced
+ create_display_region(world, player, locations_per_region, POKO_POKO_DESERT_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SARA_SARA_BAZAAR_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SARA_SARA_BEACH_EAST_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SARA_SARA_BEACH_WEST_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, ANCIENT_RESERVOIR_DISPLAY_NAME, excluded),
+ #create_display_region(world, player, locations_per_region, IBEK_CAVE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SALMON_BAY_DISPLAY_NAME, excluded),
+ ]
+
+ if (options.included_regions == options.included_regions.option_expert or
+ options.included_regions == options.included_regions.option_all):
+ excluded = False
+ else:
+ excluded = True
+
+ expert_regions = [
+ create_display_region(world, player, locations_per_region, THE_OPEN_SEA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SHOUDU_PROVINCE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_UNDERCITY_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, GANYMEDE_SHRINE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, BEAURIOR_VOLCANO_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, BEAURIOR_ROCK_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, LAKE_DELENDE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, QUINTAR_RESERVE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, DIONE_SHRINE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, QUINTAR_MAUSOLEUM_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, EASTERN_CHASM_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, TALL_TALL_HEIGHTS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, NORTHERN_CAVE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, LANDS_END_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SLIP_GLIDE_RIDE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, SEQUOIA_ATHENAEUM_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, NORTHERN_STRETCH_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, CASTLE_RAMPARTS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_CHALICE_OF_TAR_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, FLYERS_CRAG_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, JIDAMBA_TANGLE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, JIDAMBA_EACLANEYA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_DEEP_SEA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, NEPTUNE_SHRINE_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, JADE_CAVERN_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, CONTINENTAL_TRAM_DISPLAY_NAME, excluded),
+ ]
+
+ if options.included_regions == options.included_regions.option_all:
+ excluded = False
+ else:
+ excluded = True
+
+ end_game_regions = [
+ create_display_region(world, player, locations_per_region, ANCIENT_LABYRINTH_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_SEQUOIA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_DEPTHS_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, CASTLE_SEQUOIA_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_OLD_WORLD_DISPLAY_NAME, excluded),
+ create_display_region(world, player, locations_per_region, THE_NEW_WORLD_DISPLAY_NAME, excluded),
+ ]
+
+ if options.use_mods.value == options.use_mods.option_true:
+ excluded = False
+ else:
+ excluded = True
+
+ modded_regions = [
+ create_display_region(world, player, locations_per_region, MODDED_ZONE_DISPLAY_NAME, excluded),
+ ]
+
+ for beginner_display_region_subregions in beginner_regions:
+ multiworld.regions += beginner_display_region_subregions
+ for advanced_display_region_subregions in advanced_regions:
+ multiworld.regions += advanced_display_region_subregions
+ for expert_display_region_subregions in expert_regions:
+ multiworld.regions += expert_display_region_subregions
+ for end_game_display_region_subregions in end_game_regions:
+ multiworld.regions += end_game_display_region_subregions
+ for modded_display_region_subregions in modded_regions:
+ multiworld.regions += modded_display_region_subregions
+
+ connect_menu_region(world, options)
+
+ fancy_add_exits(world, SPAWNING_MEADOWS_AP_REGION, [DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION],
+ {CONTINENTAL_TRAM_AP_REGION: lambda state: logic.has_swimming(state) and logic.obscure_routes_on(state),
+ MERCURY_SHRINE_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.obscure_routes_on(state),
+ BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ YAMAGAWA_MA_AP_REGION: lambda state: logic.has_swimming(state) or logic.has_vertical_movement(state)})
+ #Delende start
+ fancy_add_exits(world, DELENDE_PLAINS_AP_REGION, [ATOP_FISH_HATCHERY_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, SPAWNING_MEADOWS_AP_REGION, MERCURY_SHRINE_AP_REGION, SOILED_DENLENDE_AP_REGION, THE_PALE_GROTTO_AP_REGION, SEASIDE_CLIFFS_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, SALMON_PASS_WEST_AP_REGION, SALMON_PASS_EAST_AP_REGION, LAKE_DELENDE_AP_REGION],
+ #Atop Fish Hatchery's sole purpose is to indicate the obscure route from Delende Plains -> Greenshire without mounts, but Archipelago doesn't like it when you can't reach a region with default settings,
+ # so ibek + owl have been added to its entrance. You can already get from Delende Plains -> Greenshire with those so I'm not adding those on the exit from Atop Fish Hatchery
+ {ATOP_FISH_HATCHERY_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state) or logic.has_glide(state),
+ DELENDE_HIGH_BRIDGES_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state) or logic.has_glide(state),
+ GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state) or logic.has_swimming(state),
+ SALMON_PASS_WEST_AP_REGION: lambda state: logic.has_swimming(state),
+ SALMON_PASS_EAST_AP_REGION: lambda state: logic.has_swimming(state),
+ LAKE_DELENDE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, ATOP_FISH_HATCHERY_AP_REGION, [DELENDE_PLAINS_AP_REGION, GREENSHIRE_REPRISE_AP_REGION],
+ {GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.obscure_routes_on(state)})
+ fancy_add_exits(world, DELENDE_HIGH_BRIDGES_AP_REGION, [DELENDE_PLAINS_AP_REGION, GRAN_AP_REGION, BELOW_GRAN_AP_REGION, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, DELENDE_PEAK_AP_REGION, HEART_TARN_AP_REGION, YAMAGAWA_MA_AP_REGION, FENCERS_KEEP_CHEST_AP_REGION, THE_PALE_GROTTO_AP_REGION, SEASIDE_CLIFFS_AP_REGION, CLIFF_OVER_SEASIDE_CAMP_AP_REGION, PROVING_MEADOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, LAKE_DELENDE_AP_REGION],
+ {GRAN_AP_REGION: lambda state: logic.can_fight_gran(state),
+ BELOW_GRAN_AP_REGION: lambda state: logic.obscure_routes_on(state),
+ DELENDE_PEAK_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ HEART_TARN_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
+ FENCERS_KEEP_CHEST_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ CLIFF_OVER_SEASIDE_CAMP_AP_REGION: lambda state: logic.obscure_routes_on(state),
+ CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_swimming(state),
+ LAKE_DELENDE_AP_REGION: lambda state: logic.obscure_routes_on(state)})
+ fancy_add_exits(world, GRAN_AP_REGION, [DELENDE_HIGH_BRIDGES_AP_REGION, BELOW_GRAN_AP_REGION])
+ fancy_add_exits(world, BELOW_GRAN_AP_REGION, [GRAN_AP_REGION, ANCIENT_RESERVOIR_AP_REGION, JADE_WATERWAYS_AP_REGION],
+ {GRAN_AP_REGION: lambda state: logic.can_fight_gran(state),
+ ANCIENT_RESERVOIR_AP_REGION: lambda state: logic.has_swimming(state),
+ JADE_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, [DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION])
+ fancy_add_exits(world, HEART_TARN_AP_REGION, [DELENDE_MESA_OVER_SPOOKY_CAVE_AP_REGION, DELENDE_PEAK_AP_REGION, GREENSHIRE_REPRISE_AP_REGION],
+ {DELENDE_PEAK_AP_REGION: lambda state: logic.has_glide(state),
+ GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, DELENDE_PEAK_AP_REGION, [DELENDE_HIGH_BRIDGES_AP_REGION, SEASIDE_CLIFFS_AP_REGION, CLIFF_OVER_SEASIDE_CAMP_AP_REGION])
+
+ #Delende end
+ fancy_add_exits(world, MERCURY_SHRINE_AP_REGION, [DELENDE_PLAINS_AP_REGION, SEASIDE_CLIFFS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION],
+ {BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, SOILED_DENLENDE_AP_REGION, [DELENDE_PLAINS_AP_REGION, THE_BANGLER_AP_REGION, THE_PALE_GROTTO_AP_REGION, JADE_WATERWAYS_AP_REGION],
+ {THE_BANGLER_AP_REGION: lambda state: logic.has_swimming(state),
+ JADE_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_PALE_GROTTO_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, THE_BANGLER_AP_REGION, [SOILED_DENLENDE_AP_REGION, SEASIDE_CLIFFS_AP_REGION, DRAFT_SHAFT_CONDUIT_AP_REGION],
+ {SOILED_DENLENDE_AP_REGION: lambda state: logic.has_swimming(state),
+ DRAFT_SHAFT_CONDUIT_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, THE_PALE_GROTTO_AP_REGION, [DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, SOILED_DENLENDE_AP_REGION, PROVING_MEADOWS_AP_REGION, JOJO_SEWERS_AP_REGION, LOWER_ICE_LAKES_AP_REGION, SALMON_PASS_EAST_AP_REGION],
+ {SOILED_DENLENDE_AP_REGION: lambda state: logic.has_swimming(state),
+ JOJO_SEWERS_AP_REGION: lambda state: logic.has_swimming(state),
+ LOWER_ICE_LAKES_AP_REGION: lambda state: logic.has_swimming(state),
+ SALMON_PASS_EAST_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Seaside Cliffs start
+ fancy_add_exits(world, SEASIDE_CLIFFS_AP_REGION, [CLIFF_OVER_SEASIDE_CAMP_AP_REGION, DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_PEAK_AP_REGION, MERCURY_SHRINE_AP_REGION, THE_BANGLER_AP_REGION, DRAFT_SHAFT_CONDUIT_AP_REGION, THE_OPEN_SEA_AP_REGION, BEAURIOR_VOLCANO_AP_REGION],
+ {CLIFF_OVER_SEASIDE_CAMP_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ DELENDE_PEAK_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, CLIFF_OVER_SEASIDE_CAMP_AP_REGION, [SEASIDE_CLIFFS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_PEAK_AP_REGION],
+ {DELENDE_PEAK_AP_REGION: lambda state: logic.obscure_routes_on(state)})
+ #Seaside Cliffs end
+ fancy_add_exits(world, DRAFT_SHAFT_CONDUIT_AP_REGION, [SEASIDE_CLIFFS_AP_REGION, THE_BANGLER_AP_REGION],
+ {THE_BANGLER_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Yamagawa M.A. start
+ fancy_add_exits(world, YAMAGAWA_MA_AP_REGION, [FENCERS_KEEP_CHEST_AP_REGION, SPAWNING_MEADOWS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, HEART_TARN_AP_REGION, LAKE_DELENDE_AP_REGION, ATOP_DAM_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION, TOWER_OF_ZOT_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION],
+ {FENCERS_KEEP_CHEST_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state),
+ HEART_TARN_AP_REGION: lambda state: (logic.obscure_routes_on(state) or logic.has_vertical_movement(state)) and logic.has_glide(state),
+ LAKE_DELENDE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.obscure_routes_on(state),
+ ATOP_DAM_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
+ SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
+ SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
+ TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
+ POKO_POKO_LAKE_DELENDE_PASS_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state)})
+ fancy_add_exits(world, FENCERS_KEEP_CHEST_AP_REGION, [YAMAGAWA_MA_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION],
+ {YAMAGAWA_MA_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ #Yamagawa M.A. end
+ #Proving Meadows start
+ fancy_add_exits(world, PROVING_MEADOWS_AP_REGION, [DELENDE_HIGH_BRIDGES_AP_REGION, THE_PALE_GROTTO_AP_REGION, PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, COBBLESTONE_CRAG_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {CAPITAL_SEQUOIA_AP_REGION: lambda state: logic.has_glide(state),
+ CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ #A Proving Meadows <-> Skumparadise connector; untraversable w/o a region pass in Regionsanity
+ fancy_add_exits(world, PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION, [PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION],
+ {PROVING_MEADOWS_AP_REGION: lambda state: logic.has_jobs(state, 3),
+ SKUMPARADISE_AP_REGION: lambda state: logic.has_jobs(state, 3)})
+ #Proving Meadows end
+ fancy_add_exits(world, SKUMPARADISE_AP_REGION, [PROVING_MEADOWS_SKUMPARADISE_CONNECTOR_AP_REGION, CAPITAL_SEQUOIA_AP_REGION])
+ #Capital Sequoia start
+ fancy_add_exits(world, CAPITAL_SEQUOIA_AP_REGION, [MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BEATSMITH_DISCO_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION],
+ # Obscure Routes: it is possible to jump from Rolling Quintar Fields onto the Capital Sequoia walls from the southeast and manage to bypass the guard and thus the job requirement
+ {MOAT_SHALLOWS_AP_REGION: lambda state: logic.has_key(state, COURTYARD_KEY) or (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ CAPITAL_MOAT_AP_REGION: lambda state: (logic.has_jobs(state, 5) or state.has(GAEA_STONE, player) or (logic.obscure_routes_on(state) and logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)) and logic.has_swimming(state),
+ BEATSMITH_DISCO_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ BOOMER_SOCIETY_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_key(state, COURTYARD_KEY) or logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state),
+ GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_jobs(state, 5) or state.has(GAEA_STONE, player) or (logic.obscure_routes_on(state) and logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME)) or logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ RAMPART_ATOP_PORTCULLIS_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.has_vertical_movement(state),
+ #note for eme: technically possible to get into the first dungeon with quintar instead of glide, but it's hard lol; come from Quintar Sanctum save point and go west up mountain and fall down through grate (that part's easy) then the quintar jump to the lamp is hard
+ CASTLE_SEQUOIA_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, MOAT_SHALLOWS_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, JOJO_SEWERS_AP_REGION, COBBLESTONE_CRAG_AP_REGION],
+ {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, CAPITAL_MOAT_AP_REGION, [MOAT_SHALLOWS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, JOJO_SEWERS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {MOAT_SHALLOWS_AP_REGION: lambda state: logic.has_swimming(state),
+ DELENDE_HIGH_BRIDGES_AP_REGION: lambda state: logic.has_swimming(state),
+ JOJO_SEWERS_AP_REGION: lambda state: logic.has_swimming(state),
+ COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_swimming(state),
+ GREENSHIRE_REPRISE_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, BEATSMITH_DISCO_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION])
+ #Capital Sequoia end
+ #Jojo Sewers section start
+ fancy_add_exits(world, JOJO_SEWERS_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, SEWERS_SECRET_PASSWORD_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION, THE_PALE_GROTTO_AP_REGION, CAPITAL_JAIL_AP_REGION, QUINTAR_NEST_AP_REGION],
+ #Swimming connection to Sewers to Boomer Society bypasses the Secret Password region
+ {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ SEWERS_TO_BOOMER_SOCIETY_AP_REGION: lambda state: logic.has_swimming(state),
+ CAPITAL_JAIL_AP_REGION: lambda state: logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_swimming(state),
+ THE_PALE_GROTTO_AP_REGION: lambda state: logic.has_swimming(state),
+ QUINTAR_NEST_AP_REGION: lambda state: (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_swimming(state))})
+ #A Jojo Sewers <-> Sewers to Boomer Society connector; untraversable w/o a region pass in Regionsanity
+ fancy_add_exits(world, SEWERS_SECRET_PASSWORD_AP_REGION, [JOJO_SEWERS_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION],
+ {JOJO_SEWERS_AP_REGION: lambda state: state.has(JOJO_SEWERS_PASS, player) or options.regionsanity.value == options.regionsanity.option_disabled,
+ SEWERS_TO_BOOMER_SOCIETY_AP_REGION: lambda state: state.has(JOJO_SEWERS_PASS, player) or options.regionsanity.value == options.regionsanity.option_disabled})
+ fancy_add_exits(world, SEWERS_TO_BOOMER_SOCIETY_AP_REGION, [JOJO_SEWERS_AP_REGION, SEWERS_SECRET_PASSWORD_AP_REGION, BOOMER_SOCIETY_AP_REGION],
+ #Swimming connection to Jojo Sewers Main (lol) bypasses the Secret Password region
+ {JOJO_SEWERS_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Jojo Sewers section end
+ fancy_add_exits(world, BOOMER_SOCIETY_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, BEATSMITH_DISCO_AP_REGION, SEWERS_TO_BOOMER_SOCIETY_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, BOOMER_OVERLOOK_AP_REGION],
+ {BEATSMITH_DISCO_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state),
+ BOOMER_OVERLOOK_AP_REGION: lambda state: logic.has_horizontal_movement(state)})
+ #Rolling Quintar Fields start
+ fancy_add_exits(world, ROLLING_QUINTAR_FIELDS_AP_REGION, [ROLLING_TREETOP_HIGHWAY_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, QUINTAR_NEST_AP_REGION, OKIMOTO_NS_AP_REGION, QUINTAR_RESERVE_AP_REGION],
+ #Obscure Route: Rolling Quintar Fields -> Sanctum Entrance; jump up to the "Pinnacle by short and tall box friends" check from the Quintar Enthusiast's House (auto-jump helps)
+ {ROLLING_TREETOP_HIGHWAY_AP_REGION: lambda state: logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state),
+ SANCTUM_ENTRANCE_AP_REGION: lambda state: logic.obscure_routes_on(state),
+ OKIMOTO_NS_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, ROLLING_TREETOP_HIGHWAY_AP_REGION, [ROLLING_QUINTAR_FIELDS_AP_REGION, SANCTUM_ENTRANCE_AP_REGION],
+ {SANCTUM_ENTRANCE_AP_REGION: lambda state: (logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state)),})
+ fancy_add_exits(world, SANCTUM_ENTRANCE_AP_REGION, [ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_TREETOP_HIGHWAY_AP_REGION, HUNTERS_TOWER_AP_REGION, QUINTAR_SANCTUM_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION],
+ {ROLLING_TREETOP_HIGHWAY_AP_REGION: lambda state: logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME) or logic.has_vertical_movement(state),
+ HUNTERS_TOWER_AP_REGION: lambda state: logic.obscure_routes_on(state) or logic.has_vertical_movement(state),
+ SANCTUM_EXIT_CLIFFTOP_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)})
+ fancy_add_exits(world, HUNTERS_TOWER_AP_REGION, [ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_TREETOP_HIGHWAY_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION],
+ {SANCTUM_EXIT_CLIFFTOP_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state)})
+ #Rolling Quintar Fields end
+ fancy_add_exits(world, QUINTAR_NEST_AP_REGION, [QUINTAR_SANCTUM_AP_REGION, COBBLESTONE_CRAG_AP_REGION, JOJO_SEWERS_AP_REGION],
+ {QUINTAR_SANCTUM_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Quintar Sanctum start
+ fancy_add_exits(world, QUINTAR_SANCTUM_AP_REGION, [SANCTUM_EXIT_CLIFFTOP_AP_REGION, QUINTAR_NEST_AP_REGION, QUINTAR_MAUSOLEUM_AP_REGION],
+ {QUINTAR_MAUSOLEUM_AP_REGION: lambda state: logic.has_swimming(state),
+ QUINTAR_NEST_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, SANCTUM_EXIT_CLIFFTOP_AP_REGION, [QUINTAR_SANCTUM_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SANCTUM_ENTRANCE_AP_REGION, HUNTERS_TOWER_AP_REGION, OKIMOTO_NS_AP_REGION, QUINTAR_RESERVE_AP_REGION],
+ {HUNTERS_TOWER_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ OKIMOTO_NS_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ #Quintar Sanctum end
+ fancy_add_exits(world, CAPITAL_JAIL_AP_REGION, [JOJO_SEWERS_AP_REGION, JAIL_SOUTH_WING_AP_REGION],
+ {JAIL_SOUTH_WING_AP_REGION: lambda state: logic.has_key(state, SOUTH_WING_KEY)})
+ fancy_add_exits(world, JAIL_SOUTH_WING_AP_REGION, [CAPITAL_JAIL_AP_REGION, JAIL_SOUTH_WING_RUBBLE_AP_REGION],
+ {CAPITAL_JAIL_AP_REGION: lambda state: logic.has_key(state, SOUTH_WING_KEY),
+ JAIL_SOUTH_WING_RUBBLE_AP_REGION: lambda state: logic.has_key(state, CELL_KEY, 6)})
+ fancy_add_exits(world, JAIL_SOUTH_WING_RUBBLE_AP_REGION, [JAIL_SOUTH_WING_AP_REGION, PIPELINE_NORTH_AP_REGION],
+ {JAIL_SOUTH_WING_AP_REGION: lambda state: logic.has_key(state, CELL_KEY, 6)})
+ fancy_add_exits(world, PIPELINE_NORTH_AP_REGION, [PIPELINE_SOUTH_AP_REGION, JAIL_SOUTH_WING_RUBBLE_AP_REGION],
+ {PIPELINE_SOUTH_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, PIPELINE_SOUTH_AP_REGION, [PIPELINE_NORTH_AP_REGION, CONTINENTAL_TRAM_AP_REGION, PIPELINE_JIDAMBA_CONNECTOR_AP_REGION])
+ #A Pipeline <-> Jidamba connector; untraversable w/o region pass in Regionsanity
+ fancy_add_exits(world, PIPELINE_JIDAMBA_CONNECTOR_AP_REGION, [PIPELINE_SOUTH_AP_REGION, JIDAMBA_CAVE_AP_REGION])
+ fancy_add_exits(world, COBBLESTONE_CRAG_AP_REGION, [PROVING_MEADOWS_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, THE_OPEN_SEA_AP_REGION, SHOUDU_WATERFRONT_AP_REGION, OKIMOTO_NS_AP_REGION],
+ {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ SHOUDU_WATERFRONT_AP_REGION: lambda state: logic.has_horizontal_movement(state),
+ OKIMOTO_NS_AP_REGION: lambda state: logic.has_horizontal_movement(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, OKIMOTO_NS_AP_REGION, [COBBLESTONE_CRAG_AP_REGION, THE_OPEN_SEA_AP_REGION, FLYERS_CRAG_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ FLYERS_CRAG_AP_REGION: lambda state: (logic.has_glide(state) and logic.has_vertical_movement(state)) or logic.has_swimming(state)})
+ fancy_add_exits(world, GREENSHIRE_REPRISE_AP_REGION, [DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, CAPITAL_MOAT_AP_REGION, SALMON_PASS_EAST_AP_REGION, GREENSHIRE_OVERLOOK_AP_REGION],
+ #Obscure Routes: it is possible to jump from Rolling Quintar Fields onto the Capital Sequoia walls from the southeast and manage to bypass the guard and thus the job requirement
+ {CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ SALMON_PASS_EAST_AP_REGION: lambda state: ((logic.has_jobs(state, 5) or logic.obscure_routes_on(state)) and logic.has_rental_quintar(state, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME)) or logic.has_vertical_movement(state),
+ GREENSHIRE_OVERLOOK_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, SALMON_PASS_EAST_AP_REGION, [GREENSHIRE_REPRISE_AP_REGION, SALMON_PASS_WEST_AP_REGION, DELENDE_PLAINS_AP_REGION],
+ {SALMON_PASS_WEST_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state),
+ DELENDE_PLAINS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, SALMON_PASS_WEST_AP_REGION, [SALMON_PASS_EAST_AP_REGION, DELENDE_PLAINS_AP_REGION, SALMON_RIVER_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION],
+ {DELENDE_PLAINS_AP_REGION: lambda state: logic.has_swimming(state),
+ SALMON_PASS_EAST_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_swimming(state),
+ SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Salmon River start
+ fancy_add_exits(world, SALMON_RIVER_AP_REGION, [POSEIDON_SHRINE_PROPER_AP_REGION, RIVER_CATS_EGO_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, SALMON_PASS_WEST_AP_REGION, SALMON_BAY_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
+ #Note: connection to Tall Tall can be with goat (obscure), owl, (rental salmon + ibek (obscure), quintar + ibek) via poseidon shrine/salmon shack roof, or poseidon stone + ibek/owl
+ {POSEIDON_SHRINE_PROPER_AP_REGION: lambda state: (logic.has_swimming(state) and logic.obscure_routes_on(state)) or logic.has_horizontal_movement(state),
+ RIVER_CATS_EGO_AP_REGION: lambda state: (logic.obscure_routes_on(state) and logic.has_rental_salmon(state)) or logic.has_swimming(state),
+ SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ LOWER_ICE_LAKES_AP_REGION: lambda state: (logic.has_vertical_movement(state) and logic.obscure_routes_on(state)) or logic.has_glide(state)})
+ fancy_add_exits(world, POSEIDON_SHRINE_PROPER_AP_REGION, [SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
+ {POSEIDON_SHRINE_ROOF_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ MUSHROOM_MOUNTAIN_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ LOWER_ICE_LAKES_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, POSEIDON_SHRINE_ROOF_AP_REGION, [SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
+ {MUSHROOM_MOUNTAIN_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ LOWER_ICE_LAKES_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, RIVER_CATS_EGO_AP_REGION, [SALMON_RIVER_AP_REGION, MUSHROOM_MOUNTAIN_AP_REGION],
+ {SALMON_RIVER_AP_REGION: lambda state: logic.has_swimming(state),
+ MUSHROOM_MOUNTAIN_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, MUSHROOM_MOUNTAIN_AP_REGION, [SALMON_RIVER_AP_REGION, RIVER_CATS_EGO_AP_REGION, SALMON_BAY_AP_REGION],
+ {SALMON_RIVER_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state) or logic.has_swimming(state),
+ RIVER_CATS_EGO_AP_REGION: lambda state: logic.has_glide(state) and logic.has_swimming(state)})
+ fancy_add_exits(world, SOUTH_SALMON_RIVER_AP_REGION, [SALMON_RIVER_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION, SALMON_PASS_WEST_AP_REGION, ATOP_DAM_AP_REGION],
+ {SALMON_RIVER_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
+ SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_swimming(state) or logic.has_vertical_movement(state) or logic.has_glide(state),
+ SALMON_PASS_WEST_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Salmon River end
+ #Poko Poko Desert start
+ fancy_add_exits(world, POKO_POKO_DESERT_AP_REGION, [POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_BEACH_WEST_PASS_AP_REGION, TOWER_OF_ZOT_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, ANCIENT_RESERVOIR_AP_REGION],
+ {POKO_POKO_EAST_PLATEAU_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ POKO_POKO_BEACH_WEST_PASS_AP_REGION: lambda state: (logic.has_horizontal_movement(state) and logic.has_vertical_movement(state)) or logic.has_glide(state),
+ TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
+ ANCIENT_RESERVOIR_AP_REGION: lambda state: logic.has_key(state, PYRAMID_KEY)})
+ fancy_add_exits(world, POKO_POKO_EAST_PLATEAU_AP_REGION, [POKO_POKO_DESERT_AP_REGION, TOWER_OF_ZOT_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, SARA_SARA_BAZAAR_AP_REGION],
+ {TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
+ POKO_POKO_LAKE_DELENDE_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, POKO_POKO_BEACH_WEST_PASS_AP_REGION, [POKO_POKO_DESERT_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION])
+ fancy_add_exits(world, TOWER_OF_ZOT_AP_REGION, [POKO_POKO_DESERT_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION, ANCIENT_LABYRINTH_AP_REGION, SALMON_BAY_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION],
+ {POKO_POKO_EAST_PLATEAU_AP_REGION: lambda state: logic.has_glide(state),
+ ANCIENT_LABYRINTH_AP_REGION: lambda state: (state.has(POKO_POKO_DESERT_PASS, player) or options.regionsanity.value == options.regionsanity.option_disabled) and (state.has(ANCIENT_TABLET_A, player) or logic.obscure_routes_on(state)) and logic.has_vertical_movement(state) and logic.has_glide(state),
+ SALMON_BAY_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)})
+ fancy_add_exits(world, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, [POKO_POKO_EAST_PLATEAU_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, LAKE_DELENDE_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION],
+ {POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION: lambda state: logic.has_glide(state),
+ SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state)})
+ fancy_add_exits(world, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, [POKO_POKO_EAST_PLATEAU_AP_REGION, SPAWNING_MEADOWS_AP_REGION])
+ #Poko Poko Desert end
+ fancy_add_exits(world, SARA_SARA_BAZAAR_AP_REGION, [POKO_POKO_DESERT_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION, SHOUDU_PROVINCE_AP_REGION, BAZAAR_COAST_AP_REGION, CONTINENTAL_TRAM_AP_REGION],
+ #Swimming connection to both the Bazaar Coast and the Continental Tram bc you can get to the Tram without going into The Open Sea and vice versa
+ {SARA_SARA_BEACH_WEST_AP_REGION: lambda state: logic.has_rental_quintar(state, SARA_SARA_BAZAAR_DISPLAY_NAME),
+ SHOUDU_PROVINCE_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and state.has(FERRY_PASS, player),
+ BAZAAR_COAST_AP_REGION: lambda state: logic.has_swimming(state),
+ CONTINENTAL_TRAM_AP_REGION: lambda state: logic.has_swimming(state) or logic.has_key(state, TRAM_KEY)})
+ fancy_add_exits(world, SARA_SARA_BEACH_EAST_AP_REGION, [SARA_SARA_BAZAAR_AP_REGION, THE_OPEN_SEA_AP_REGION, IBEK_CAVE_AP_REGION, BEAURIOR_VOLCANO_AP_REGION],
+ {IBEK_CAVE_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ BEAURIOR_VOLCANO_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, SARA_SARA_BEACH_WEST_AP_REGION, [POKO_POKO_BEACH_WEST_PASS_AP_REGION, TOWER_OF_ZOT_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {POKO_POKO_BEACH_WEST_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.has_vertical_movement(state),
+ SARA_SARA_BAZAAR_AP_REGION: lambda state: logic.has_horizontal_movement(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Ancient Reservoir start
+ fancy_add_exits(world, ANCIENT_RESERVOIR_AP_REGION, [POKO_POKO_DESERT_AP_REGION, IBEK_CAVE_AP_REGION, BELOW_GRAN_AP_REGION],
+ {POKO_POKO_DESERT_AP_REGION: lambda state: logic.has_key(state, PYRAMID_KEY),
+ BELOW_GRAN_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, IBEK_CAVE_AP_REGION, [SARA_SARA_BEACH_EAST_AP_REGION],
+ {SARA_SARA_BEACH_EAST_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ #Ancient Reservoir end
+ #Salmon Bay start
+ fancy_add_exits(world, SALMON_BAY_AP_REGION, [THE_OPEN_SEA_AP_REGION, SALMON_RIVER_MOUTH_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ SALMON_RIVER_MOUTH_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, SALMON_RIVER_MOUTH_AP_REGION, [SALMON_BAY_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, POKO_POKO_EAST_PLATEAU_AP_REGION, TOWER_OF_ZOT_AP_REGION],
+ {SOUTH_SALMON_RIVER_AP_REGION: lambda state: (logic.has_vertical_movement(state) and logic.has_glide(state)) or logic.has_swimming(state),
+ TOWER_OF_ZOT_AP_REGION: lambda state: logic.has_glide(state)})
+ #Salmon Bay end
+ #The Open Sea start
+ fancy_add_exits(world, THE_OPEN_SEA_AP_REGION, [SEASIDE_CLIFFS_AP_REGION, PROVING_MEADOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, COBBLESTONE_CRAG_AP_REGION, OKIMOTO_NS_AP_REGION, SHOUDU_WATERFRONT_AP_REGION, BAZAAR_COAST_AP_REGION, SARA_SARA_BEACH_EAST_AP_REGION, SARA_SARA_BEACH_WEST_AP_REGION, SALMON_BAY_AP_REGION, SHOUDU_PROVINCE_AP_REGION, THE_UNDERCITY_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION, THE_DEEP_SEA_AP_REGION],
+ {SEASIDE_CLIFFS_AP_REGION: lambda state: logic.has_swimming(state),
+ PROVING_MEADOWS_AP_REGION: lambda state: logic.has_swimming(state),
+ CAPITAL_MOAT_AP_REGION: lambda state: logic.has_swimming(state),
+ COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_swimming(state),
+ OKIMOTO_NS_AP_REGION: lambda state: logic.has_swimming(state),
+ SHOUDU_WATERFRONT_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_UNDERCITY_AP_REGION: lambda state: logic.has_swimming(state),
+ BAZAAR_COAST_AP_REGION: lambda state: logic.has_swimming(state),
+ SARA_SARA_BEACH_EAST_AP_REGION: lambda state: logic.has_swimming(state),
+ SARA_SARA_BEACH_WEST_AP_REGION: lambda state: logic.has_swimming(state),
+ SALMON_BAY_AP_REGION: lambda state: logic.has_swimming(state),
+ SHOUDU_PROVINCE_AP_REGION: lambda state: logic.has_swimming(state),
+ JIDAMBA_ATOLLS_AP_REGION: lambda state: logic.has_swimming(state),
+ JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_DEEP_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, JIDAMBA_ATOLLS_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {JIDAMBA_FOREST_FLOOR_AP_REGION: lambda state: logic.has_glide(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, BAZAAR_COAST_AP_REGION, [THE_OPEN_SEA_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, CONTINENTAL_TRAM_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ SARA_SARA_BAZAAR_AP_REGION: lambda state: logic.has_swimming(state),
+ CONTINENTAL_TRAM_AP_REGION: lambda state: logic.has_swimming(state)})
+ #The Open Sea end
+ fancy_add_exits(world, SHOUDU_WATERFRONT_AP_REGION, [THE_OPEN_SEA_AP_REGION, SHOUDU_PROVINCE_AP_REGION, COBBLESTONE_CRAG_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ SHOUDU_PROVINCE_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ COBBLESTONE_CRAG_AP_REGION: lambda state: logic.has_horizontal_movement(state)})
+ fancy_add_exits(world, SHOUDU_PROVINCE_AP_REGION, [SARA_SARA_BAZAAR_AP_REGION, SHOUDU_WATERFRONT_AP_REGION, GANYMEDE_SHRINE_AP_REGION, THE_UNDERCITY_AP_REGION, QUINTAR_RESERVE_AP_REGION],
+ {SARA_SARA_BAZAAR_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and state.has(FERRY_PASS, player),
+ GANYMEDE_SHRINE_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_vertical_movement(state) and state.has(ELEVATOR_PART, player, 10)})
+ fancy_add_exits(world, THE_UNDERCITY_AP_REGION, [SHOUDU_PROVINCE_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, GANYMEDE_SHRINE_AP_REGION, [SHOUDU_PROVINCE_AP_REGION])
+ fancy_add_exits(world, BEAURIOR_VOLCANO_AP_REGION, [SARA_SARA_BEACH_EAST_AP_REGION, BEAURIOR_ROCK_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {BEAURIOR_ROCK_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, BEAURIOR_ROCK_AP_REGION, [BEAURIOR_VOLCANO_AP_REGION])
+ #Lake Delende start
+ fancy_add_exits(world, LAKE_DELENDE_AP_REGION, [ATOP_DAM_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, DELENDE_PLAINS_AP_REGION, DELENDE_HIGH_BRIDGES_AP_REGION],
+ {ATOP_DAM_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
+ POKO_POKO_LAKE_DELENDE_PASS_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ DELENDE_PLAINS_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ DELENDE_HIGH_BRIDGES_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, ATOP_DAM_AP_REGION, [LAKE_DELENDE_AP_REGION, POKO_POKO_LAKE_DELENDE_PASS_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, DELENDE_PLAINS_AP_REGION])
+ #Lake Delende end
+ #Quintar Reserve start
+ fancy_add_exits(world, QUINTAR_RESERVE_AP_REGION, [SHOUDU_PROVINCE_AP_REGION, DIONE_SHRINE_AP_REGION, QUINTAR_MAUSOLEUM_AP_REGION],
+ {QUINTAR_MAUSOLEUM_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, RESERVE_TREETOPS_AP_REGION, [QUINTAR_RESERVE_AP_REGION])
+ #Quintar Reserve end
+ #Dione Shrine start
+ fancy_add_exits(world, DIONE_SHRINE_AP_REGION, [QUINTAR_RESERVE_AP_REGION, DIONE_ROOF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
+ {DIONE_ROOF_AP_REGION: lambda state: logic.has_glide(state) and logic.has_vertical_movement(state) and logic.obscure_routes_on(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state)})
+ fancy_add_exits(world, DIONE_ROOF_AP_REGION, [DIONE_SHRINE_AP_REGION, QUINTAR_RESERVE_AP_REGION, RESERVE_TREETOPS_AP_REGION, THE_CHALICE_OF_TAR_AP_REGION],
+ {THE_CHALICE_OF_TAR_AP_REGION: lambda state: logic.has_glide(state)})
+ #Dione Shrine end
+ fancy_add_exits(world, QUINTAR_MAUSOLEUM_AP_REGION, [QUINTAR_RESERVE_AP_REGION, QUINTAR_SANCTUM_AP_REGION],
+ {QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_swimming(state),
+ QUINTAR_SANCTUM_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, EASTERN_CHASM_AP_REGION, [QUINTAR_RESERVE_AP_REGION, DIONE_ROOF_AP_REGION, THE_OPEN_SEA_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state) and logic.obscure_routes_on(state)})
+ #Tall, Tall Heights start
+ fancy_add_exits(world, BOOMER_OVERLOOK_AP_REGION, [BOOMER_SOCIETY_AP_REGION, LONE_CHEST_RAMPART_AP_REGION, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, UPPER_ICE_LAKES_AP_REGION],
+ {RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION: lambda state: logic.has_glide(state) or logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME),
+ TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, [BOOMER_OVERLOOK_AP_REGION, UPPER_ICE_LAKES_AP_REGION])
+ fancy_add_exits(world, GREENSHIRE_OVERLOOK_AP_REGION, [GREENSHIRE_REPRISE_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION],
+ {TALL_TALL_SAVE_POINT_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, TALL_TALL_SAVE_POINT_AP_REGION, [BOOMER_OVERLOOK_AP_REGION, GREENSHIRE_OVERLOOK_AP_REGION, LOWER_ICE_LAKES_AP_REGION],
+ {BOOMER_OVERLOOK_AP_REGION: lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state)})
+ fancy_add_exits(world, LOWER_ICE_LAKES_AP_REGION, [THE_PALE_GROTTO_AP_REGION, SALMON_RIVER_AP_REGION, POSEIDON_SHRINE_PROPER_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, UPPER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, LANDS_END_AP_REGION, LOWER_NORTHERN_CAVE_AP_REGION],
+ {THE_PALE_GROTTO_AP_REGION: lambda state: logic.has_swimming(state),
+ TALL_TALL_SAVE_POINT_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ UPPER_ICE_LAKES_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ LANDS_END_COTTAGE_RIDGE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ LANDS_END_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, UPPER_ICE_LAKES_AP_REGION, [BOOMER_OVERLOOK_AP_REGION, TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION, TALL_TALL_SAVE_POINT_AP_REGION, LOWER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, SOUVENIR_SHOP_AP_REGION, SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, MIDDLE_NORTHERN_CAVE_AP_REGION],
+ {BOOMER_OVERLOOK_AP_REGION: lambda state: logic.has_glide(state),
+ TALL_TALL_RAMPARTS_CRAG_CHEST_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ TALL_TALL_DIAMONDSMITH_AP_REGION: lambda state: logic.has_glide(state),
+ SOUVENIR_SHOP_AP_REGION: lambda state: logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME) or logic.has_glide(state),
+ SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, LANDS_END_COTTAGE_RIDGE_AP_REGION, [LOWER_ICE_LAKES_AP_REGION, LANDS_END_AP_REGION],
+ {LANDS_END_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state)})
+ fancy_add_exits(world, TALL_TALL_DIAMONDSMITH_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, LANDS_END_NORTHERN_PEAK_AP_REGION, NORTHERN_STRETCH_RACE_START_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {NORTHERN_STRETCH_RACE_START_AP_REGION: lambda state: logic.has_glide(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, SLIP_GLIDE_RIDE_EXIT_AP_REGION, [SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION])
+ fancy_add_exits(world, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, SLIP_GLIDE_RIDE_EXIT_AP_REGION, SOUVENIR_SHOP_AP_REGION, UPPER_NORTHERN_CAVE_AP_REGION],
+ {SLIP_GLIDE_RIDE_EXIT_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, SOUVENIR_SHOP_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION],
+ {SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, [UPPER_ICE_LAKES_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, ICY_SPIKES_MADNESS_AP_REGION, SEQUOIA_ATHENAEUM_AP_REGION],
+ {SEQUOIA_ATHENAEUM_BALCONY_AP_REGION: lambda state: logic.has_glide(state),
+ ICY_SPIKES_MADNESS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ SEQUOIA_ATHENAEUM_AP_REGION: lambda state: state.has(VERMILLION_BOOK, player) and state.has(VIRIDIAN_BOOK, player) and state.has(CERULEAN_BOOK, player)})
+ fancy_add_exits(world, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, [SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, ICY_SPIKES_MADNESS_AP_REGION],
+ {TALL_TALL_TALL_CHEST_AP_REGION: lambda state: (logic.has_vertical_movement(state) and logic.has_glide(state)) or logic.can_push_ice_block_and_goat(state, TALL_TALL_HEIGHTS_DISPLAY_NAME),
+ ICY_SPIKES_MADNESS_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state)})
+ fancy_add_exits(world, TALL_TALL_TALL_CHEST_AP_REGION, [SEQUOIA_ATHENAEUM_BALCONY_AP_REGION])
+ fancy_add_exits(world, ICY_SPIKES_MADNESS_AP_REGION, [SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, PAMOA_TREE_AP_REGION],
+ {SEQUOIA_ATHENAEUM_ENTRANCE_AP_REGION: lambda state: logic.has_glide(state),
+ SEQUOIA_ATHENAEUM_BALCONY_AP_REGION: lambda state: logic.has_glide(state),
+ PAMOA_TREE_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, PAMOA_TREE_AP_REGION, [ICY_SPIKES_MADNESS_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, HUNTERS_TOWER_AP_REGION, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, THE_CHALICE_OF_TAR_AP_REGION],
+ {ICY_SPIKES_MADNESS_AP_REGION: lambda state: logic.has_glide(state),
+ THE_CHALICE_OF_TAR_AP_REGION: lambda state: logic.has_glide(state)})
+ #Tall, Tall Heights end
+ #Northern Cave start
+ fancy_add_exits(world, UPPER_NORTHERN_CAVE_AP_REGION, [SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, SLIP_GLIDE_RIDE_AP_REGION],
+ {SLIP_GLIDE_RIDE_AP_REGION: lambda state: logic.has_glide(state) and logic.has_vertical_movement(state),
+ SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, MIDDLE_NORTHERN_CAVE_AP_REGION, [ICE_CELL_AP_REGION, LOWER_NORTHERN_CAVE_AP_REGION])
+ fancy_add_exits(world, ICE_CELL_AP_REGION, [LOWER_NORTHERN_CAVE_AP_REGION, SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION],
+ {LOWER_NORTHERN_CAVE_AP_REGION: lambda state: logic.has_key(state, ICE_CELL_KEY),
+ SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION: lambda state: logic.can_push_ice_block_and_goat(state, NORTHERN_CAVE_DISPLAY_NAME) or logic.has_glide(state)})
+ fancy_add_exits(world, LOWER_NORTHERN_CAVE_AP_REGION, [ICE_CELL_AP_REGION, LOWER_ICE_LAKES_AP_REGION])
+ #Northern Cave end
+ #Land's End start
+ fancy_add_exits(world, LANDS_END_AP_REGION, [LANDS_END_NORTHERN_PEAK_AP_REGION, OWL_TREE_AP_REGION, ATOP_FISH_HATCHERY_AP_REGION, HEART_TARN_AP_REGION, SOUTH_SALMON_RIVER_AP_REGION, ATOP_DAM_AP_REGION, LOWER_ICE_LAKES_AP_REGION, LANDS_END_COTTAGE_RIDGE_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ #From Callisto Shrine save point, you can drop down to Delende, or to South Salmon River or Atop Salmon River Dam with the quintar
+ {LANDS_END_NORTHERN_PEAK_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ OWL_TREE_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ ATOP_FISH_HATCHERY_AP_REGION: lambda state: logic.obscure_routes_on(state),
+ HEART_TARN_AP_REGION: lambda state: logic.has_glide(state),
+ SOUTH_SALMON_RIVER_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.obscure_routes_on(state),
+ ATOP_DAM_AP_REGION: lambda state: logic.has_horizontal_movement(state) and logic.obscure_routes_on(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, LANDS_END_NORTHERN_PEAK_AP_REGION, [LANDS_END_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {TALL_TALL_DIAMONDSMITH_AP_REGION: lambda state: logic.has_glide(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, OWL_TREE_AP_REGION, [LANDS_END_AP_REGION])
+ #Land's End end
+ #Slip Glide Ride start
+ fancy_add_exits(world, SLIP_GLIDE_RIDE_ENTRANCE_AP_REGION, [ICE_CELL_AP_REGION, SLIP_GLIDE_RIDE_AP_REGION],
+ {ICE_CELL_AP_REGION: lambda state: logic.has_glide(state),
+ SLIP_GLIDE_RIDE_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, SLIP_GLIDE_RIDE_AP_REGION, [SLIP_GLIDE_RIDE_EXIT_AP_REGION],
+ {SLIP_GLIDE_RIDE_EXIT_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state) and logic.has_key(state, RED_DOOR_KEY, 3)})
+ #Slip Glide Ride end
+ #Regionsanity: can't get out of Athenaeum because you can't reach the duck through the door
+ fancy_add_exits(world, SEQUOIA_ATHENAEUM_AP_REGION, [MENU_AP_REGION])
+ #Northern Stretch start
+ fancy_add_exits(world, NORTHERN_STRETCH_RACE_START_AP_REGION, [NORTHERN_STRETCH_RACE_FINISH_AP_REGION, SLIP_GLIDE_RIDE_EXIT_AP_REGION, SLIP_TO_CAVE_OR_SOUVENIRS_AP_REGION, SOUVENIR_SHOP_AP_REGION, TALL_TALL_DIAMONDSMITH_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, [NORTHERN_STRETCH_RACE_START_AP_REGION, SUMMONERS_TOWER_AP_REGION, SEQUOIA_ATHENAEUM_BALCONY_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {NORTHERN_STRETCH_RACE_START_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state),
+ SUMMONERS_TOWER_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, SUMMONERS_TOWER_AP_REGION, [NORTHERN_STRETCH_RACE_START_AP_REGION, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, THE_OPEN_SEA_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Northern Stretch end
+ #Castle Ramparts start
+ fancy_add_exits(world, RAMPART_ATOP_PORTCULLIS_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION, HUNTERS_TOWER_AP_REGION, LONE_CHEST_RAMPART_AP_REGION],
+ {LONE_CHEST_RAMPART_AP_REGION: lambda state: logic.has_vertical_movement(state)})
+ fancy_add_exits(world, LONE_CHEST_RAMPART_AP_REGION, [RAMPART_ATOP_PORTCULLIS_AP_REGION, BOOMER_OVERLOOK_AP_REGION, BEATSMITH_DISCO_AP_REGION],
+ {BEATSMITH_DISCO_AP_REGION: lambda state: logic.has_glide(state) and logic.obscure_routes_on(state)})
+ fancy_add_exits(world, RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, [LONE_CHEST_RAMPART_AP_REGION, BOOMER_OVERLOOK_AP_REGION, PEAK_RAMPARTS_AP_REGION],
+ {PEAK_RAMPARTS_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_horizontal_movement(state)})
+ fancy_add_exits(world, PEAK_RAMPARTS_AP_REGION, [RAMPARTS_TALL_TALL_TRAVERSE_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION],
+ {TALL_TALL_TALL_CHEST_AP_REGION: lambda state: logic.has_glide(state)})
+ #Castle Ramparts end
+ fancy_add_exits(world, THE_CHALICE_OF_TAR_AP_REGION, [SANCTUM_ENTRANCE_AP_REGION, HUNTERS_TOWER_AP_REGION, SANCTUM_EXIT_CLIFFTOP_AP_REGION, TALL_TALL_TALL_CHEST_AP_REGION, PAMOA_TREE_AP_REGION, NORTHERN_STRETCH_RACE_FINISH_AP_REGION, QUINTAR_RESERVE_AP_REGION, DIONE_ROOF_AP_REGION, EASTERN_CHASM_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
+ {TALL_TALL_TALL_CHEST_AP_REGION: lambda state: logic.has_glide(state),
+ PAMOA_TREE_AP_REGION: lambda state: logic.has_glide(state),
+ NORTHERN_STRETCH_RACE_FINISH_AP_REGION: lambda state: logic.has_vertical_movement(state) and logic.has_glide(state),
+ QUINTAR_RESERVE_AP_REGION: lambda state: logic.has_glide(state),
+ DIONE_ROOF_AP_REGION: lambda state: logic.has_glide(state),
+ EASTERN_CHASM_AP_REGION: lambda state: (state.has(DIONE_SHRINE_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state) and logic.has_vertical_movement(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state) and logic.obscure_routes_on(state)})
+ fancy_add_exits(world, FLYERS_CRAG_AP_REGION, [OKIMOTO_NS_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
+ {JIDAMBA_SUMMIT_AP_REGION: lambda state: (state.has(THE_OPEN_SEA_PASS, player) or options.regionsanity.value != options.regionsanity.option_extreme) and logic.has_glide(state)})
+ #Jidamba Tangle section start
+ fancy_add_exits(world, JIDAMBA_FOREST_FLOOR_AP_REGION, [JIDAMBA_DIAMONDSMITH_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_CANOPY_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
+ #if we change the elevators to Pipeline to always be powered, then add an exit to Pipeline Jidamba Connector
+ {JIDAMBA_DIAMONDSMITH_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_SOUTHWEST_BEACH_AP_REGION: lambda state: logic.has_glide(state),
+ EUROPA_SHRINE_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_CANOPY_AP_REGION: lambda state: logic.has_vertical_movement(state),
+ JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, JIDAMBA_DIAMONDSMITH_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, JIDAMBA_CANOPY_AP_REGION],
+ {JIDAMBA_ATOLLS_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, JIDAMBA_SOUTHWEST_BEACH_AP_REGION,[JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
+ {JIDAMBA_FOREST_FLOOR_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, EUROPA_SHRINE_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
+ {JIDAMBA_CAVE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ JIDAMBA_SOUTH_CLIFF_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, JIDAMBA_CAVE_AP_REGION, [EUROPA_SHRINE_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION],
+ {EUROPA_SHRINE_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_glide(state)})
+ fancy_add_exits(world, JIDAMBA_SOUTH_CLIFF_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_DIAMONDSMITH_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
+ {JIDAMBA_DIAMONDSMITH_AP_REGION: lambda state: logic.has_horizontal_movement(state),
+ EUROPA_SHRINE_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, [JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
+ {JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION: lambda state: logic.has_horizontal_movement(state) or logic.has_vertical_movement(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_glide(state),
+ JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, [JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
+ {JIDAMBA_SOUTH_CLIFF_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state),
+ JIDAMBA_EACLANEYA_COURTYARD_AP_REGION: lambda state: logic.has_vertical_movement(state) or logic.has_horizontal_movement(state),
+ JIDAMBA_SUMMIT_AP_REGION: lambda state: logic.has_glide(state),
+ JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ #The one-way Tangle -> Eaclaneya connector is logically connected from the Summit because the Canopy door you need to unlock & switch you need to press is up there, even though the physical door to the Eaclaneya is in the Courtyard
+ fancy_add_exits(world, JIDAMBA_SUMMIT_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_CAVE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_CANOPY_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION, TANGLE_EACLANEYA_CONNECTOR_AP_REGION],
+ {JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ #A one-way Tangle -> Eaclaneya connector; untraversable w/o region pass in Regionsanity
+ fancy_add_exits(world, TANGLE_EACLANEYA_CONNECTOR_AP_REGION, [JIDAMBA_EACLANEYA_AP_REGION],
+ {JIDAMBA_EACLANEYA_AP_REGION: lambda state: logic.has_jidamba_keys(state)})
+ fancy_add_exits(world, JIDAMBA_CANOPY_AP_REGION, [JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_DIAMONDSMITH_AP_REGION, EUROPA_SHRINE_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION, JIDAMBA_WATERWAYS_AP_REGION],
+ {JIDAMBA_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ #I am not listing has_swimming on all these exits; i already wrote a has_swimming rule on every entrance into the Waterways
+ fancy_add_exits(world, JIDAMBA_WATERWAYS_AP_REGION, [THE_OPEN_SEA_AP_REGION, JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_ATOLLS_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_AP_REGION, JIDAMBA_SOUTH_CLIFF_AP_REGION, JIDAMBA_EACLANEYA_COURTYARD_AP_REGION, JIDAMBA_SOUTHWEST_BEACH_CLIFF_AP_REGION, JIDAMBA_SUMMIT_AP_REGION])
+ #Jidamba Tangle section end
+ #Removed connection from Eaclaneya -> Tangle because you can't go through that door if you haven't hit the switches on the Tangle side
+ fancy_add_exits(world, JIDAMBA_EACLANEYA_AP_REGION, [THE_OPEN_SEA_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, THE_DEEP_SEA_AP_REGION, [THE_OPEN_SEA_AP_REGION, NEPTUNE_SHRINE_AP_REGION, THE_DEPTHS_AP_REGION, THE_SEQUOIA_AP_REGION],
+ {THE_OPEN_SEA_AP_REGION: lambda state: logic.has_swimming(state),
+ NEPTUNE_SHRINE_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_DEPTHS_AP_REGION: lambda state: logic.has_swimming(state),
+ THE_SEQUOIA_AP_REGION: lambda state: logic.has_golden_quintar(state)})
+ fancy_add_exits(world, NEPTUNE_SHRINE_AP_REGION, [THE_DEEP_SEA_AP_REGION],
+ {THE_DEEP_SEA_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, JADE_WATERWAYS_AP_REGION, [JADE_CAVERN_AP_REGION, SOILED_DENLENDE_AP_REGION, BELOW_GRAN_AP_REGION],
+ {JADE_CAVERN_AP_REGION: lambda state: logic.has_golden_quintar(state),
+ SOILED_DENLENDE_AP_REGION: lambda state: logic.has_swimming(state),
+ BELOW_GRAN_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, JADE_CAVERN_AP_REGION, [JADE_WATERWAYS_AP_REGION],
+ {JADE_WATERWAYS_AP_REGION: lambda state: logic.has_swimming(state)})
+ fancy_add_exits(world, CONTINENTAL_TRAM_AP_REGION, [PIPELINE_SOUTH_AP_REGION, SARA_SARA_BAZAAR_AP_REGION, BAZAAR_COAST_AP_REGION],
+ {SARA_SARA_BAZAAR_AP_REGION: lambda state: logic.has_swimming(state) or state.has(TRAM_KEY, player),
+ BAZAAR_COAST_AP_REGION: lambda state: logic.has_swimming(state)})
+ #Ancient Labyrinth section start
+ fancy_add_exits(world, ANCIENT_LABYRINTH_AP_REGION, [TOWER_OF_ZOT_AP_REGION, LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION],
+ #TODO: check if your starting point is Ancient Labyrinth if you can push the block out to Poko Poko Desert, with or without the Poko Poko Desert Pass
+ {LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION: lambda state: state.has(ANCIENT_TABLET_B, player) or logic.obscure_routes_on(state)})
+ fancy_add_exits(world, LABYRINTH_WEIRD_REBAR_HALLWAY_AP_REGION, [LABYRINTH_CORE_AP_REGION],
+ {LABYRINTH_CORE_AP_REGION: lambda state: state.has(ANCIENT_TABLET_C, player) or logic.obscure_routes_on(state)})
+ #only adding an exit here to make the jsonifier stop crying
+ fancy_add_exits(world, LABYRINTH_CORE_AP_REGION,[MENU_AP_REGION])
+ #Ancient Labyrinth section end
+ fancy_add_exits(world, THE_SEQUOIA_AP_REGION, [THE_DEEP_SEA_AP_REGION])
+ fancy_add_exits(world, THE_DEPTHS_AP_REGION, [THE_DEEP_SEA_AP_REGION])
+ fancy_add_exits(world, CASTLE_SEQUOIA_AP_REGION, [CAPITAL_SEQUOIA_AP_REGION])
+ # regions without connections don't get parsed by Jsonifier
+ fancy_add_exits(world, THE_NEW_WORLD_AP_REGION, [MENU_AP_REGION])
+ fancy_add_exits(world, THE_OLD_WORLD_AP_REGION, [MENU_AP_REGION])
+ fancy_add_exits(world, MODDED_ZONE_AP_REGION, [MENU_AP_REGION])
+
+def get_locations_per_ap_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]:
+ per_region: Dict[str, List[LocationData]] = {}
+
+ for location in locations:
+ per_region.setdefault(location.ap_region, []).append(location)
+
+ return per_region
+
+def create_ap_region(world: "CrystalProjectWorld", player: int, locations_per_region: Dict[str, List[LocationData]], name: str, excluded: bool) -> Tuple[Region, Location | None]:
+ """
+ Creates an AP Region and returns it, as well as the Location used for region completion
+ """
+ logic = CrystalProjectLogic(player, world.options)
+ ap_region = Region(name, player, world.multiworld)
+
+ region_completion: Location | None = None
+
+ #if the region isn't part of the multiworld, we still make the region so that all the exits still work,
+ #but we also don't fill it with locations
+ if not excluded:
+ if name in locations_per_region:
+ for location_data in locations_per_region[name]:
+ location = create_location(player, location_data, ap_region)
+ ap_region.locations.append(location)
+ if location_data.regionsanity:
+ region_completion = location
+
+ # This is for making sure players can earn money for required shop checks in shopsanity + regionsanity
+ if world.options.regionsanity.value != world.options.regionsanity.option_disabled and world.options.shopsanity.value != world.options.shopsanity.option_disabled:
+ for location in ap_region.locations:
+ if "Shop -" in location.name:
+ location.access_rule = combine_callables(location.access_rule, lambda state: logic.can_earn_money(state, ap_region.name))
+
+ return ap_region, region_completion
+
+def create_display_region(world: "CrystalProjectWorld", player: int, locations_per_region: Dict[str, List[LocationData]], display_region_name: str, excluded: bool) -> List[Region]:
+ ap_regions: List[Region] = []
+ ap_region_names = display_region_subregions_dictionary[display_region_name]
+
+ region_completion_location = None
+
+ if not excluded:
+ world.included_regions.append(display_region_name)
+
+ for ap_region_name in ap_region_names:
+ ap_region, region_completion_location_temp = create_ap_region(world, player, locations_per_region, ap_region_name, excluded)
+ ap_regions.append(ap_region)
+ if not excluded:
+ if region_completion_location_temp is not None:
+ if region_completion_location is not None:
+ raise Exception(f"Two region completion locations exist inside {display_region_name}")
+ region_completion_location = region_completion_location_temp
+
+ #need to add the rule for every other location in the ap region, as well as every location in other ap regions, as well as a can_reach rule on other ap regions in this display region
+ # This is for the region completion location
+ if not excluded:
+ if world.options.regionsanity.value != world.options.regionsanity.option_disabled and region_completion_location is not None:
+ locations_in_display_region: List[Location] = []
+ for ap_region_name in display_region_subregions_dictionary[display_region_name]:
+ for location in world.get_locations():
+ if location.parent_region.name == ap_region_name and location != region_completion_location:
+ locations_in_display_region.append(location)
+
+ region_completion_location.access_rule = lambda state: can_reach_all_locations(state, locations_in_display_region)
+
+ return ap_regions
+
+def can_reach_all_locations(state: CollectionState, locations: List[Location]) -> bool:
+ result: bool = True
+
+ for location in locations:
+ if not location.can_reach(state):
+ result = False
+ break
+
+ return result
+
+def create_location(player: int, location_data: LocationData, region: Region) -> Location:
+ location = CrystalProjectLocation(player, location_data.name, location_data.code, region)
+
+ if location_data.rule:
+ location.access_rule = location_data.rule
+
+ return location
+
+def combine_callables(callable1: Callable[[CollectionState], bool], callable2: Callable[[CollectionState], bool]) -> Callable[[CollectionState], bool]:
+ return lambda state, a=callable1, b=callable2: a(state) and b(state)
+
+def fancy_add_exits(self, region: str, exits: List[str],
+ rules: Dict[str, Callable[[CollectionState], bool]] | None = None):
+ if rules is not None:
+ for region_rule in rules:
+ if not region_rule in exits:
+ raise Exception(f"A rule was defined for the entrance {region} -> {region_rule} but {region_rule} isn't in the list of exits from {region}")
+
+ for destination_ap_region in exits:
+ destination_display_region = ap_region_to_display_region_dictionary[destination_ap_region]
+ if rules is None:
+ rules = {}
+ if destination_ap_region in rules:
+ rules[destination_ap_region] = combine_callables(rules[destination_ap_region], rules_on_display_regions[destination_display_region])
+ else:
+ rules[destination_ap_region] = rules_on_display_regions[destination_display_region]
+
+ # all regions except Menu have an exit to menu
+ if region != MENU_AP_REGION:
+ exits.append(MENU_AP_REGION)
+
+ self.multiworld.get_region(region, self.player).add_exits(exits, rules)
+
+def connect_menu_region(world: "CrystalProjectWorld", options: CrystalProjectOptions) -> None:
+ logic = CrystalProjectLogic(world.player, options)
+
+ fancy_add_exits(world, MENU_AP_REGION, [SPAWNING_MEADOWS_AP_REGION, CAPITAL_SEQUOIA_AP_REGION, MERCURY_SHRINE_AP_REGION, POSEIDON_SHRINE_ROOF_AP_REGION, POKO_POKO_DESERT_AP_REGION, GANYMEDE_SHRINE_AP_REGION, DIONE_ROOF_AP_REGION, UPPER_ICE_LAKES_AP_REGION, OWL_TREE_AP_REGION, EUROPA_SHRINE_AP_REGION, NEPTUNE_SHRINE_AP_REGION, THE_OLD_WORLD_AP_REGION, THE_NEW_WORLD_AP_REGION, MODDED_ZONE_AP_REGION],
+ # If regionsanity is enabled it will set its own origin region, if it isn't we should connect menu to spawning meadows
+ {SPAWNING_MEADOWS_AP_REGION: lambda state: options.regionsanity.value == options.regionsanity.option_disabled,
+ CAPITAL_SEQUOIA_AP_REGION: lambda state: state.has(GAEA_STONE, world.player),
+ MERCURY_SHRINE_AP_REGION: lambda state: state.has(MERCURY_STONE, world.player),
+ POSEIDON_SHRINE_ROOF_AP_REGION: lambda state: state.has(POSEIDON_STONE, world.player),
+ POKO_POKO_DESERT_AP_REGION: lambda state: state.has(MARS_STONE, world.player),
+ GANYMEDE_SHRINE_AP_REGION: lambda state: state.has(GANYMEDE_STONE, world.player),
+ DIONE_ROOF_AP_REGION: lambda state: state.has(DIONE_STONE, world.player),
+ UPPER_ICE_LAKES_AP_REGION: lambda state: state.has(TRITON_STONE, world.player),
+ OWL_TREE_AP_REGION: lambda state: state.has(CALLISTO_STONE, world.player),
+ EUROPA_SHRINE_AP_REGION: lambda state: state.has(EUROPA_STONE, world.player),
+ NEPTUNE_SHRINE_AP_REGION: lambda state: state.has(NEPTUNE_STONE, world.player),
+ THE_OLD_WORLD_AP_REGION: lambda state: logic.old_world_requirements(state),
+ THE_NEW_WORLD_AP_REGION: lambda state: logic.new_world_requirements(state)})
world.multiworld.register_indirect_condition(world.get_region(THE_DEPTHS_AP_REGION), world.get_entrance(MENU_AP_REGION + " -> " + THE_OLD_WORLD_AP_REGION))
\ No newline at end of file
diff --git a/worlds/crystal_project/rules.py b/worlds/crystal_project/rules.py
index 8a4a83869a99..ce9da2ea6fda 100644
--- a/worlds/crystal_project/rules.py
+++ b/worlds/crystal_project/rules.py
@@ -1,187 +1,190 @@
-from .options import CrystalProjectOptions
-from .constants.keys import *
-from .constants.key_items import *
-from .constants.mounts import *
-from .constants.teleport_stones import *
-from .constants.ap_regions import *
-from .constants.display_regions import *
-from .constants.jobs import SCHOLAR_JOB
-from .constants.region_passes import *
-from .constants.scholar_abilities import REVERSE_POLARITY
-from .constants.item_groups import *
-from .constants.level_requirements import *
-from .items import singleton_keys
-from BaseClasses import CollectionState
-
-class CrystalProjectLogic:
- player: int
- options: CrystalProjectOptions
-
- def __init__(self, player: int, options: CrystalProjectOptions):
- self.player = player
- self.options = options
-
- def has_jobs(self, state: CollectionState, job_minimum: int) -> bool:
- return self.get_job_count(state) >= job_minimum
-
- def get_job_count(self, state: CollectionState) -> int:
- count = 0
- for job in state.multiworld.worlds[self.player].item_name_groups[JOB]:
- if state.has(job, self.player):
- count += 1
-
- #subtract starting jobs
- return count - self.get_starting_job_count()
-
- def get_starting_job_count(self):
- if self.options.job_rando.value == self.options.job_rando.option_full:
- return self.options.starting_job_quantity.value
- else:
- return 6
-
- def has_enough_clamshells(self, state: CollectionState):
- clamshell_quantity = 2
- if self.options.goal.value == self.options.goal.option_clamshells:
- clamshell_quantity = self.options.clamshell_goal_quantity.value
- return state.has(CLAMSHELL, self.player, clamshell_quantity)
-
- def has_rental_quintar(self, state: CollectionState, rental_display_region_name: str) -> bool:
- #Using the display region for this check bc it's least likely to change and equivalent in function in this case
- has_rental_quintar: bool = False
-
- #If you have Owl Drum or Quintar Flute you're just good to go
- if self.has_horizontal_movement(state):
- has_rental_quintar = True
- #If not we check for Quintar Pass and access to the rental location
- else:
- has_rental_quintar = state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player) or state.has(PROGRESSIVE_MOUNT, self.player)
- if self.options.regionsanity.value != self.options.regionsanity.option_disabled:
- if rental_display_region_name == ROLLING_QUINTAR_FIELDS_DISPLAY_NAME and not state.has(ROLLING_QUINTAR_FIELDS_PASS, self.player):
- has_rental_quintar = False
- if rental_display_region_name == SARA_SARA_BAZAAR_DISPLAY_NAME and not state.has(SARA_SARA_BAZAAR_PASS, self.player):
- has_rental_quintar = False
-
- return has_rental_quintar
-
- def has_horizontal_movement(self, state: CollectionState) -> bool:
- return state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 2) or state.has(OWL_DRUM, self.player) or state.has(PROGRESSIVE_MOUNT, self.player, 2)
-
- def has_fast(self, state: CollectionState) -> bool:
- return state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 2) or state.has(PROGRESSIVE_MOUNT, self.player, 2)
-
- def has_vertical_movement(self, state: CollectionState) -> bool:
- return state.has(IBEK_BELL, self.player) or state.has(PROGRESSIVE_MOUNT, self.player, 3)
-
- def can_push_ice_block_and_goat(self, state: CollectionState, region_pass: str) -> bool:
- return self.has_vertical_movement(state) and (state.has(region_pass, self.player, 1) or self.options.regionsanity.value == self.options.regionsanity.option_disabled)
-
- def has_glide(self, state: CollectionState) -> bool:
- return state.has(OWL_DRUM, self.player) or state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 3) or state.has(PROGRESSIVE_MOUNT, self.player, 4)
-
- def has_rental_salmon(self, state: CollectionState) -> bool:
- return self.has_swimming(state) or state.has(SALMON_RIVER_PASS, self.player) or self.options.regionsanity.value == self.options.regionsanity.option_disabled
-
- def has_swimming(self, state: CollectionState) -> bool:
- return state.has(PROGRESSIVE_SALMON_VIOLA, self.player) or state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 3) or state.has(PROGRESSIVE_MOUNT, self.player, 5)
-
- def has_golden_quintar(self, state: CollectionState) -> bool:
- return state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 3) or state.has(PROGRESSIVE_MOUNT, self.player, 7)
-
- def obscure_routes_on(self, state: CollectionState) -> bool:
- return self.options.obscure_routes.value == self.options.obscure_routes.option_true
-
- def new_world_requirements(self, state: CollectionState) -> bool:
- if self.options.goal.value == self.options.goal.option_astley or self.options.goal.value == self.options.goal.option_true_astley:
- return self.has_jobs(state, self.options.new_world_stone_job_quantity.value)
- else:
- return state.has(NEW_WORLD_STONE, self.player)
-
- def old_world_requirements(self, state: CollectionState) -> bool:
- if self.options.goal.value == self.options.goal.option_true_astley:
- return self.has_swimming(state) and state.has(DEITY_EYE, self.player, 4) and state.has(STEM_WARD, self.player) and state.can_reach(THE_DEPTHS_AP_REGION, player=self.player)
- else:
- return state.has(OLD_WORLD_STONE, self.player)
-
- def is_area_in_level_range(self, state: CollectionState, min_level: int) -> bool:
- min_level = min_level + self.options.level_compared_to_enemies.value
-
- if min_level > self.options.max_level.value:
- min_level = self.options.max_level.value
-
- # Players start with 1 Progressive Level
- count = ((min_level - 1) // self.options.progressive_level_size.value) + 1
-
- if not self.options.level_gating.value == self.options.level_gating.option_none:
- return state.has(PROGRESSIVE_LEVEL, self.player, count)
-
- return True
-
- #has_key is for: Luxury Key, Gardeners Key, All Wing Keys, Cell Keys, Room One Key, Small Keys, Boss Key, Red Door Keys,
- #Ice Puzzle Keys, Rampart Key, Forgotten Key, Tram Key, Courtyard Key, Pyramid Key
- def has_key(self, state: CollectionState, key_name: str, count: int = 1) -> bool:
- if state.has(SKELETON_KEY, self.player):
- return True
- if (self.options.key_mode.value == self.options.key_mode.option_key_ring or
- self.options.key_mode.value == self.options.key_mode.option_key_ring_skelefree):
- return self.has_key_ring(state, key_name)
- return state.has(key_name, self.player, count)
-
- def has_key_ring(self, state: CollectionState, key_name: str) -> bool:
- if key_name in singleton_keys:
- return state.has(key_name, self.player)
-
- if (key_name == CELL_KEY
- or key_name == SOUTH_WING_KEY
- or key_name == EAST_WING_KEY
- or key_name == WEST_WING_KEY
- or key_name == DARK_WING_KEY):
- return state.has(PRISON_KEY_RING, self.player)
-
- if (key_name == BEAURIOR_BOSS_KEY
- or key_name == SMALL_KEY):
- return state.has(BEAURIOR_KEY_RING, self.player)
-
- if key_name == RED_DOOR_KEY:
- return state.has(SLIP_GLIDE_RIDE_KEY_RING, self.player)
-
- if key_name == ICE_PUZZLE_KEY:
- return state.has(ICE_PUZZLE_KEY_RING, self.player)
-
- if (key_name == FOLIAGE_KEY
- or key_name == CANOPY_KEY
- or key_name == CAVE_KEY):
- return state.has(JIDAMBA_KEY_RING, self.player)
-
- return False
-
- def has_jidamba_keys(self, state: CollectionState) -> bool:
- if state.has(SKELETON_KEY, self.player) or (self.has_key(state, FOLIAGE_KEY, 1) and self.has_key(state, CAVE_KEY, 1) and self.has_key(state, CANOPY_KEY,1)):
- return True
- else:
- return False
-
- def can_earn_money(self, state: CollectionState, shop_ap_region: str) -> bool:
- if shop_ap_region == MERCURY_SHRINE_AP_REGION:
- return True
-
- from .regions import display_region_levels_dictionary
- from .regions import ap_region_to_display_region_dictionary
-
- has_combat = False
- region_checked = 0
- #This is getting the order of the Display Region that the shop is in; higher order means more difficult zones
- shop_region_index = list(display_region_levels_dictionary.keys()).index(ap_region_to_display_region_dictionary[shop_ap_region])
-
- for ap_region in state.multiworld.worlds[self.player].get_regions():
- region_checked += 1
- #checking if the player has access to money-earning zones that are higher than 6 regions below the shop's region, to make sure they're not expected to grind Spawning Meadows enemies to buy something in Neptune Shrine
- if region_checked > (shop_region_index - 6) and ap_region.can_reach(state) and ap_region.name != MENU_AP_REGION and ap_region.name != MODDED_ZONE_AP_REGION:
- enemy_level = display_region_levels_dictionary[ap_region_to_display_region_dictionary[ap_region.name]][0]
- if enemy_level > 0 or (ap_region.name == CAPITAL_SEQUOIA_AP_REGION and self.is_area_in_level_range(state, CAPITAL_SEQUOIA_ENEMY_LEVEL)) or (ap_region.name == QUINTAR_RESERVE_AP_REGION and self.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)):
- has_combat = True
- break
-
- return has_combat
-
- def can_fight_gran(self, state: CollectionState) -> bool:
+from .options import CrystalProjectOptions
+from .constants.keys import *
+from .constants.key_items import *
+from .constants.mounts import *
+from .constants.teleport_stones import *
+from .constants.ap_regions import *
+from .constants.display_regions import *
+from .constants.jobs import SCHOLAR_JOB
+from .constants.region_passes import *
+from .constants.scholar_abilities import REVERSE_POLARITY
+from .constants.item_groups import *
+from .constants.level_requirements import *
+from .items import singleton_keys
+from BaseClasses import CollectionState
+
+class CrystalProjectLogic:
+ player: int
+ options: CrystalProjectOptions
+
+ def __init__(self, player: int, options: CrystalProjectOptions):
+ self.player = player
+ self.options = options
+
+ def has_jobs(self, state: CollectionState, job_minimum: int) -> bool:
+ return self.get_job_count(state) >= job_minimum
+
+ def get_job_count(self, state: CollectionState) -> int:
+ count = 0
+ for job in state.multiworld.worlds[self.player].item_name_groups[JOB]:
+ if state.has(job, self.player):
+ count += 1
+
+ #subtract starting jobs
+ return count - self.get_starting_job_count()
+
+ def get_starting_job_count(self):
+ if self.options.job_rando.value == self.options.job_rando.option_full:
+ return self.options.starting_job_quantity.value
+ else:
+ return 6
+
+ def has_enough_clamshells(self, state: CollectionState):
+ clamshell_quantity = 2
+ if self.options.goal.value == self.options.goal.option_clamshells:
+ clamshell_quantity = self.options.clamshell_goal_quantity.value
+ return state.has(CLAMSHELL, self.player, clamshell_quantity)
+
+ def has_rental_quintar(self, state: CollectionState, rental_display_region_name: str) -> bool:
+ #Using the display region for this check bc it's least likely to change and equivalent in function in this case
+ has_rental_quintar: bool = False
+
+ #If you have Owl Drum or Quintar Flute you're just good to go
+ if self.has_horizontal_movement(state):
+ has_rental_quintar = True
+ #If not we check for Quintar Pass and access to the rental location
+ else:
+ has_rental_quintar = state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player) or state.has(PROGRESSIVE_MOUNT, self.player)
+ if self.options.regionsanity.value != self.options.regionsanity.option_disabled:
+ if rental_display_region_name == ROLLING_QUINTAR_FIELDS_DISPLAY_NAME and not state.has(ROLLING_QUINTAR_FIELDS_PASS, self.player):
+ has_rental_quintar = False
+ if rental_display_region_name == SARA_SARA_BAZAAR_DISPLAY_NAME and not state.has(SARA_SARA_BAZAAR_PASS, self.player):
+ has_rental_quintar = False
+
+ return has_rental_quintar
+
+ def has_horizontal_movement(self, state: CollectionState) -> bool:
+ return state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 2) or state.has(OWL_DRUM, self.player) or state.has(PROGRESSIVE_MOUNT, self.player, 2)
+
+ def has_fast(self, state: CollectionState) -> bool:
+ return state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 2) or state.has(PROGRESSIVE_MOUNT, self.player, 2)
+
+ def has_vertical_movement(self, state: CollectionState) -> bool:
+ return state.has(IBEK_BELL, self.player) or state.has(PROGRESSIVE_MOUNT, self.player, 3)
+
+ def can_push_ice_block_and_goat(self, state: CollectionState, region_pass: str) -> bool:
+ return self.has_vertical_movement(state) and (state.has(region_pass, self.player, 1) or self.options.regionsanity.value == self.options.regionsanity.option_disabled)
+
+ def has_glide(self, state: CollectionState) -> bool:
+ return state.has(OWL_DRUM, self.player) or state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 3) or state.has(PROGRESSIVE_MOUNT, self.player, 4)
+
+ def has_rental_salmon(self, state: CollectionState) -> bool:
+ return self.has_swimming(state) or state.has(SALMON_RIVER_PASS, self.player) or self.options.regionsanity.value == self.options.regionsanity.option_disabled
+
+ def has_swimming(self, state: CollectionState) -> bool:
+ return state.has(PROGRESSIVE_SALMON_VIOLA, self.player) or state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 3) or state.has(PROGRESSIVE_MOUNT, self.player, 5)
+
+ def has_golden_quintar(self, state: CollectionState) -> bool:
+ return state.has(PROGRESSIVE_QUINTAR_WOODWIND, self.player, 3) or state.has(PROGRESSIVE_MOUNT, self.player, 7)
+
+ def obscure_routes_on(self, state: CollectionState) -> bool:
+ return self.options.obscure_routes.value == self.options.obscure_routes.option_true
+
+ def new_world_requirements(self, state: CollectionState) -> bool:
+ if self.options.goal.value == self.options.goal.option_astley or self.options.goal.value == self.options.goal.option_true_astley:
+ return self.has_jobs(state, self.options.new_world_stone_job_quantity.value)
+ else:
+ return state.has(NEW_WORLD_STONE, self.player)
+
+ def old_world_requirements(self, state: CollectionState) -> bool:
+ if self.options.goal.value == self.options.goal.option_true_astley:
+ return self.has_swimming(state) and state.has(DEITY_EYE, self.player, 4) and state.has(STEM_WARD, self.player) and state.can_reach(THE_DEPTHS_AP_REGION, player=self.player)
+ else:
+ return state.has(OLD_WORLD_STONE, self.player)
+
+ def is_area_in_level_range(self, state: CollectionState, min_level: int) -> bool:
+ min_level = min_level + self.options.level_compared_to_enemies.value
+
+ if min_level > self.options.max_level.value:
+ min_level = self.options.max_level.value
+
+ # Players start with 1 Progressive Level
+ count = ((min_level - 1) // self.options.progressive_level_size.value) + 1
+
+ if not self.options.level_gating.value == self.options.level_gating.option_none:
+ return state.has(PROGRESSIVE_LEVEL, self.player, count)
+
+ return True
+
+ #has_key is for: Luxury Key, Gardeners Key, All Wing Keys, Cell Keys, Room One Key, Small Keys, Boss Key, Red Door Keys,
+ #Ice Puzzle Keys, Rampart Key, Forgotten Key, Tram Key, Courtyard Key, Pyramid Key
+ def has_key(self, state: CollectionState, key_name: str, count: int = 1) -> bool:
+ if state.has(SKELETON_KEY, self.player):
+ return True
+ if (self.options.key_mode.value == self.options.key_mode.option_key_ring or
+ self.options.key_mode.value == self.options.key_mode.option_key_ring_skelefree):
+ return self.has_key_ring(state, key_name)
+ return state.has(key_name, self.player, count)
+
+ def has_key_ring(self, state: CollectionState, key_name: str) -> bool:
+ if key_name in singleton_keys:
+ return state.has(key_name, self.player)
+
+ if (key_name == CELL_KEY
+ or key_name == SOUTH_WING_KEY
+ or key_name == EAST_WING_KEY
+ or key_name == WEST_WING_KEY
+ or key_name == DARK_WING_KEY):
+ return state.has(PRISON_KEY_RING, self.player)
+
+ if (key_name == BEAURIOR_BOSS_KEY
+ or key_name == SMALL_KEY):
+ return state.has(BEAURIOR_KEY_RING, self.player)
+
+ if key_name == RED_DOOR_KEY:
+ return state.has(SLIP_GLIDE_RIDE_KEY_RING, self.player)
+
+ if key_name == ICE_PUZZLE_KEY:
+ return state.has(ICE_PUZZLE_KEY_RING, self.player)
+
+ if (key_name == FOLIAGE_KEY
+ or key_name == CANOPY_KEY
+ or key_name == CAVE_KEY):
+ return state.has(JIDAMBA_KEY_RING, self.player)
+
+ return False
+
+ def has_jidamba_keys(self, state: CollectionState) -> bool:
+ if state.has(SKELETON_KEY, self.player) or (self.has_key(state, FOLIAGE_KEY, 1) and self.has_key(state, CAVE_KEY, 1) and self.has_key(state, CANOPY_KEY,1)):
+ return True
+ else:
+ return False
+
+ def can_earn_money(self, state: CollectionState, shop_ap_region: str) -> bool:
+ if shop_ap_region == MERCURY_SHRINE_AP_REGION:
+ return True
+
+ from .regions import display_region_levels_dictionary
+ from .regions import ap_region_to_display_region_dictionary
+
+ has_combat = False
+
+ #This is getting the order of the Display Region that the shop is in; higher order means more difficult zones
+ # (if the shop has no combat in its region, then we can't exactly do a compare on enemy levels directly)
+ shop_region_index = list(display_region_levels_dictionary.keys()).index(ap_region_to_display_region_dictionary[shop_ap_region])
+
+ for ap_region in state.multiworld.worlds[self.player].get_regions():
+ current_display_region_index = list(display_region_levels_dictionary.keys()).index(ap_region_to_display_region_dictionary[ap_region.name])
+
+ #checking if the player has access to money-earning zones that are higher than 6 regions below the shop's region, to make sure they're not expected to grind Spawning Meadows enemies
+ #to buy something in Neptune Shrine
+ if current_display_region_index > (shop_region_index - 6) and ap_region.can_reach(state) and ap_region.name != MENU_AP_REGION and ap_region.name != MODDED_ZONE_AP_REGION:
+ enemy_level = display_region_levels_dictionary[ap_region_to_display_region_dictionary[ap_region.name]][0]
+ if enemy_level > 0 or (ap_region.name == CAPITAL_SEQUOIA_AP_REGION and self.is_area_in_level_range(state, CAPITAL_SEQUOIA_ENEMY_LEVEL)) or (ap_region.name == QUINTAR_RESERVE_AP_REGION and self.is_area_in_level_range(state, QUINTAR_RESERVE_ENEMY_LEVEL)):
+ has_combat = True
+ break
+
+ return has_combat
+
+ def can_fight_gran(self, state: CollectionState) -> bool:
return (state.has(SCHOLAR_JOB, self.player) and state.has(REVERSE_POLARITY, self.player)) or self.is_area_in_level_range(state, GRAN_FIGHT_LEVEL)
\ No newline at end of file
diff --git a/worlds/crystal_project/test/bases.py b/worlds/crystal_project/test/bases.py
index bb6aaeb8d021..8e895a63dd4e 100644
--- a/worlds/crystal_project/test/bases.py
+++ b/worlds/crystal_project/test/bases.py
@@ -1,61 +1,61 @@
-from test.bases import WorldTestBase
-from ..items import display_region_name_to_pass_dict
-from ..constants.mounts import *
-from ..constants.key_items import *
-from ..constants.item_groups import *
-
-class CrystalProjectTestBase(WorldTestBase):
- game = "Crystal Project"
- run_default_tests = False
-
- def assert_region_entrances(self, region: str, reachable_regions: tuple[str, ...] | None=None, unreachable_regions: tuple[str, ...] | None=None):
- self.world.origin_region_name = region
-
- if isinstance(reachable_regions, tuple):
- for reachable in reachable_regions:
- with self.subTest(msg="Test Can Reach Entrance", region=region, reachable_region=reachable):
- self.assertTrue(self.can_reach_entrance(region + " -> " + reachable))
-
- if isinstance(unreachable_regions, tuple):
- for unreachable in unreachable_regions:
- with self.subTest(msg="Test Cannot Reach Entrance", region=region, unreachable_region=unreachable):
- self.assertFalse(self.can_reach_entrance(region + " -> " + unreachable))
-
-
- def assert_locations(self, reachable_locations: list[str] | None=None, unreachable_locations: list[str] | None=None):
- if isinstance(reachable_locations, list):
- for reachable in reachable_locations:
- with self.subTest(msg="Test Can Reach Location", reachable_location=reachable):
- self.assertTrue(self.can_reach_location(reachable))
-
- if isinstance(unreachable_locations, list):
- for unreachable in unreachable_locations:
- with self.subTest(msg="Test Cannot Reach Location", unreachable_location=unreachable):
- self.assertFalse(self.can_reach_location(unreachable))
-
- def set_collected_job_count(self, job_count: int):
- """Guarantees that you have collected X non-starting jobs.
- If you run this twice in a single test, i.e. collect_jobs(3) and then collect_jobs(4) you should assume you have 4 jobs collected, not 7"""
- self.collect(self.get_items_by_name(self.world.item_name_groups[JOB])[:job_count])
-
-
- def collect_mounts(self):
- self.collect_by_name([PROGRESSIVE_SALMON_VIOLA, PROGRESSIVE_QUINTAR_WOODWIND, IBEK_BELL, OWL_DRUM, PROGRESSIVE_MOUNT])
-
-
- def collect_all_progressive_levels(self):
- self.collect_by_name(PROGRESSIVE_LEVEL)
-
- def collect_progressive_levels(self, count):
- for _ in range(count):
- self.collect(self.get_item_by_name(PROGRESSIVE_LEVEL))
-
- def collect_passes(self):
- for region in display_region_name_to_pass_dict:
- self.collect_by_name(display_region_name_to_pass_dict[region])
-
-
- def collect_mounts_and_progressive_levels_and_passes(self):
- self.collect_mounts()
- self.collect_all_progressive_levels()
+from test.bases import WorldTestBase
+from ..items import display_region_name_to_pass_dict
+from ..constants.mounts import *
+from ..constants.key_items import *
+from ..constants.item_groups import *
+
+class CrystalProjectTestBase(WorldTestBase):
+ game = "Crystal Project"
+ run_default_tests = False
+
+ def assert_region_entrances(self, region: str, reachable_regions: tuple[str, ...] | None=None, unreachable_regions: tuple[str, ...] | None=None):
+ self.world.origin_region_name = region
+
+ if isinstance(reachable_regions, tuple):
+ for reachable in reachable_regions:
+ with self.subTest(msg="Test Can Reach Entrance", region=region, reachable_region=reachable):
+ self.assertTrue(self.can_reach_entrance(region + " -> " + reachable))
+
+ if isinstance(unreachable_regions, tuple):
+ for unreachable in unreachable_regions:
+ with self.subTest(msg="Test Cannot Reach Entrance", region=region, unreachable_region=unreachable):
+ self.assertFalse(self.can_reach_entrance(region + " -> " + unreachable))
+
+
+ def assert_locations(self, reachable_locations: list[str] | None=None, unreachable_locations: list[str] | None=None):
+ if isinstance(reachable_locations, list):
+ for reachable in reachable_locations:
+ with self.subTest(msg="Test Can Reach Location", reachable_location=reachable):
+ self.assertTrue(self.can_reach_location(reachable))
+
+ if isinstance(unreachable_locations, list):
+ for unreachable in unreachable_locations:
+ with self.subTest(msg="Test Cannot Reach Location", unreachable_location=unreachable):
+ self.assertFalse(self.can_reach_location(unreachable))
+
+ def set_collected_job_count(self, job_count: int):
+ """Guarantees that you have collected X non-starting jobs.
+ If you run this twice in a single test, i.e. collect_jobs(3) and then collect_jobs(4) you should assume you have 4 jobs collected, not 7"""
+ self.collect(self.get_items_by_name(self.world.item_name_groups[JOB])[:job_count])
+
+
+ def collect_mounts(self):
+ self.collect_by_name([PROGRESSIVE_SALMON_VIOLA, PROGRESSIVE_QUINTAR_WOODWIND, IBEK_BELL, OWL_DRUM, PROGRESSIVE_MOUNT])
+
+
+ def collect_all_progressive_levels(self):
+ self.collect_by_name(PROGRESSIVE_LEVEL)
+
+ def collect_progressive_levels(self, count):
+ for _ in range(count):
+ self.collect(self.get_item_by_name(PROGRESSIVE_LEVEL))
+
+ def collect_passes(self):
+ for region in display_region_name_to_pass_dict:
+ self.collect_by_name(display_region_name_to_pass_dict[region])
+
+
+ def collect_mounts_and_progressive_levels_and_passes(self):
+ self.collect_mounts()
+ self.collect_all_progressive_levels()
self.collect_passes()
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_capital_sequoia.py b/worlds/crystal_project/test/test_capital_sequoia.py
index 26c873739fe0..a0e4e078d379 100644
--- a/worlds/crystal_project/test/test_capital_sequoia.py
+++ b/worlds/crystal_project/test/test_capital_sequoia.py
@@ -1,90 +1,90 @@
-from ..constants.keys import *
-from ..constants.ap_regions import *
-from ..constants.mounts import *
-from ..constants.region_passes import *
-
-from .bases import CrystalProjectTestBase
-
-class TestCapitalSequoiaNoLevelGating(CrystalProjectTestBase):
- options = {
- "level_gating": 0,
- "progressive_mount_mode": 0,
- }
- # Current Capital Sequoia exits: MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION,
- # ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION
- def test_region_connections_no_items(self):
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION),
- unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
- RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_courtyard_key(self):
- self.collect_by_name(COURTYARD_KEY)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION,
- ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION,),
- unreachable_regions=(CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_quintar_flute(self):
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION,
- ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION),
- unreachable_regions=(CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_five_jobs(self):
- self.set_collected_job_count(4)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, JOJO_SEWERS_AP_REGION, SKUMPARADISE_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION),
- unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
- RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
- self.set_collected_job_count(5)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION,
- GREENSHIRE_REPRISE_AP_REGION),
- unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
- CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_ibek_owl(self):
- self.collect_by_name(IBEK_BELL)
- self.collect_by_name(OWL_DRUM)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION,
- BOOMER_SOCIETY_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
- RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION),
- unreachable_regions=(CAPITAL_MOAT_AP_REGION,))
-
-
-
-class TestCapitalSequoiaWithRegionsanity(CrystalProjectTestBase):
- options = {
- "level_gating": 0,
- "progressive_mount_mode": 0,
- "regionsanity": 1,
- "start_inventory_from_pool": {SPAWNING_MEADOWS_PASS: 1}
- }
-
- def test_region_connections_no_items(self):
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(),
- unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION,
- ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
- CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_with_just_passes(self):
- self.collect_passes()
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION),
- unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
- RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_five_jobs_and_passes(self):
- self.set_collected_job_count(5)
- self.collect_passes()
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, GREENSHIRE_REPRISE_AP_REGION),
- unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
- CASTLE_SEQUOIA_AP_REGION))
-
- def test_region_connections_ibek_owl_and_passes(self):
- self.collect_by_name(IBEK_BELL)
- self.collect_by_name(OWL_DRUM)
- self.collect_passes()
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION,
- ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
- CASTLE_SEQUOIA_AP_REGION),
+from ..constants.keys import *
+from ..constants.ap_regions import *
+from ..constants.mounts import *
+from ..constants.region_passes import *
+
+from .bases import CrystalProjectTestBase
+
+class TestCapitalSequoiaNoLevelGating(CrystalProjectTestBase):
+ options = {
+ "level_gating": 0,
+ "progressive_mount_mode": 0,
+ }
+ # Current Capital Sequoia exits: MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION,
+ # ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION
+ def test_region_connections_no_items(self):
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION),
+ unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
+ RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_courtyard_key(self):
+ self.collect_by_name(COURTYARD_KEY)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION,
+ ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION,),
+ unreachable_regions=(CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_quintar_flute(self):
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION,
+ ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION),
+ unreachable_regions=(CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_five_jobs(self):
+ self.set_collected_job_count(4)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, JOJO_SEWERS_AP_REGION, SKUMPARADISE_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION),
+ unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
+ RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
+ self.set_collected_job_count(5)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION,
+ GREENSHIRE_REPRISE_AP_REGION),
+ unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
+ CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_ibek_owl(self):
+ self.collect_by_name(IBEK_BELL)
+ self.collect_by_name(OWL_DRUM)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION,
+ BOOMER_SOCIETY_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
+ RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION),
+ unreachable_regions=(CAPITAL_MOAT_AP_REGION,))
+
+
+
+class TestCapitalSequoiaWithRegionsanity(CrystalProjectTestBase):
+ options = {
+ "level_gating": 0,
+ "progressive_mount_mode": 0,
+ "regionsanity": 1,
+ "start_inventory_from_pool": {SPAWNING_MEADOWS_PASS: 1}
+ }
+
+ def test_region_connections_no_items(self):
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(),
+ unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION,
+ ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
+ CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_with_just_passes(self):
+ self.collect_passes()
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION, reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION),
+ unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,
+ RAMPART_ATOP_PORTCULLIS_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_five_jobs_and_passes(self):
+ self.set_collected_job_count(5)
+ self.collect_passes()
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, GREENSHIRE_REPRISE_AP_REGION),
+ unreachable_regions=(MOAT_SHALLOWS_AP_REGION, CAPITAL_MOAT_AP_REGION, BOOMER_SOCIETY_AP_REGION, COBBLESTONE_CRAG_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
+ CASTLE_SEQUOIA_AP_REGION))
+
+ def test_region_connections_ibek_owl_and_passes(self):
+ self.collect_by_name(IBEK_BELL)
+ self.collect_by_name(OWL_DRUM)
+ self.collect_passes()
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(MOAT_SHALLOWS_AP_REGION, PROVING_MEADOWS_AP_REGION, SKUMPARADISE_AP_REGION, JOJO_SEWERS_AP_REGION, BOOMER_SOCIETY_AP_REGION,
+ ROLLING_QUINTAR_FIELDS_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, RAMPART_ATOP_PORTCULLIS_AP_REGION,
+ CASTLE_SEQUOIA_AP_REGION),
unreachable_regions=(CAPITAL_MOAT_AP_REGION,))
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_default.py b/worlds/crystal_project/test/test_default.py
index 83a6a7889033..1a220197d3f9 100644
--- a/worlds/crystal_project/test/test_default.py
+++ b/worlds/crystal_project/test/test_default.py
@@ -1,17 +1,17 @@
-from .bases import CrystalProjectTestBase
-from ..constants.ap_regions import *
-
-
-class TestDefault(CrystalProjectTestBase):
- run_default_tests = True
-
- def test_region_accessibility(self):
- self.assertBeatable(False)
- self.collect_all_but("")
- self.assertBeatable(True)
-
- def test_base_functions(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assert_region_entrances(JIDAMBA_WATERWAYS_AP_REGION, (THE_OPEN_SEA_AP_REGION,))
- self.assert_region_entrances(TANGLE_EACLANEYA_CONNECTOR_AP_REGION, (), (JIDAMBA_EACLANEYA_AP_REGION,))
+from .bases import CrystalProjectTestBase
+from ..constants.ap_regions import *
+
+
+class TestDefault(CrystalProjectTestBase):
+ run_default_tests = True
+
+ def test_region_accessibility(self):
+ self.assertBeatable(False)
+ self.collect_all_but("")
+ self.assertBeatable(True)
+
+ def test_base_functions(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assert_region_entrances(JIDAMBA_WATERWAYS_AP_REGION, (THE_OPEN_SEA_AP_REGION,))
+ self.assert_region_entrances(TANGLE_EACLANEYA_CONNECTOR_AP_REGION, (), (JIDAMBA_EACLANEYA_AP_REGION,))
self.assert_locations(["Spawning Meadows Chest - Jump on Nan"], ["Spawning Meadows NPC - Butterfly Goo"])
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_goals.py b/worlds/crystal_project/test/test_goals.py
index 34d41e3bd793..19c4282ef667 100644
--- a/worlds/crystal_project/test/test_goals.py
+++ b/worlds/crystal_project/test/test_goals.py
@@ -1,19 +1,19 @@
-from .bases import CrystalProjectTestBase
-from ..options import *
-from ..constants.key_items import *
-from ..constants.keys import *
-from ..constants.mounts import *
-from ..constants.ap_regions import *
-from BaseClasses import CollectionState
-
-
-class TestTrueAstleyBeginner(CrystalProjectTestBase):
- options = {
- "goal": Goal.option_true_astley,
- "included_regions": IncludedRegions.option_beginner,
-
- }
-
- def test_completability(self):
- self.collect_all_but("")
+from .bases import CrystalProjectTestBase
+from ..options import *
+from ..constants.key_items import *
+from ..constants.keys import *
+from ..constants.mounts import *
+from ..constants.ap_regions import *
+from BaseClasses import CollectionState
+
+
+class TestTrueAstleyBeginner(CrystalProjectTestBase):
+ options = {
+ "goal": Goal.option_true_astley,
+ "included_regions": IncludedRegions.option_beginner,
+
+ }
+
+ def test_completability(self):
+ self.collect_all_but("")
self.assertBeatable(True)
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_highest_item_to_location_ratio.py b/worlds/crystal_project/test/test_highest_item_to_location_ratio.py
index 63f7c52c8659..936672e6e04c 100644
--- a/worlds/crystal_project/test/test_highest_item_to_location_ratio.py
+++ b/worlds/crystal_project/test/test_highest_item_to_location_ratio.py
@@ -1,23 +1,23 @@
-from .bases import CrystalProjectTestBase
-
-class TestStuffingTheMostItemsIntoBeginnerRegions(CrystalProjectTestBase):
- options = {
- "goal": 2, #clamshells
- "clamshell_goal_quantity": 99,
- "extra_clamshells_in_pool": 99,
- "included_regions": 0, #beginner
- "job_rando": 2, #full
- "starting_job_quantity": 1,
- "kill_bosses_mode": 0,
- "shopsanity": 0,
- "regionsanity": 1,
- "progressive_level_size": 3,
- "max_level": 99,
- "key_mode": 2, #vanilla
- "start_with_treasure_finder": 0,
- "start_with_maps": 1,
- "include_summon_abilities": 1,
- "include_scholar_abilities": 1
- }
- #Todo: turn off start with maps in the future once we get enough item massaging for that to not break unit tests
+from .bases import CrystalProjectTestBase
+
+class TestStuffingTheMostItemsIntoBeginnerRegions(CrystalProjectTestBase):
+ options = {
+ "goal": 2, #clamshells
+ "clamshell_goal_quantity": 99,
+ "extra_clamshells_in_pool": 99,
+ "included_regions": 0, #beginner
+ "job_rando": 2, #full
+ "starting_job_quantity": 1,
+ "kill_bosses_mode": 0,
+ "shopsanity": 0,
+ "regionsanity": 1,
+ "progressive_level_size": 3,
+ "max_level": 99,
+ "key_mode": 2, #vanilla
+ "start_with_treasure_finder": 0,
+ "start_with_maps": 1,
+ "include_summon_abilities": 1,
+ "include_scholar_abilities": 1
+ }
+ #Todo: turn off start with maps in the future once we get enough item massaging for that to not break unit tests
run_default_tests = True
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_key_options.py b/worlds/crystal_project/test/test_key_options.py
index 13500f61e503..958a87ea541d 100644
--- a/worlds/crystal_project/test/test_key_options.py
+++ b/worlds/crystal_project/test/test_key_options.py
@@ -1,226 +1,226 @@
-from .bases import CrystalProjectTestBase
-from ..constants.key_items import *
-from ..constants.keys import *
-from ..constants.ap_regions import *
-from ..items import key_rings, dungeon_keys
-
-class MultiuseKeyMethods(CrystalProjectTestBase):
- def has_no_skeleton_key(self):
- self.assertTrue(self.count(SKELETON_KEY) == 0)
-
- def has_no_dungeon_keys(self):
- self.assertTrue(len(self.get_items_by_name(dungeon_keys)) == 0)
-
- def has_no_keyring(self):
- self.assertTrue(len(self.get_items_by_name(key_rings)) == 0)
-
- def has_skeleton_key(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- #Player can reach South Wing Rubble location without any prison keys if they go through the Tram
- unreachable_locations = ["Capital Sequoia Chest - Gardener's Shed 1",
- "Capital Jail Chest - South Wing jail cell across from busted wall",
- "Capital Jail Chest - West Wing jail cell among the glowy plants",
- "Capital Jail Chest - Locked among the foliage in West Wing",
- "Capital Jail Chest - Locked beyond overgrown West Wing hallway",
- "Capital Jail Chest - East Wing bedroom closet twinsies the 1st",
- "Capital Jail Chest - Locked in broken East Wing jail cell",
- "Capital Jail Crystal - Reaper, above hell pool"]
- reachable_locations = ["Capital Jail Chest - Fiercely guarded and locked in South Wing rubble 1"]
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(SKELETON_KEY))
- reachable_locations.extend(unreachable_locations)
- unreachable_locations = []
- self.assert_locations(reachable_locations, unreachable_locations)
-
- def has_singleton_key(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assertFalse(self.can_reach_location("Capital Sequoia Chest - Gardener's Shed 1"))
- self.collect(self.get_item_by_name(GARDENERS_KEY))
- self.assertTrue(self.can_reach_location("Capital Sequoia Chest - Gardener's Shed 1"))
-
-class TestSkeletonKeyMode(MultiuseKeyMethods):
- options = {
- "key_mode": 0
- }
-
- def test_has_skeleton_key(self):
- self.has_skeleton_key()
-
-class TestKeyRings(MultiuseKeyMethods):
- run_default_tests = False
-
- options = {
- "key_mode": 1
- }
-
- def test_has_no_dungeon_keys(self):
- self.has_no_dungeon_keys()
-
- def test_has_skeleton_key(self, skeleton_key_bool = True):
- if skeleton_key_bool:
- self.has_skeleton_key()
- else:
- self.has_no_skeleton_key()
-
- def test_has_singleton_key(self):
- self.has_singleton_key()
-
- def test_has_prison_keyring(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assertFalse(self.can_reach_location("Capital Jail Crystal - Reaper, above hell pool"))
- self.collect(self.get_item_by_name(PRISON_KEY_RING))
- self.assertTrue(self.can_reach_location("Capital Jail Crystal - Reaper, above hell pool"))
-
- def test_has_beaurior_keyring(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assertFalse(self.can_reach_location("Beaurior Volcano Crystal - Valkyrie"))
- self.collect(self.get_item_by_name(BEAURIOR_KEY_RING))
- self.assertTrue(self.can_reach_location("Beaurior Volcano Crystal - Valkyrie"))
-
- def test_has_slip_glide_ride_keyring(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assertFalse(self.can_reach_location("Slip Glide Ride Crystal - Summoner"))
- self.collect(self.get_item_by_name(SLIP_GLIDE_RIDE_KEY_RING))
- self.assertTrue(self.can_reach_location("Slip Glide Ride Crystal - Summoner"))
-
- def test_has_ice_puzzle_keyring(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.collect([self.get_item_by_name(VERMILLION_BOOK), self.get_item_by_name(VIRIDIAN_BOOK), self.get_item_by_name(CERULEAN_BOOK)])
- self.assertFalse(self.can_reach_location("Sequoia Athenaeum Chest - You expected another Chips Challenge, but it was me, Dio!"))
- self.collect(self.get_item_by_name(ICE_PUZZLE_KEY_RING))
- self.assertTrue(self.can_reach_location("Sequoia Athenaeum Chest - You expected another Chips Challenge, but it was me, Dio!"))
-
- def test_has_jidamba_keyring(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assertFalse(self.can_reach_region(JIDAMBA_EACLANEYA_AP_REGION))
- self.collect(self.get_item_by_name(JIDAMBA_KEY_RING))
- self.assertTrue(self.can_reach_region(JIDAMBA_EACLANEYA_AP_REGION))
-
-class TestVanillaKeys(MultiuseKeyMethods):
- run_default_tests = False
-
- options = {
- "key_mode": 2
- }
-
- def test_has_no_keyring(self):
- self.has_no_keyring()
-
- def test_has_skeleton_key(self, skeleton_key_bool = True):
- if skeleton_key_bool:
- self.has_skeleton_key()
- else:
- self.has_no_skeleton_key()
-
- def test_has_singleton_key(self):
- self.has_singleton_key()
-
- def test_has_prison_keys(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- #Player can reach South Wing Rubble location without any prison keys if they go through the Tram
- unreachable_locations = ["Capital Jail Chest - South Wing jail cell across from busted wall",
- "Capital Jail Chest - West Wing jail cell among the glowy plants",
- "Capital Jail Chest - Locked among the foliage in West Wing",
- "Capital Jail Chest - Locked beyond overgrown West Wing hallway",
- "Capital Jail Chest - East Wing bedroom closet twinsies the 1st",
- "Capital Jail Chest - Locked in broken East Wing jail cell",
- "Capital Jail Crystal - Reaper, above hell pool"]
- reachable_locations = ["Capital Jail Chest - Fiercely guarded and locked in South Wing rubble 1"]
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(SOUTH_WING_KEY))
- expected_passing_location = "Capital Jail Chest - South Wing jail cell across from busted wall"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(CELL_KEY))
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(WEST_WING_KEY))
- expected_passing_location = "Capital Jail Chest - West Wing jail cell among the glowy plants"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(EAST_WING_KEY))
- expected_passing_location = "Capital Jail Chest - East Wing bedroom closet twinsies the 1st"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(DARK_WING_KEY))
- expected_passing_location = "Capital Jail Crystal - Reaper, above hell pool"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect(self.get_item_by_name(CELL_KEY))
- self.collect(self.get_item_by_name(CELL_KEY))
- self.collect(self.get_item_by_name(CELL_KEY))
- expected_passing_location = "Capital Jail Chest - Locked among the foliage in West Wing"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
- self.collect_by_name(CELL_KEY)
- expected_passing_location_a = "Capital Jail Chest - Locked beyond overgrown West Wing hallway"
- expected_passing_location_b = "Capital Jail Chest - Locked in broken East Wing jail cell"
- unreachable_locations.remove(expected_passing_location_a)
- unreachable_locations.remove(expected_passing_location_b)
- reachable_locations.extend([expected_passing_location_a, expected_passing_location_b])
- self.assert_locations(reachable_locations, unreachable_locations)
-
-class TestKeyRingsSkeleFree(TestKeyRings):
- run_default_tests = False
-
- options = {
- "key_mode": 3
- }
-
- def test_has_no_dungeon_keys(self):
- self.has_no_dungeon_keys()
-
- def test_has_skeleton_key(self, skeleton_key_bool = False):
- TestKeyRings.test_has_skeleton_key(self, skeleton_key_bool)
-
- def test_has_singleton_key(self):
- self.has_singleton_key()
-
- def test_has_prison_keyring(self):
- TestKeyRings.test_has_prison_keyring(self)
-
- def test_has_beaurior_keyring(self):
- TestKeyRings.test_has_beaurior_keyring(self)
-
- def test_has_slip_glide_ride_keyring(self):
- TestKeyRings.test_has_slip_glide_ride_keyring(self)
-
- def test_has_ice_puzzle_keyring(self):
- TestKeyRings.test_has_ice_puzzle_keyring(self)
-
- def test_has_jidamba_keyring(self):
- TestKeyRings.test_has_jidamba_keyring(self)
-
-class TestVanillaKeysSkeleFree(TestVanillaKeys):
- run_default_tests = False
-
- options = {
- "key_mode": 4
- }
-
- def test_has_no_skeleton_key(self):
- self.has_no_skeleton_key()
-
- def test_has_no_keyring(self):
- self.has_no_keyring()
-
- def test_has_skeleton_key(self, skeleton_key_bool = False):
- TestVanillaKeys.test_has_skeleton_key(self, skeleton_key_bool)
-
- def test_has_singleton_key(self):
- self.has_singleton_key()
-
- def test_has_prison_keys(self):
+from .bases import CrystalProjectTestBase
+from ..constants.key_items import *
+from ..constants.keys import *
+from ..constants.ap_regions import *
+from ..items import key_rings, dungeon_keys
+
+class MultiuseKeyMethods(CrystalProjectTestBase):
+ def has_no_skeleton_key(self):
+ self.assertTrue(self.count(SKELETON_KEY) == 0)
+
+ def has_no_dungeon_keys(self):
+ self.assertTrue(len(self.get_items_by_name(dungeon_keys)) == 0)
+
+ def has_no_keyring(self):
+ self.assertTrue(len(self.get_items_by_name(key_rings)) == 0)
+
+ def has_skeleton_key(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ #Player can reach South Wing Rubble location without any prison keys if they go through the Tram
+ unreachable_locations = ["Capital Sequoia Chest - Gardener's Shed 1",
+ "Capital Jail Chest - South Wing jail cell across from busted wall",
+ "Capital Jail Chest - West Wing jail cell among the glowy plants",
+ "Capital Jail Chest - Locked among the foliage in West Wing",
+ "Capital Jail Chest - Locked beyond overgrown West Wing hallway",
+ "Capital Jail Chest - East Wing bedroom closet twinsies the 1st",
+ "Capital Jail Chest - Locked in broken East Wing jail cell",
+ "Capital Jail Crystal - Reaper, above hell pool"]
+ reachable_locations = ["Capital Jail Chest - Fiercely guarded and locked in South Wing rubble 1"]
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ reachable_locations.extend(unreachable_locations)
+ unreachable_locations = []
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ def has_singleton_key(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assertFalse(self.can_reach_location("Capital Sequoia Chest - Gardener's Shed 1"))
+ self.collect(self.get_item_by_name(GARDENERS_KEY))
+ self.assertTrue(self.can_reach_location("Capital Sequoia Chest - Gardener's Shed 1"))
+
+class TestSkeletonKeyMode(MultiuseKeyMethods):
+ options = {
+ "key_mode": 0
+ }
+
+ def test_has_skeleton_key(self):
+ self.has_skeleton_key()
+
+class TestKeyRings(MultiuseKeyMethods):
+ run_default_tests = False
+
+ options = {
+ "key_mode": 1
+ }
+
+ def test_has_no_dungeon_keys(self):
+ self.has_no_dungeon_keys()
+
+ def test_has_skeleton_key(self, skeleton_key_bool = True):
+ if skeleton_key_bool:
+ self.has_skeleton_key()
+ else:
+ self.has_no_skeleton_key()
+
+ def test_has_singleton_key(self):
+ self.has_singleton_key()
+
+ def test_has_prison_keyring(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assertFalse(self.can_reach_location("Capital Jail Crystal - Reaper, above hell pool"))
+ self.collect(self.get_item_by_name(PRISON_KEY_RING))
+ self.assertTrue(self.can_reach_location("Capital Jail Crystal - Reaper, above hell pool"))
+
+ def test_has_beaurior_keyring(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assertFalse(self.can_reach_location("Beaurior Volcano Crystal - Valkyrie"))
+ self.collect(self.get_item_by_name(BEAURIOR_KEY_RING))
+ self.assertTrue(self.can_reach_location("Beaurior Volcano Crystal - Valkyrie"))
+
+ def test_has_slip_glide_ride_keyring(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assertFalse(self.can_reach_location("Slip Glide Ride Crystal - Summoner"))
+ self.collect(self.get_item_by_name(SLIP_GLIDE_RIDE_KEY_RING))
+ self.assertTrue(self.can_reach_location("Slip Glide Ride Crystal - Summoner"))
+
+ def test_has_ice_puzzle_keyring(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.collect([self.get_item_by_name(VERMILLION_BOOK), self.get_item_by_name(VIRIDIAN_BOOK), self.get_item_by_name(CERULEAN_BOOK)])
+ self.assertFalse(self.can_reach_location("Sequoia Athenaeum Chest - You expected another Chips Challenge, but it was me, Dio!"))
+ self.collect(self.get_item_by_name(ICE_PUZZLE_KEY_RING))
+ self.assertTrue(self.can_reach_location("Sequoia Athenaeum Chest - You expected another Chips Challenge, but it was me, Dio!"))
+
+ def test_has_jidamba_keyring(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assertFalse(self.can_reach_region(JIDAMBA_EACLANEYA_AP_REGION))
+ self.collect(self.get_item_by_name(JIDAMBA_KEY_RING))
+ self.assertTrue(self.can_reach_region(JIDAMBA_EACLANEYA_AP_REGION))
+
+class TestVanillaKeys(MultiuseKeyMethods):
+ run_default_tests = False
+
+ options = {
+ "key_mode": 2
+ }
+
+ def test_has_no_keyring(self):
+ self.has_no_keyring()
+
+ def test_has_skeleton_key(self, skeleton_key_bool = True):
+ if skeleton_key_bool:
+ self.has_skeleton_key()
+ else:
+ self.has_no_skeleton_key()
+
+ def test_has_singleton_key(self):
+ self.has_singleton_key()
+
+ def test_has_prison_keys(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ #Player can reach South Wing Rubble location without any prison keys if they go through the Tram
+ unreachable_locations = ["Capital Jail Chest - South Wing jail cell across from busted wall",
+ "Capital Jail Chest - West Wing jail cell among the glowy plants",
+ "Capital Jail Chest - Locked among the foliage in West Wing",
+ "Capital Jail Chest - Locked beyond overgrown West Wing hallway",
+ "Capital Jail Chest - East Wing bedroom closet twinsies the 1st",
+ "Capital Jail Chest - Locked in broken East Wing jail cell",
+ "Capital Jail Crystal - Reaper, above hell pool"]
+ reachable_locations = ["Capital Jail Chest - Fiercely guarded and locked in South Wing rubble 1"]
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(SOUTH_WING_KEY))
+ expected_passing_location = "Capital Jail Chest - South Wing jail cell across from busted wall"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(CELL_KEY))
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(WEST_WING_KEY))
+ expected_passing_location = "Capital Jail Chest - West Wing jail cell among the glowy plants"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(EAST_WING_KEY))
+ expected_passing_location = "Capital Jail Chest - East Wing bedroom closet twinsies the 1st"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(DARK_WING_KEY))
+ expected_passing_location = "Capital Jail Crystal - Reaper, above hell pool"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect(self.get_item_by_name(CELL_KEY))
+ self.collect(self.get_item_by_name(CELL_KEY))
+ self.collect(self.get_item_by_name(CELL_KEY))
+ expected_passing_location = "Capital Jail Chest - Locked among the foliage in West Wing"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ self.collect_by_name(CELL_KEY)
+ expected_passing_location_a = "Capital Jail Chest - Locked beyond overgrown West Wing hallway"
+ expected_passing_location_b = "Capital Jail Chest - Locked in broken East Wing jail cell"
+ unreachable_locations.remove(expected_passing_location_a)
+ unreachable_locations.remove(expected_passing_location_b)
+ reachable_locations.extend([expected_passing_location_a, expected_passing_location_b])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+class TestKeyRingsSkeleFree(TestKeyRings):
+ run_default_tests = False
+
+ options = {
+ "key_mode": 3
+ }
+
+ def test_has_no_dungeon_keys(self):
+ self.has_no_dungeon_keys()
+
+ def test_has_skeleton_key(self, skeleton_key_bool = False):
+ TestKeyRings.test_has_skeleton_key(self, skeleton_key_bool)
+
+ def test_has_singleton_key(self):
+ self.has_singleton_key()
+
+ def test_has_prison_keyring(self):
+ TestKeyRings.test_has_prison_keyring(self)
+
+ def test_has_beaurior_keyring(self):
+ TestKeyRings.test_has_beaurior_keyring(self)
+
+ def test_has_slip_glide_ride_keyring(self):
+ TestKeyRings.test_has_slip_glide_ride_keyring(self)
+
+ def test_has_ice_puzzle_keyring(self):
+ TestKeyRings.test_has_ice_puzzle_keyring(self)
+
+ def test_has_jidamba_keyring(self):
+ TestKeyRings.test_has_jidamba_keyring(self)
+
+class TestVanillaKeysSkeleFree(TestVanillaKeys):
+ run_default_tests = False
+
+ options = {
+ "key_mode": 4
+ }
+
+ def test_has_no_skeleton_key(self):
+ self.has_no_skeleton_key()
+
+ def test_has_no_keyring(self):
+ self.has_no_keyring()
+
+ def test_has_skeleton_key(self, skeleton_key_bool = False):
+ TestVanillaKeys.test_has_skeleton_key(self, skeleton_key_bool)
+
+ def test_has_singleton_key(self):
+ self.has_singleton_key()
+
+ def test_has_prison_keys(self):
TestVanillaKeys.test_has_prison_keys(self)
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_level_options.py b/worlds/crystal_project/test/test_level_options.py
index 97ca8f62c03b..1503c631cbe2 100644
--- a/worlds/crystal_project/test/test_level_options.py
+++ b/worlds/crystal_project/test/test_level_options.py
@@ -1,269 +1,269 @@
-from .bases import CrystalProjectTestBase
-from ..constants.key_items import *
-from ..constants.keys import *
-from ..constants.mounts import *
-from ..constants.ap_regions import *
-from BaseClasses import CollectionState
-
-
-class TestLevelGatingOff(CrystalProjectTestBase):
- options = {
- "level_gating": 0,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
- def test_region_accessibility(self):
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
- self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
-
- def test_boss_accessibility(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
-
- unreachable_locations = []
- reachable_locations = ["Soiled Den Boss - Bone Thief",
- "Delende Boss - Troll",
- "Quintar Sanctum Boss - Fancy Quintar",
- "Beaurior Rock Boss - Iguanadon & Iguanadin",
- "Shoudu Province NPC - 1 Sky Arena Win Prize",
- "Shoudu Province Chest - 5 Sky Arena Wins room 4",
- "Shoudu Province NPC - Gold in 8 Sky Arena Wins room 1",
- "The Depths Boss - The Devourer"]
- self.assert_locations(reachable_locations, unreachable_locations)
-
-class TestLevelGatingLevelPasses(CrystalProjectTestBase):
- options = {
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
- # Level gating defaults: Progressive Level Passes, Progressive Level Size = 6
- def test_region_accessibility(self):
- # Ancient Reservoir Min Level = 33; player starts with 1 Progressive Level
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(4)
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
-
- def test_boss_accessibility(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
- # Starting level expectation: 6
- # Bone Thief 12, Fancy Quintar 26, 1 Sky Arena 30 (but Shoudu is 36), Iganabros 40, 5 Sky Arena 44, Troll 50, 8 Sky Arena 54, The Devourer 65
- unreachable_locations = ["Soiled Den Boss - Bone Thief",
- "Quintar Sanctum Boss - Fancy Quintar",
- "Shoudu Province NPC - 1 Sky Arena Win Prize",
- "Beaurior Rock Boss - Iguanadon & Iguanadin",
- "Shoudu Province Chest - 5 Sky Arena Wins room 4",
- "Delende Boss - Troll",
- "Shoudu Province NPC - Gold in 8 Sky Arena Wins room 1",
- "The Depths Boss - The Devourer"]
- reachable_locations = []
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 12
- self.collect_progressive_levels(1)
- expected_passing_location = "Soiled Den Boss - Bone Thief"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 18 +1 Progressive Level: 24
- self.collect_progressive_levels(2)
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 30
- # 1 sky arena win itself only requires level 30, but Shoudu is level 36
- self.collect_progressive_levels(1)
- expected_passing_location = "Quintar Sanctum Boss - Fancy Quintar"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 36
- self.collect_progressive_levels(1)
- expected_passing_location = "Shoudu Province NPC - 1 Sky Arena Win Prize"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 42
- self.collect_progressive_levels(1)
- expected_passing_location = "Beaurior Rock Boss - Iguanadon & Iguanadin"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 48
- self.collect_progressive_levels(1)
- expected_passing_location = "Shoudu Province Chest - 5 Sky Arena Wins room 4"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 54
- self.collect_progressive_levels(1)
- expected_passing_location_a = "Delende Boss - Troll"
- expected_passing_location_b = "Shoudu Province NPC - Gold in 8 Sky Arena Wins room 1"
- unreachable_locations.remove(expected_passing_location_a)
- unreachable_locations.remove(expected_passing_location_b)
- reachable_locations.extend([expected_passing_location_a, expected_passing_location_b])
- self.assert_locations(reachable_locations, unreachable_locations)
- # +1 Progressive Level: 60
- # The Devourer is above level 60, but by default that's the highest level a player can reach
- self.collect_progressive_levels(1)
- expected_passing_location = "The Depths Boss - The Devourer"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
-class TestLevelGatingLevelCapped(TestLevelGatingLevelPasses):
- options = {
- "level_gating": 2,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
-class TestLevelGatingLevelCatchUp(TestLevelGatingLevelPasses):
- options = {
- "level_gating": 3,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
-class TestMaxLevelIncrease(CrystalProjectTestBase):
- options = {
- "level_gating": 1,
- "max_level": 62,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
- def test_boss_above_level_60(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
- # Default starting level expectation: 6; 1 Progressive Level in starting inventory; Progressive Level Size 6
-
- # The Devourer: 65
- unreachable_locations = ["The Depths Boss - The Devourer"]
- reachable_locations = []
- self.collect_progressive_levels(9)
- self.assert_locations(reachable_locations, unreachable_locations)
- self.collect_progressive_levels(1)
- expected_passing_location = "The Depths Boss - The Devourer"
- unreachable_locations.remove(expected_passing_location)
- reachable_locations.extend([expected_passing_location])
- self.assert_locations(reachable_locations, unreachable_locations)
-
- def test_region_above_level_60(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
- # Default starting level expectation: 6; 1 Progressive Level in starting inventory; Progressive Level Size 6
-
- # The Depths: 63
- self.collect_progressive_levels(9)
- self.assertFalse(self.can_reach_region(THE_DEPTHS_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
-
-class TestMaxLevelDecrease(CrystalProjectTestBase):
- options = {
- "level_gating": 1,
- "max_level": 3,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
- # you can do everything at level 3 B)
- def test_region_above_level_60(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
- # The Depths: 63
- self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
-
- def test_number_of_progressive_levels_in_pool(self):
- # 0 Progressive Levels in the pool
- self.assertTrue(len(self.get_items_by_name(PROGRESSIVE_LEVEL)) == 0)
- starting_state: CollectionState = CollectionState(self.multiworld)
- # only 1 pre-collected Progressive Level
- self.assertTrue(starting_state.has(PROGRESSIVE_LEVEL, self.player, 1))
- self.assertFalse(starting_state.has(PROGRESSIVE_LEVEL, self.player, 2))
-
-class TestProgressiveLevelSize(CrystalProjectTestBase):
- options = {
- "progressive_level_size": 10,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- }
-
- def test_region_accessibility(self):
- self.collect_mounts()
- # Ancient Reservoir Min Level = 33; player starts with 1 Progressive Level
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(2)
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
-
-class TestLevelComparedToEnemiesIncrease(CrystalProjectTestBase):
- options = {
- "level_gating": 1,
- "level_compared_to_enemies": 5,
- "max_level": 62,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
- def test_region_accessibility(self):
- self.collect_mounts()
- # Ancient Reservoir Min Level = 33; +5; player starts with 2 Progressive Levels b/c Spawning Meadows level is 3+5 = 8 and Progressive Level size is 6
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(4)
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
-
- def test_region_above_level_60(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
- # Default starting level expectation: 6; player starts with 2 Progressive Levels b/c Spawning Meadows level is 3+5 = 8 and Progressive Level Size is 6
-
- # The Depths: 63
- self.collect_progressive_levels(8)
- self.assertFalse(self.can_reach_region(THE_DEPTHS_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
-
-class TestLevelComparedToEnemiesDecrease(CrystalProjectTestBase):
- options = {
- "level_gating": 1,
- "level_compared_to_enemies": -5,
- "max_level": 62,
- "progressive_mount_mode": 0,
- "key_mode": 0,
- "kill_bosses_mode": 1
- }
-
- def test_region_accessibility(self):
- self.collect_mounts()
- # Ancient Reservoir Min Level = 33; -5; player starts with 1 Progressive Level; Progressive Level size 6 (default)
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(3)
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
-
- def test_region_above_level_60(self):
- self.collect_mounts()
- self.collect(self.get_item_by_name(SKELETON_KEY))
- # Default starting level expectation: 6; 1 Progressive Level in starting inventory; Progressive Level Size 6
-
- # The Depths: 63
- self.collect_progressive_levels(8)
- self.assertFalse(self.can_reach_region(THE_DEPTHS_AP_REGION))
- self.collect_progressive_levels(1)
+from .bases import CrystalProjectTestBase
+from ..constants.key_items import *
+from ..constants.keys import *
+from ..constants.mounts import *
+from ..constants.ap_regions import *
+from BaseClasses import CollectionState
+
+
+class TestLevelGatingOff(CrystalProjectTestBase):
+ options = {
+ "level_gating": 0,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+ def test_region_accessibility(self):
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
+ self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+
+ def test_boss_accessibility(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+
+ unreachable_locations = []
+ reachable_locations = ["Soiled Den Boss - Bone Thief",
+ "Delende Boss - Troll",
+ "Quintar Sanctum Boss - Fancy Quintar",
+ "Beaurior Rock Boss - Iguanadon & Iguanadin",
+ "Shoudu Province NPC - 1 Sky Arena Win Prize",
+ "Shoudu Province Chest - 5 Sky Arena Wins room 4",
+ "Shoudu Province NPC - Gold in 8 Sky Arena Wins room 1",
+ "The Depths Boss - The Devourer"]
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+class TestLevelGatingLevelPasses(CrystalProjectTestBase):
+ options = {
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+ # Level gating defaults: Progressive Level Passes, Progressive Level Size = 6
+ def test_region_accessibility(self):
+ # Ancient Reservoir Min Level = 33; player starts with 1 Progressive Level
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(4)
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+
+ def test_boss_accessibility(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ # Starting level expectation: 6
+ # Bone Thief 12, Fancy Quintar 26, 1 Sky Arena 30 (but Shoudu is 36), Iganabros 40, 5 Sky Arena 44, Troll 50, 8 Sky Arena 54, The Devourer 65
+ unreachable_locations = ["Soiled Den Boss - Bone Thief",
+ "Quintar Sanctum Boss - Fancy Quintar",
+ "Shoudu Province NPC - 1 Sky Arena Win Prize",
+ "Beaurior Rock Boss - Iguanadon & Iguanadin",
+ "Shoudu Province Chest - 5 Sky Arena Wins room 4",
+ "Delende Boss - Troll",
+ "Shoudu Province NPC - Gold in 8 Sky Arena Wins room 1",
+ "The Depths Boss - The Devourer"]
+ reachable_locations = []
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 12
+ self.collect_progressive_levels(1)
+ expected_passing_location = "Soiled Den Boss - Bone Thief"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 18 +1 Progressive Level: 24
+ self.collect_progressive_levels(2)
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 30
+ # 1 sky arena win itself only requires level 30, but Shoudu is level 36
+ self.collect_progressive_levels(1)
+ expected_passing_location = "Quintar Sanctum Boss - Fancy Quintar"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 36
+ self.collect_progressive_levels(1)
+ expected_passing_location = "Shoudu Province NPC - 1 Sky Arena Win Prize"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 42
+ self.collect_progressive_levels(1)
+ expected_passing_location = "Beaurior Rock Boss - Iguanadon & Iguanadin"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 48
+ self.collect_progressive_levels(1)
+ expected_passing_location = "Shoudu Province Chest - 5 Sky Arena Wins room 4"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 54
+ self.collect_progressive_levels(1)
+ expected_passing_location_a = "Delende Boss - Troll"
+ expected_passing_location_b = "Shoudu Province NPC - Gold in 8 Sky Arena Wins room 1"
+ unreachable_locations.remove(expected_passing_location_a)
+ unreachable_locations.remove(expected_passing_location_b)
+ reachable_locations.extend([expected_passing_location_a, expected_passing_location_b])
+ self.assert_locations(reachable_locations, unreachable_locations)
+ # +1 Progressive Level: 60
+ # The Devourer is above level 60, but by default that's the highest level a player can reach
+ self.collect_progressive_levels(1)
+ expected_passing_location = "The Depths Boss - The Devourer"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+class TestLevelGatingLevelCapped(TestLevelGatingLevelPasses):
+ options = {
+ "level_gating": 2,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+class TestLevelGatingLevelCatchUp(TestLevelGatingLevelPasses):
+ options = {
+ "level_gating": 3,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+class TestMaxLevelIncrease(CrystalProjectTestBase):
+ options = {
+ "level_gating": 1,
+ "max_level": 62,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+ def test_boss_above_level_60(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ # Default starting level expectation: 6; 1 Progressive Level in starting inventory; Progressive Level Size 6
+
+ # The Devourer: 65
+ unreachable_locations = ["The Depths Boss - The Devourer"]
+ reachable_locations = []
+ self.collect_progressive_levels(9)
+ self.assert_locations(reachable_locations, unreachable_locations)
+ self.collect_progressive_levels(1)
+ expected_passing_location = "The Depths Boss - The Devourer"
+ unreachable_locations.remove(expected_passing_location)
+ reachable_locations.extend([expected_passing_location])
+ self.assert_locations(reachable_locations, unreachable_locations)
+
+ def test_region_above_level_60(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ # Default starting level expectation: 6; 1 Progressive Level in starting inventory; Progressive Level Size 6
+
+ # The Depths: 63
+ self.collect_progressive_levels(9)
+ self.assertFalse(self.can_reach_region(THE_DEPTHS_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
+
+class TestMaxLevelDecrease(CrystalProjectTestBase):
+ options = {
+ "level_gating": 1,
+ "max_level": 3,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+ # you can do everything at level 3 B)
+ def test_region_above_level_60(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ # The Depths: 63
+ self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
+
+ def test_number_of_progressive_levels_in_pool(self):
+ # 0 Progressive Levels in the pool
+ self.assertTrue(len(self.get_items_by_name(PROGRESSIVE_LEVEL)) == 0)
+ starting_state: CollectionState = CollectionState(self.multiworld)
+ # only 1 pre-collected Progressive Level
+ self.assertTrue(starting_state.has(PROGRESSIVE_LEVEL, self.player, 1))
+ self.assertFalse(starting_state.has(PROGRESSIVE_LEVEL, self.player, 2))
+
+class TestProgressiveLevelSize(CrystalProjectTestBase):
+ options = {
+ "progressive_level_size": 10,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ }
+
+ def test_region_accessibility(self):
+ self.collect_mounts()
+ # Ancient Reservoir Min Level = 33; player starts with 1 Progressive Level
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(2)
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+
+class TestLevelComparedToEnemiesIncrease(CrystalProjectTestBase):
+ options = {
+ "level_gating": 1,
+ "level_compared_to_enemies": 5,
+ "max_level": 62,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+ def test_region_accessibility(self):
+ self.collect_mounts()
+ # Ancient Reservoir Min Level = 33; +5; player starts with 2 Progressive Levels b/c Spawning Meadows level is 3+5 = 8 and Progressive Level size is 6
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(4)
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+
+ def test_region_above_level_60(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ # Default starting level expectation: 6; player starts with 2 Progressive Levels b/c Spawning Meadows level is 3+5 = 8 and Progressive Level Size is 6
+
+ # The Depths: 63
+ self.collect_progressive_levels(8)
+ self.assertFalse(self.can_reach_region(THE_DEPTHS_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
+
+class TestLevelComparedToEnemiesDecrease(CrystalProjectTestBase):
+ options = {
+ "level_gating": 1,
+ "level_compared_to_enemies": -5,
+ "max_level": 62,
+ "progressive_mount_mode": 0,
+ "key_mode": 0,
+ "kill_bosses_mode": 1
+ }
+
+ def test_region_accessibility(self):
+ self.collect_mounts()
+ # Ancient Reservoir Min Level = 33; -5; player starts with 1 Progressive Level; Progressive Level size 6 (default)
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(3)
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+
+ def test_region_above_level_60(self):
+ self.collect_mounts()
+ self.collect(self.get_item_by_name(SKELETON_KEY))
+ # Default starting level expectation: 6; 1 Progressive Level in starting inventory; Progressive Level Size 6
+
+ # The Depths: 63
+ self.collect_progressive_levels(8)
+ self.assertFalse(self.can_reach_region(THE_DEPTHS_AP_REGION))
+ self.collect_progressive_levels(1)
self.assertTrue(self.can_reach_region(THE_DEPTHS_AP_REGION))
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_mount_options.py b/worlds/crystal_project/test/test_mount_options.py
index 42e5e76297eb..a8535d6537f4 100644
--- a/worlds/crystal_project/test/test_mount_options.py
+++ b/worlds/crystal_project/test/test_mount_options.py
@@ -1,127 +1,127 @@
-from .bases import CrystalProjectTestBase
-from ..constants.teleport_stones import *
-from ..constants.key_items import *
-from ..constants.mounts import *
-from ..constants.ap_regions import *
-
-
-class TestProgressiveMountModeOn(CrystalProjectTestBase):
- options = {
- "progressive_mount_mode": 1,
- "level_gating": 0,
- }
-
- def test_quintar_pass(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.assertFalse(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertTrue(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
-
- def test_quintar_flute(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertFalse(self.can_reach_region(SALMON_RIVER_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertTrue(self.can_reach_region(SALMON_RIVER_AP_REGION))
-
- def test_ibek_bell(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertFalse(self.can_reach_region(SOUVENIR_SHOP_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertTrue(self.can_reach_region(SOUVENIR_SHOP_AP_REGION))
-
- def test_owl_drum(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect_by_name(DIONE_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertFalse(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertTrue(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
-
- def test_salmon_violin(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect_by_name(DIONE_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertFalse(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertTrue(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
-
- def test_quintar_ocarina(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect_by_name(DIONE_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertFalse(self.can_reach_region(THE_SEQUOIA_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.assertTrue(self.can_reach_region(THE_SEQUOIA_AP_REGION))
-
-class TestProgressiveMountModeOff(CrystalProjectTestBase):
- options = {
- "progressive_mount_mode": 0,
- "level_gating": 0,
- }
-
- def test_quintar_pass(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.assertFalse(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.assertTrue(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
-
- def test_quintar_flute(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.assertFalse(self.can_reach_region(SALMON_RIVER_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.assertTrue(self.can_reach_region(SALMON_RIVER_AP_REGION))
-
- def test_ibek_bell(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.assertFalse(self.can_reach_region(GREENSHIRE_OVERLOOK_AP_REGION))
- self.collect(self.get_item_by_name(IBEK_BELL))
- self.assertTrue(self.can_reach_region(GREENSHIRE_OVERLOOK_AP_REGION))
-
- def test_owl_drum(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect_by_name(DIONE_STONE)
- self.assertFalse(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
- self.collect(self.get_item_by_name(OWL_DRUM))
- self.assertTrue(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
-
- def test_salmon_violin(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect_by_name(DIONE_STONE)
- self.assertFalse(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_SALMON_VIOLA))
- self.assertTrue(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
-
- def test_quintar_ocarina(self):
- self.set_collected_job_count(18)
- self.collect_by_name(GAEA_STONE)
- self.collect_by_name(DIONE_STONE)
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
- self.assertFalse(self.can_reach_region(THE_SEQUOIA_AP_REGION))
- self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+from .bases import CrystalProjectTestBase
+from ..constants.teleport_stones import *
+from ..constants.key_items import *
+from ..constants.mounts import *
+from ..constants.ap_regions import *
+
+
+class TestProgressiveMountModeOn(CrystalProjectTestBase):
+ options = {
+ "progressive_mount_mode": 1,
+ "level_gating": 0,
+ }
+
+ def test_quintar_pass(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.assertFalse(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertTrue(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
+
+ def test_quintar_flute(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertFalse(self.can_reach_region(SALMON_RIVER_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertTrue(self.can_reach_region(SALMON_RIVER_AP_REGION))
+
+ def test_ibek_bell(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertFalse(self.can_reach_region(SOUVENIR_SHOP_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertTrue(self.can_reach_region(SOUVENIR_SHOP_AP_REGION))
+
+ def test_owl_drum(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect_by_name(DIONE_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertFalse(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertTrue(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
+
+ def test_salmon_violin(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect_by_name(DIONE_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertFalse(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertTrue(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
+
+ def test_quintar_ocarina(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect_by_name(DIONE_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertFalse(self.can_reach_region(THE_SEQUOIA_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.assertTrue(self.can_reach_region(THE_SEQUOIA_AP_REGION))
+
+class TestProgressiveMountModeOff(CrystalProjectTestBase):
+ options = {
+ "progressive_mount_mode": 0,
+ "level_gating": 0,
+ }
+
+ def test_quintar_pass(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.assertFalse(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.assertTrue(self.can_reach_region(CAPITAL_JAIL_AP_REGION))
+
+ def test_quintar_flute(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.assertFalse(self.can_reach_region(SALMON_RIVER_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.assertTrue(self.can_reach_region(SALMON_RIVER_AP_REGION))
+
+ def test_ibek_bell(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.assertFalse(self.can_reach_region(GREENSHIRE_OVERLOOK_AP_REGION))
+ self.collect(self.get_item_by_name(IBEK_BELL))
+ self.assertTrue(self.can_reach_region(GREENSHIRE_OVERLOOK_AP_REGION))
+
+ def test_owl_drum(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect_by_name(DIONE_STONE)
+ self.assertFalse(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
+ self.collect(self.get_item_by_name(OWL_DRUM))
+ self.assertTrue(self.can_reach_region(JIDAMBA_FOREST_FLOOR_AP_REGION))
+
+ def test_salmon_violin(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect_by_name(DIONE_STONE)
+ self.assertFalse(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_SALMON_VIOLA))
+ self.assertTrue(self.can_reach_region(THE_OPEN_SEA_AP_REGION))
+
+ def test_quintar_ocarina(self):
+ self.set_collected_job_count(18)
+ self.collect_by_name(GAEA_STONE)
+ self.collect_by_name(DIONE_STONE)
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
+ self.assertFalse(self.can_reach_region(THE_SEQUOIA_AP_REGION))
+ self.collect(self.get_item_by_name(PROGRESSIVE_QUINTAR_WOODWIND))
self.assertTrue(self.can_reach_region(THE_SEQUOIA_AP_REGION))
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_regionsanity.py b/worlds/crystal_project/test/test_regionsanity.py
index 55c3ae700f87..6d7dd4cccb4b 100644
--- a/worlds/crystal_project/test/test_regionsanity.py
+++ b/worlds/crystal_project/test/test_regionsanity.py
@@ -1,116 +1,116 @@
-from .bases import CrystalProjectTestBase
-from ..options import *
-from ..constants.jobs import *
-from ..constants.region_passes import *
-from ..constants.ap_regions import *
-from ..constants.display_regions import *
-from ..constants.teleport_stones import *
-from ..constants.keys import *
-from ..constants.key_items import *
-from ..constants.mounts import *
-
-class TestRegionsanityOff(CrystalProjectTestBase):
- options = {
- "regionsanity": 0,
- }
-
- def test_region_accessibility(self):
- self.set_collected_job_count(5)
- self.collect_mounts()
- self.collect_all_progressive_levels()
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
- COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
-
-class TestRegionsanityOn(CrystalProjectTestBase):
- options = {
- "regionsanity": 1,
- "start_inventory_from_pool": {SPAWNING_MEADOWS_PASS: 1},
- "kill_bosses_mode": KillBossesMode.option_true,
- }
-
- def test_region_accessibility(self):
- self.set_collected_job_count(5)
- self.collect_mounts()
- self.collect_all_progressive_levels()
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- unreachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
- COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
- self.collect_by_name(JOJO_SEWERS_PASS)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION,),
- unreachable_regions=(ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
- COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
- self.collect_by_name(ROLLING_QUINTAR_FIELDS_PASS)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION,),
- unreachable_regions=(SKUMPARADISE_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
- self.collect_by_name(SKUMPARADISE_PASS)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,),
- unreachable_regions=(COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
- self.collect_by_name(COBBLESTONE_CRAG_PASS)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
- COBBLESTONE_CRAG_AP_REGION,),
- unreachable_regions=(GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
- self.collect_by_name(GREENSHIRE_REPRISE_PASS)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
- COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,),
- unreachable_regions=(CASTLE_SEQUOIA_AP_REGION,))
- self.collect_by_name(CASTLE_SEQUOIA_PASS)
- self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
- reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
- COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
-
- def test_has_rental_quintar(self):
- self.set_collected_job_count(5)
- self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
- self.collect(self.get_item_by_name(GAEA_STONE))
- self.collect_all_progressive_levels()
- # Checking reachability of Salmon Pass tests whether or not has_rental_quintar is correctly making sure you can use the rental desk in rolling quintar fields
- self.collect_by_name(SALMON_PASS_PASS)
- self.collect_by_name(CAPITAL_SEQUOIA_PASS)
- self.collect_by_name(GREENSHIRE_REPRISE_PASS)
- self.assertFalse(self.can_reach_region(SALMON_PASS_EAST_AP_REGION))
- self.collect_by_name(ROLLING_QUINTAR_FIELDS_PASS)
- self.assertTrue(self.can_reach_region(SALMON_PASS_EAST_AP_REGION))
-
- def test_region_completion(self):
- #Verifying that all subregions must be reachable before the region completion location is reachable
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.collect_mounts()
- self.collect_all_progressive_levels()
- self.collect_by_name([POKO_POKO_DESERT_PASS, ANCIENT_RESERVOIR_PASS, SARA_SARA_BAZAAR_PASS, SARA_SARA_BEACH_EAST_PASS])
- self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.assertTrue(self.can_reach_region(IBEK_CAVE_AP_REGION))
- self.assert_locations(unreachable_locations=[f"{ANCIENT_RESERVOIR_DISPLAY_NAME} Region Completion"])
-
- self.collect_by_name(PYRAMID_KEY)
- self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
- self.assert_locations(reachable_locations=[f"{ANCIENT_RESERVOIR_DISPLAY_NAME} Region Completion"])
-
- def test_region_completion3(self):
- # This test attempts to validate region completion logic for spawning meadows
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
- self.collect_all_progressive_levels()
- self.collect_by_name([SUMMONER_JOB])
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
- self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
- self.collect(self.get_item_by_name(BLACK_SQUIRREL))
- self.collect(self.get_item_by_name(BLACK_SQUIRREL))
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
- self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
- self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
- self.collect(self.get_item_by_name(BLACK_SQUIRREL))
- self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
- self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
- self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
-
- def world_setup(self, *args, **kwargs):
- super().world_setup(seed=995067462)
-
+from .bases import CrystalProjectTestBase
+from ..options import *
+from ..constants.jobs import *
+from ..constants.region_passes import *
+from ..constants.ap_regions import *
+from ..constants.display_regions import *
+from ..constants.teleport_stones import *
+from ..constants.keys import *
+from ..constants.key_items import *
+from ..constants.mounts import *
+
+class TestRegionsanityOff(CrystalProjectTestBase):
+ options = {
+ "regionsanity": 0,
+ }
+
+ def test_region_accessibility(self):
+ self.set_collected_job_count(5)
+ self.collect_mounts()
+ self.collect_all_progressive_levels()
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
+ COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION))
+
+class TestRegionsanityOn(CrystalProjectTestBase):
+ options = {
+ "regionsanity": 1,
+ "start_inventory_from_pool": {SPAWNING_MEADOWS_PASS: 1},
+ "kill_bosses_mode": KillBossesMode.option_true,
+ }
+
+ def test_region_accessibility(self):
+ self.set_collected_job_count(5)
+ self.collect_mounts()
+ self.collect_all_progressive_levels()
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ unreachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
+ COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
+ self.collect_by_name(JOJO_SEWERS_PASS)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION,),
+ unreachable_regions=(ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
+ COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
+ self.collect_by_name(ROLLING_QUINTAR_FIELDS_PASS)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION,),
+ unreachable_regions=(SKUMPARADISE_AP_REGION, COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
+ self.collect_by_name(SKUMPARADISE_PASS)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,),
+ unreachable_regions=(COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
+ self.collect_by_name(COBBLESTONE_CRAG_PASS)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
+ COBBLESTONE_CRAG_AP_REGION,),
+ unreachable_regions=(GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
+ self.collect_by_name(GREENSHIRE_REPRISE_PASS)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
+ COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION,),
+ unreachable_regions=(CASTLE_SEQUOIA_AP_REGION,))
+ self.collect_by_name(CASTLE_SEQUOIA_PASS)
+ self.assert_region_entrances(CAPITAL_SEQUOIA_AP_REGION,
+ reachable_regions=(JOJO_SEWERS_AP_REGION, ROLLING_QUINTAR_FIELDS_AP_REGION, SKUMPARADISE_AP_REGION,
+ COBBLESTONE_CRAG_AP_REGION, GREENSHIRE_REPRISE_AP_REGION, CASTLE_SEQUOIA_AP_REGION,))
+
+ def test_has_rental_quintar(self):
+ self.set_collected_job_count(5)
+ self.collect(self.get_item_by_name(PROGRESSIVE_MOUNT))
+ self.collect(self.get_item_by_name(GAEA_STONE))
+ self.collect_all_progressive_levels()
+ # Checking reachability of Salmon Pass tests whether or not has_rental_quintar is correctly making sure you can use the rental desk in rolling quintar fields
+ self.collect_by_name(SALMON_PASS_PASS)
+ self.collect_by_name(CAPITAL_SEQUOIA_PASS)
+ self.collect_by_name(GREENSHIRE_REPRISE_PASS)
+ self.assertFalse(self.can_reach_region(SALMON_PASS_EAST_AP_REGION))
+ self.collect_by_name(ROLLING_QUINTAR_FIELDS_PASS)
+ self.assertTrue(self.can_reach_region(SALMON_PASS_EAST_AP_REGION))
+
+ def test_region_completion(self):
+ #Verifying that all subregions must be reachable before the region completion location is reachable
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.collect_mounts()
+ self.collect_all_progressive_levels()
+ self.collect_by_name([POKO_POKO_DESERT_PASS, ANCIENT_RESERVOIR_PASS, SARA_SARA_BAZAAR_PASS, SARA_SARA_BEACH_EAST_PASS])
+ self.assertFalse(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.assertTrue(self.can_reach_region(IBEK_CAVE_AP_REGION))
+ self.assert_locations(unreachable_locations=[f"{ANCIENT_RESERVOIR_DISPLAY_NAME} Region Completion"])
+
+ self.collect_by_name(PYRAMID_KEY)
+ self.assertTrue(self.can_reach_region(ANCIENT_RESERVOIR_AP_REGION))
+ self.assert_locations(reachable_locations=[f"{ANCIENT_RESERVOIR_DISPLAY_NAME} Region Completion"])
+
+ def test_region_completion3(self):
+ # This test attempts to validate region completion logic for spawning meadows
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
+ self.collect_all_progressive_levels()
+ self.collect_by_name([SUMMONER_JOB])
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
+ self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
+ self.collect(self.get_item_by_name(BLACK_SQUIRREL))
+ self.collect(self.get_item_by_name(BLACK_SQUIRREL))
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
+ self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
+ self.assertFalse(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
+ self.collect(self.get_item_by_name(BLACK_SQUIRREL))
+ self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Region Completion"))
+ self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " Boss - Shaku Summon"))
+ self.assertTrue(self.can_reach_location(SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Butterfly Goo"))
+
+ def world_setup(self, *args, **kwargs):
+ super().world_setup(seed=995067462)
+
diff --git a/worlds/crystal_project/test/test_shops.py b/worlds/crystal_project/test/test_shops.py
index 0f4d446cc942..6335e2824705 100644
--- a/worlds/crystal_project/test/test_shops.py
+++ b/worlds/crystal_project/test/test_shops.py
@@ -1,18 +1,18 @@
-from .bases import CrystalProjectTestBase
-from ..constants.region_passes import *
-
-class TestCanEarnMoney(CrystalProjectTestBase):
- run_default_tests = False
-
- options = {
- "shopsanity" : 1,
- "regionsanity" : 1,
- "start_inventory_from_pool": {PROVING_MEADOWS_PASS : 1}
- }
-
- def test_can_earn_money(self):
- self.collect_all_progressive_levels()
- self.assertTrue(self.can_reach_location("Proving Meadows Chest - Tarzan"))
- self.assertFalse(self.can_reach_location("Proving Meadows Shop - Item Merchant 1"))
- self.collect_by_name(THE_PALE_GROTTO_PASS)
+from .bases import CrystalProjectTestBase
+from ..constants.region_passes import *
+
+class TestCanEarnMoney(CrystalProjectTestBase):
+ run_default_tests = False
+
+ options = {
+ "shopsanity" : 1,
+ "regionsanity" : 1,
+ "start_inventory_from_pool": {PROVING_MEADOWS_PASS : 1}
+ }
+
+ def test_can_earn_money(self):
+ self.collect_all_progressive_levels()
+ self.assertTrue(self.can_reach_location("Proving Meadows Chest - Tarzan"))
+ self.assertFalse(self.can_reach_location("Proving Meadows Shop - Item Merchant 1"))
+ self.collect_by_name(THE_PALE_GROTTO_PASS)
self.assertTrue(self.can_reach_location("Proving Meadows Shop - Item Merchant 1"))
\ No newline at end of file
diff --git a/worlds/crystal_project/test/test_spawning_meadows.py b/worlds/crystal_project/test/test_spawning_meadows.py
index 610b31064d26..325b9047896e 100644
--- a/worlds/crystal_project/test/test_spawning_meadows.py
+++ b/worlds/crystal_project/test/test_spawning_meadows.py
@@ -1,163 +1,163 @@
-from ..constants.ap_regions import *
-from ..constants.mounts import *
-from .bases import CrystalProjectTestBase
-
-class TestSpawningMeadows(CrystalProjectTestBase):
- def test_region_accessibility(self):
- self.assertTrue(self.can_reach_region(SPAWNING_MEADOWS_AP_REGION))
-
- def test_region_connections_no_items(self):
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions=(DELENDE_PLAINS_AP_REGION,), unreachable_regions=(MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION))
-
-class TestSpawningMeadowsObscureRoutes(CrystalProjectTestBase):
- options = {
- "level_gating": 0,
- "progressive_mount_mode": 0,
- "obscure_routes": 1
- }
-
- def test_obscure_routes(self):
- unreachable_regions = (MERCURY_SHRINE_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
- reachable_regions = (DELENDE_PLAINS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION,)
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
-
- self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
- unreachable_regions = (MERCURY_SHRINE_AP_REGION, BEAURIOR_VOLCANO_AP_REGION)
- reachable_regions = (DELENDE_PLAINS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, YAMAGAWA_MA_AP_REGION)
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
-
-class TestSpawningMeadowsNoObscureRoutes(CrystalProjectTestBase):
- options = {
- "level_gating": 0,
- "progressive_mount_mode": 0,
- "obscure_routes": 0
- }
-
- def test_obscure_routes(self):
- unreachable_regions = (MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
- reachable_regions = (DELENDE_PLAINS_AP_REGION,)
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
-
- self.collect(self.get_item_by_name(IBEK_BELL))
- unreachable_regions = (CONTINENTAL_TRAM_AP_REGION,)
- reachable_regions = (DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
-
- self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
- unreachable_regions = (CONTINENTAL_TRAM_AP_REGION,)
- reachable_regions = (DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
-
- self.collect_mounts()
- unreachable_regions = (CONTINENTAL_TRAM_AP_REGION,)
- reachable_regions = (DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
- self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
-
-class TestSpawningMeadowsConnectionRulesNoLevelGating(CrystalProjectTestBase):
- options = {
- "level_gating": 0,
- "progressive_mount_mode": 0,
- "obscure_routes": 0
- }
-
- def test_mercury_shrine_connection(self):
- self.collect_by_name(IBEK_BELL)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + MERCURY_SHRINE_AP_REGION))
-
- def test_poko_poko_connection(self):
- self.collect_by_name(IBEK_BELL)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
-
- def test_tram_connection(self):
- self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + CONTINENTAL_TRAM_AP_REGION))
-
- def test_volcano_connection(self):
- self.collect_by_name(IBEK_BELL)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
-
- def test_yamagawa_connection_vertical_movement(self):
- self.collect_by_name(IBEK_BELL)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
- def test_yamagawa_connection_swimming_salmon(self):
- self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
- def test_yamagawa_connection_swimming_quintar(self):
- self.collect_by_name([PROGRESSIVE_QUINTAR_WOODWIND])
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
-class TestSpawningMeadowsConnectionRulesWithLevelGating(CrystalProjectTestBase):
- options = {
- "level_gating": 1,
- "progressive_mount_mode": 0,
- "obscure_routes": 0
- }
- # Default Progressive Level Size: 6, 1 Progressive Level in player's starting inventory
- # Poko Poko Desert: 30
- def test_poko_poko_connection_fails_with_ibek_no_level_cap(self):
- self.collect_by_name(IBEK_BELL)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
-
- def test_poko_poko_connection_fails_with_level_cap_no_ibek(self):
- self.collect_progressive_levels(2)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
-
- def test_poko_poko_connection_succeeds_with_ibek_and_level_cap(self):
- self.collect_by_name(IBEK_BELL)
- self.collect_progressive_levels(3)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
-
- # Continental Tram
- def test_tram_connection_fails_with_obscure_routes_off(self):
- self.collect_mounts_and_progressive_levels_and_passes()
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + CONTINENTAL_TRAM_AP_REGION))
-
- # Beaurior Volcano: 37
- def test_beaurior_volcano_fails_with_ibek_no_level_cap(self):
- self.collect_by_name([IBEK_BELL])
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
-
- def test_beaurior_volcano_connection_fails_with_level_cap_no_ibek(self):
- self.collect_progressive_levels(1)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
-
- def test_beaurior_connection_succeeds_with_ibek_and_level_cap(self):
- self.collect_by_name(IBEK_BELL)
- self.collect_progressive_levels(5)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
-
- # Yamagawa M.A.: 15
- def test_yamagawa_connection_fails_with_mounts_no_level_cap(self):
- self.collect_mounts()
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
- def test_yamagawa_connection_fails_with_level_cap_no_mounts(self):
- self.collect_all_progressive_levels()
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
- def test_yamagawa_connection_succeeds_with_level_cap_and_ibek(self):
- self.collect_by_name([IBEK_BELL])
- self.collect_progressive_levels(1)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
- def test_yamagawa_connection_succeeds_with_level_cap_and_salmon(self):
- self.collect_by_name([PROGRESSIVE_SALMON_VIOLA])
- self.collect_progressive_levels(1)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
- self.collect_progressive_levels(1)
- self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
-
- def test_yamagawa_connection_succeeds_with_level_cap_and_quintar(self):
- self.collect_by_name([PROGRESSIVE_QUINTAR_WOODWIND])
- self.collect_progressive_levels(1)
- self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
- self.collect_progressive_levels(1)
+from ..constants.ap_regions import *
+from ..constants.mounts import *
+from .bases import CrystalProjectTestBase
+
+class TestSpawningMeadows(CrystalProjectTestBase):
+ def test_region_accessibility(self):
+ self.assertTrue(self.can_reach_region(SPAWNING_MEADOWS_AP_REGION))
+
+ def test_region_connections_no_items(self):
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions=(DELENDE_PLAINS_AP_REGION,), unreachable_regions=(MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION))
+
+class TestSpawningMeadowsObscureRoutes(CrystalProjectTestBase):
+ options = {
+ "level_gating": 0,
+ "progressive_mount_mode": 0,
+ "obscure_routes": 1
+ }
+
+ def test_obscure_routes(self):
+ unreachable_regions = (MERCURY_SHRINE_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
+ reachable_regions = (DELENDE_PLAINS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION,)
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
+
+ self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
+ unreachable_regions = (MERCURY_SHRINE_AP_REGION, BEAURIOR_VOLCANO_AP_REGION)
+ reachable_regions = (DELENDE_PLAINS_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, YAMAGAWA_MA_AP_REGION)
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
+
+class TestSpawningMeadowsNoObscureRoutes(CrystalProjectTestBase):
+ options = {
+ "level_gating": 0,
+ "progressive_mount_mode": 0,
+ "obscure_routes": 0
+ }
+
+ def test_obscure_routes(self):
+ unreachable_regions = (MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, CONTINENTAL_TRAM_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
+ reachable_regions = (DELENDE_PLAINS_AP_REGION,)
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
+
+ self.collect(self.get_item_by_name(IBEK_BELL))
+ unreachable_regions = (CONTINENTAL_TRAM_AP_REGION,)
+ reachable_regions = (DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
+
+ self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
+ unreachable_regions = (CONTINENTAL_TRAM_AP_REGION,)
+ reachable_regions = (DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
+
+ self.collect_mounts()
+ unreachable_regions = (CONTINENTAL_TRAM_AP_REGION,)
+ reachable_regions = (DELENDE_PLAINS_AP_REGION, MERCURY_SHRINE_AP_REGION, POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION, BEAURIOR_VOLCANO_AP_REGION, YAMAGAWA_MA_AP_REGION)
+ self.assert_region_entrances(SPAWNING_MEADOWS_AP_REGION, reachable_regions, unreachable_regions)
+
+class TestSpawningMeadowsConnectionRulesNoLevelGating(CrystalProjectTestBase):
+ options = {
+ "level_gating": 0,
+ "progressive_mount_mode": 0,
+ "obscure_routes": 0
+ }
+
+ def test_mercury_shrine_connection(self):
+ self.collect_by_name(IBEK_BELL)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + MERCURY_SHRINE_AP_REGION))
+
+ def test_poko_poko_connection(self):
+ self.collect_by_name(IBEK_BELL)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
+
+ def test_tram_connection(self):
+ self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + CONTINENTAL_TRAM_AP_REGION))
+
+ def test_volcano_connection(self):
+ self.collect_by_name(IBEK_BELL)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
+
+ def test_yamagawa_connection_vertical_movement(self):
+ self.collect_by_name(IBEK_BELL)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+ def test_yamagawa_connection_swimming_salmon(self):
+ self.collect_by_name(PROGRESSIVE_SALMON_VIOLA)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+ def test_yamagawa_connection_swimming_quintar(self):
+ self.collect_by_name([PROGRESSIVE_QUINTAR_WOODWIND])
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+class TestSpawningMeadowsConnectionRulesWithLevelGating(CrystalProjectTestBase):
+ options = {
+ "level_gating": 1,
+ "progressive_mount_mode": 0,
+ "obscure_routes": 0
+ }
+ # Default Progressive Level Size: 6, 1 Progressive Level in player's starting inventory
+ # Poko Poko Desert: 30
+ def test_poko_poko_connection_fails_with_ibek_no_level_cap(self):
+ self.collect_by_name(IBEK_BELL)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
+
+ def test_poko_poko_connection_fails_with_level_cap_no_ibek(self):
+ self.collect_progressive_levels(2)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
+
+ def test_poko_poko_connection_succeeds_with_ibek_and_level_cap(self):
+ self.collect_by_name(IBEK_BELL)
+ self.collect_progressive_levels(3)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + POKO_POKO_SPAWNING_MEADOWS_PASS_AP_REGION))
+
+ # Continental Tram
+ def test_tram_connection_fails_with_obscure_routes_off(self):
+ self.collect_mounts_and_progressive_levels_and_passes()
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + CONTINENTAL_TRAM_AP_REGION))
+
+ # Beaurior Volcano: 37
+ def test_beaurior_volcano_fails_with_ibek_no_level_cap(self):
+ self.collect_by_name([IBEK_BELL])
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
+
+ def test_beaurior_volcano_connection_fails_with_level_cap_no_ibek(self):
+ self.collect_progressive_levels(1)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
+
+ def test_beaurior_connection_succeeds_with_ibek_and_level_cap(self):
+ self.collect_by_name(IBEK_BELL)
+ self.collect_progressive_levels(5)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + BEAURIOR_VOLCANO_AP_REGION))
+
+ # Yamagawa M.A.: 15
+ def test_yamagawa_connection_fails_with_mounts_no_level_cap(self):
+ self.collect_mounts()
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+ def test_yamagawa_connection_fails_with_level_cap_no_mounts(self):
+ self.collect_all_progressive_levels()
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+ def test_yamagawa_connection_succeeds_with_level_cap_and_ibek(self):
+ self.collect_by_name([IBEK_BELL])
+ self.collect_progressive_levels(1)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+ def test_yamagawa_connection_succeeds_with_level_cap_and_salmon(self):
+ self.collect_by_name([PROGRESSIVE_SALMON_VIOLA])
+ self.collect_progressive_levels(1)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+ self.collect_progressive_levels(1)
+ self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+
+ def test_yamagawa_connection_succeeds_with_level_cap_and_quintar(self):
+ self.collect_by_name([PROGRESSIVE_QUINTAR_WOODWIND])
+ self.collect_progressive_levels(1)
+ self.assertFalse(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
+ self.collect_progressive_levels(1)
self.assertTrue(self.can_reach_entrance(SPAWNING_MEADOWS_AP_REGION + " -> " + YAMAGAWA_MA_AP_REGION))
\ No newline at end of file
diff --git a/worlds/crystal_project/unused_locations.py b/worlds/crystal_project/unused_locations.py
index 60c28149e34e..efcfde2f7fc2 100644
--- a/worlds/crystal_project/unused_locations.py
+++ b/worlds/crystal_project/unused_locations.py
@@ -1,35 +1,35 @@
-from typing import List
-from .locations import LocationData, npc_index_offset
-from .constants.ap_regions import *
-from .constants.display_regions import *
-
-def get_unused_locations() -> List[LocationData]:
- location_table: List[LocationData] = [
- LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Slow Nan walk", 4 + npc_index_offset),
- LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Master Fencer", 3573 + npc_index_offset),
- LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " NPC - Master Scholar", 3574 + npc_index_offset),
- LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - Master Shaman", 3572 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Beatsmith", 3560 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Cleric", 3568 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Monk", 3567 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Rogue", 3571 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Warlock", 3570 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Warrior", 3566 + npc_index_offset),
- LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Wizard", 3569 + npc_index_offset),
- LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Master Hunter", 3558 + npc_index_offset),
- LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Master Dervish", 3575 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Aegis", 3610 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Beastmaster", 3608 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Ninja", 3550 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Nomad", 3548 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Reaper", 3611 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Summoner", 3557 + npc_index_offset),
- LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Valkyrie", 3554 + npc_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Master Assassin", 3605 + npc_index_offset),
- LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Master Samurai", 3576 + npc_index_offset),
- LocationData(PAMOA_TREE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Master Chemist", 3707 + npc_index_offset),
- LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " NPC - Master Mimic", 3606 + npc_index_offset),
- LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Master Weaver", 3579 + npc_index_offset),
- ]
-
+from typing import List
+from .locations import LocationData, npc_index_offset
+from .constants.ap_regions import *
+from .constants.display_regions import *
+
+def get_unused_locations() -> List[LocationData]:
+ location_table: List[LocationData] = [
+ LocationData(SPAWNING_MEADOWS_AP_REGION, SPAWNING_MEADOWS_DISPLAY_NAME + " NPC - Slow Nan walk", 4 + npc_index_offset),
+ LocationData(DELENDE_HIGH_BRIDGES_AP_REGION, DELENDE_DISPLAY_NAME + " NPC - Master Fencer", 3573 + npc_index_offset),
+ LocationData(YAMAGAWA_MA_AP_REGION, YAMAGAWA_MA_DISPLAY_NAME + " NPC - Master Scholar", 3574 + npc_index_offset),
+ LocationData(SEASIDE_CLIFFS_AP_REGION, SEASIDE_CLIFFS_DISPLAY_NAME + " NPC - Master Shaman", 3572 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Beatsmith", 3560 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Cleric", 3568 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Monk", 3567 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Rogue", 3571 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Warlock", 3570 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Warrior", 3566 + npc_index_offset),
+ LocationData(CAPITAL_SEQUOIA_AP_REGION, CAPITAL_SEQUOIA_DISPLAY_NAME + " NPC - Master Wizard", 3569 + npc_index_offset),
+ LocationData(ROLLING_QUINTAR_FIELDS_AP_REGION, ROLLING_QUINTAR_FIELDS_DISPLAY_NAME + " NPC - Master Hunter", 3558 + npc_index_offset),
+ LocationData(SARA_SARA_BEACH_WEST_AP_REGION, SARA_SARA_BEACH_WEST_DISPLAY_NAME + " NPC - Master Dervish", 3575 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Aegis", 3610 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Beastmaster", 3608 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Ninja", 3550 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Nomad", 3548 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Reaper", 3611 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Summoner", 3557 + npc_index_offset),
+ LocationData(SUMMONERS_TOWER_AP_REGION, NORTHERN_STRETCH_DISPLAY_NAME + " NPC - Master Valkyrie", 3554 + npc_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Master Assassin", 3605 + npc_index_offset),
+ LocationData(SHOUDU_PROVINCE_AP_REGION, SHOUDU_PROVINCE_DISPLAY_NAME + " NPC - Master Samurai", 3576 + npc_index_offset),
+ LocationData(PAMOA_TREE_AP_REGION, TALL_TALL_HEIGHTS_DISPLAY_NAME + " NPC - Master Chemist", 3707 + npc_index_offset),
+ LocationData(THE_CHALICE_OF_TAR_AP_REGION, THE_CHALICE_OF_TAR_DISPLAY_NAME + " NPC - Master Mimic", 3606 + npc_index_offset),
+ LocationData(JIDAMBA_FOREST_FLOOR_AP_REGION, JIDAMBA_TANGLE_DISPLAY_NAME + " NPC - Master Weaver", 3579 + npc_index_offset),
+ ]
+
return location_table
\ No newline at end of file
diff --git a/worlds/cv64/aesthetics.py b/worlds/cv64/aesthetics.py
index 66709174d837..5bfe94368498 100644
--- a/worlds/cv64/aesthetics.py
+++ b/worlds/cv64/aesthetics.py
@@ -214,7 +214,7 @@
"\"Banshee Boomerang.\"",
0x10: "No weapon triangle\n"
"advantages with this.",
- 0x12: "It looks sus? Trust me,"
+ 0x12: "It looks sus? Trust me,\n"
"my wares are genuine.",
0x15: "This non-volatile kind\n"
"is safe to handle.",
diff --git a/worlds/cv64/data/patches.py b/worlds/cv64/data/patches.py
index 4a964612f03e..160c9c73e1f1 100644
--- a/worlds/cv64/data/patches.py
+++ b/worlds/cv64/data/patches.py
@@ -2034,7 +2034,7 @@
0x24090000, # ADDIU T1, R0, 0x0000 <- Starting Ice Traps
0xA1099BE2, # SB T1, 0x9BE2 (T0)
0x240C0000, # ADDIU T4, R0, 0x0000
- 0x240D0022, # ADDIU T5, R0, 0x0022
+ 0x240D0023, # ADDIU T5, R0, 0x0023
0x11AC0007, # BEQ T5, T4, [forward 0x07]
0x3C0A8040, # LUI T2, 0x8040
0x014C5021, # ADDU T2, T2, T4
diff --git a/worlds/cv64/docs/setup_en.md b/worlds/cv64/docs/setup_en.md
index 707618a1eba5..51a91bbad15a 100644
--- a/worlds/cv64/docs/setup_en.md
+++ b/worlds/cv64/docs/setup_en.md
@@ -30,7 +30,7 @@ the White Jewels.
1. Create your options file (YAML). You can make one on the
[Castlevania 64 options page](../../../games/Castlevania%2064/player-options).
-2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game).
+2. Follow the general Archipelago instructions for [generating a game](/tutorial/Archipelago/setup_en#generating-a-game).
This will generate an output file for you. Your patch file will have the `.apcv64` file extension.
3. Open `ArchipelagoLauncher.exe`
4. Select "Open Patch" on the left side and select your patch file.
diff --git a/worlds/cv64/rom.py b/worlds/cv64/rom.py
index 2e56df394d5a..4c31dc148a35 100644
--- a/worlds/cv64/rom.py
+++ b/worlds/cv64/rom.py
@@ -1030,7 +1030,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if CV64_US_10_HASH != basemd5.hexdigest():
- raise Exception("Supplied Base Rom does not match known MD5 for Castlevania 64 US 1.0."
+ raise Exception("Supplied Base Rom does not match known MD5 for Castlevania 64 US 1.0. "
"Get the correct game and version, then dump it.")
setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes)
return base_rom_bytes
diff --git a/worlds/cvcotm/client.py b/worlds/cvcotm/client.py
index 36019cd60ecc..74a853bbaacf 100644
--- a/worlds/cvcotm/client.py
+++ b/worlds/cvcotm/client.py
@@ -247,6 +247,10 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
await bizhawk.write(ctx.bizhawk_ctx, [(QUEUED_TEXTBOX_1_ADDRESS, [0 for _ in range(12)], "EWRAM")])
return
+ # If the player doesn't have Dash Boots for whatever reason, put them in their inventory now.
+ if not magic_items_array[0]:
+ await bizhawk.write(ctx.bizhawk_ctx, [(MAGIC_ITEMS_ARRAY_START, [1], "EWRAM")])
+
# Enable DeathLink if it's in our slot_data.
if "DeathLink" not in ctx.tags and ctx.slot_data["death_link"]:
await ctx.update_death_link(True)
diff --git a/worlds/cvcotm/docs/setup_en.md b/worlds/cvcotm/docs/setup_en.md
index 4c34dcb836ef..f968ee01a537 100644
--- a/worlds/cvcotm/docs/setup_en.md
+++ b/worlds/cvcotm/docs/setup_en.md
@@ -28,7 +28,7 @@ clear it.
## Generating and Patching a Game
1. Create your settings file (YAML). You can make one on the [Castlevania: Circle of the Moon options page](../../../games/Castlevania%20-%20Circle%20of%20the%20Moon/player-options).
-2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game).
+2. Follow the general Archipelago instructions for [generating a game](/tutorial/Archipelago/setup_en#generating-a-game).
This will generate an output file for you. Your patch file will have the `.apcvcotm` file extension.
3. Open `ArchipelagoLauncher.exe`.
4. Select "Open Patch" on the left side and select your patch file.
diff --git a/worlds/cvcotm/rom.py b/worlds/cvcotm/rom.py
index 350829292b34..a1b41ba31dfe 100644
--- a/worlds/cvcotm/rom.py
+++ b/worlds/cvcotm/rom.py
@@ -586,7 +586,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
basemd5.update(base_rom_bytes)
# if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]:
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]:
- raise Exception("Supplied Base ROM does not match known MD5s for Castlevania: Circle of the Moon USA."
+ raise Exception("Supplied Base ROM does not match known MD5s for Castlevania: Circle of the Moon USA. "
"Get the correct game and version, then dump it.")
setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes)
return base_rom_bytes
diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py
index a1ad2f6a7066..8c9716c03d5a 100644
--- a/worlds/dark_souls_3/__init__.py
+++ b/worlds/dark_souls_3/__init__.py
@@ -90,6 +90,22 @@ def generate_early(self) -> None:
self.created_regions = set()
self.all_excluded_locations.update(self.options.exclude_locations.value)
+ # This code doesn't work because tests don't verify options
+ # Don't consider disabled locations to be AP-excluded
+ # if not self.options.enable_dlc:
+ # self.options.exclude_locations.value = {
+ # location
+ # for location in self.options.exclude_locations
+ # if not location_dictionary[location].dlc
+ # }
+
+ # if not self.options.enable_ngp:
+ # self.options.exclude_locations.value = {
+ # location for
+ # location in self.options.exclude_locations
+ # if not location_dictionary[location].ngp
+ # }
+
# 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:
@@ -264,6 +280,13 @@ def create_region(self, region_name, location_table) -> Region:
):
new_location.progress_type = LocationProgressType.EXCLUDED
else:
+ # Don't consider non-randomized locations to be AP-excluded
+ 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)
+
# Don't allow missable duplicates of progression items to be expected progression.
if location.name in self.missable_dupe_prog_locs: continue
@@ -283,11 +306,6 @@ def create_region(self, region_name, location_table) -> Region:
parent = new_region,
)
new_location.place_locked_item(event_item)
- 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)
@@ -1357,7 +1375,7 @@ def write_spoiler(self, spoiler_handle: TextIO) -> None:
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":
+ if self.options.excluded_location_behavior != "forbid_useful":
text += f"\n{self.player_name}'s world excluded: {sorted(self.all_excluded_locations)}\n"
if text:
diff --git a/worlds/dc1/Items.py b/worlds/dc1/Items.py
index 8182d740c3e8..cad7c2ed9cba 100644
--- a/worlds/dc1/Items.py
+++ b/worlds/dc1/Items.py
@@ -14,6 +14,7 @@
progressive_item_list[progressiveName].append(prog_item)
class DarkCloudItem(Item):
+ # type = None
game: str = dc1_name
def __init__(self, name: str,
diff --git a/worlds/dc1/Location.py b/worlds/dc1/Location.py
index 17972e2f935e..e2ad9dfa6a4c 100644
--- a/worlds/dc1/Location.py
+++ b/worlds/dc1/Location.py
@@ -9,4 +9,3 @@ def __init__(self, player, name, address, loc_type, region: Region, access=None,
super(DarkCloudLocation, self).__init__(player, name, address)
self.type = loc_type
self.parent_region = region
- self.access = access
diff --git a/worlds/dc1/Rules.py b/worlds/dc1/Rules.py
index 75c5c6772479..0043d370647f 100644
--- a/worlds/dc1/Rules.py
+++ b/worlds/dc1/Rules.py
@@ -3,7 +3,6 @@
class RuleManager:
def xiao_available(self, state: CollectionState, player: int) -> bool:
- #TODO might be able to remove the collect_item implementation using state.count("Progressive...")?
return state.has("Stray Cat", player) and state.has("Gaffer's Lamp", player) and state.has("Pike", player)
def dran_accessible(self, state: CollectionState, player: int) -> bool:
@@ -11,7 +10,7 @@ def dran_accessible(self, state: CollectionState, player: int) -> bool:
self.xiao_available(state, player)
def goro_available(self, state: CollectionState, player: int) -> bool:
- return state.has("Matataki River E", player) and state.has("Cacao's Laundry", player) and \
+ return state.has("Matataki River H", player) and state.has("Cacao's Laundry", player) and \
self.xiao_available(state, player)
def utan_accessible(self, state: CollectionState, player: int) -> bool:
diff --git a/worlds/dc1/__init__.py b/worlds/dc1/__init__.py
index 69e1ef49d2ac..50aa5b765d5e 100644
--- a/worlds/dc1/__init__.py
+++ b/worlds/dc1/__init__.py
@@ -46,15 +46,11 @@ class DarkCloudWorld(World):
for i in dungeon_locations:
location_name_to_id.update(i)
- geo_items = []
-
- origin_region_name = "Norune" # Not sure if this should change?
-
+ origin_region_name = "Norune"
+
def generate_early(self) -> None:
for i in range(self.options.boss_goal):
- self.geo_items.extend(geo_funcs[i](self.options, self.player))
-
- self.multiworld.itempool.extend(self.geo_items)
+ self.multiworld.itempool.extend(geo_funcs[i](self.options, self.player))
def create_items(self):
pass
diff --git a/worlds/dc1/archipelago.json b/worlds/dc1/archipelago.json
index d6ad0c8490cf..dceee385d581 100644
--- a/worlds/dc1/archipelago.json
+++ b/worlds/dc1/archipelago.json
@@ -1,8 +1,8 @@
-{
- "game": "Dark Cloud 1",
- "version": 7,
- "compatible_version": 7,
- "minimum_ap_version": "0.6.1",
- "world_version": "0.3.0",
- "authors": ["Lizardman175"]
+{
+ "game": "Dark Cloud 1",
+ "version": 7,
+ "compatible_version": 7,
+ "minimum_ap_version": "0.6.1",
+ "world_version": "0.3.0",
+ "authors": ["Lizardman175"]
}
\ No newline at end of file
diff --git a/worlds/dc1/data/FactoryGeoItems.py b/worlds/dc1/data/FactoryGeoItems.py
index 71ba0e194832..0bf2e6fabad1 100644
--- a/worlds/dc1/data/FactoryGeoItems.py
+++ b/worlds/dc1/data/FactoryGeoItems.py
@@ -52,7 +52,7 @@ def create_factory_atla(options: DarkCloudOptions, player: int) -> List["DarkClo
factory_required = []
factory_useful = []
- factory_filler = filler_ids
+ factory_filler = filler_ids.copy()
if options.boss_goal == 5 or options.all_bosses:
factory_required.extend(head_ids + ["Progressive Parts HD"])
@@ -81,4 +81,5 @@ def create_factory_atla(options: DarkCloudOptions, player: int) -> List["DarkClo
# print(len(items))
# print (items)
- return items
\ No newline at end of file
+ return items
+
diff --git a/worlds/dc1/data/MatatakiGeoItems.py b/worlds/dc1/data/MatatakiGeoItems.py
index d57a572767ce..4647bfe3d9ef 100644
--- a/worlds/dc1/data/MatatakiGeoItems.py
+++ b/worlds/dc1/data/MatatakiGeoItems.py
@@ -69,7 +69,6 @@
other_ids = ["Matataki Trees", "Matataki Trees", "Matataki Bridge"]
-# TODO split these lists into 2 based on items that spawn from the first half of a dungeon or the second
# Atla that give MCs by content quality (unless handled otherwise). If MC shuffle is on, these all need to be required
mc_useful = ["Progressive Owl Shop", "Progressive Owl Shop",
"Progressive Bunbuku's House",
@@ -80,7 +79,6 @@
"Progressive Couscous's House", "Progressive Gob's House",
"Progressive Watermill 2", "Progressive Watermill 3", ]
mc_filler_2 = ["Progressive Bunbuku's House",]
-
# Always required/useful/filler items
required = river_ids + cacao_ids
useful = pao_ids + baron_ids + gob_ids + owl_ids
@@ -92,36 +90,36 @@ def create_matataki_atla(options: DarkCloudOptions, player: int) -> list["DarkCl
items = []
- matataki_progression = required
- matataki_useful = useful
- matataki_filler = filler
+ matataki_required = required.copy()
+ matataki_useful = useful.copy()
+ matataki_filler = filler.copy()
# Mush house is only full required if Utan is required
if options.all_bosses or options.boss_goal == 2:
- matataki_progression.extend(mush_ids)
- matataki_progression.extend(mush_ids_mc)
- matataki_progression.extend(mush_ids_mc2)
+ matataki_required.extend(mush_ids)
+ matataki_required.extend(mush_ids_mc)
+ matataki_required.extend(mush_ids_mc2)
else:
matataki_useful.extend(mush_ids)
if options.miracle_sanity:
- matataki_progression.extend(mush_ids_mc)
- matataki_progression.extend(mush_ids_mc2)
+ matataki_required.extend(mush_ids_mc)
+ matataki_required.extend(mush_ids_mc2)
else:
matataki_useful.extend(mush_ids_mc)
matataki_useful.extend(mush_ids_mc2)
if options.miracle_sanity:
- matataki_progression.extend(mc_useful)
- matataki_progression.extend(mc_useful_2)
- matataki_progression.extend(mc_filler)
- matataki_progression.extend(mc_filler_2)
+ matataki_required.extend(mc_useful)
+ matataki_required.extend(mc_useful_2)
+ matataki_required.extend(mc_filler)
+ matataki_required.extend(mc_filler_2)
else:
matataki_useful.extend(mc_useful)
matataki_useful.extend(mc_useful_2)
matataki_filler.extend(mc_filler)
matataki_filler.extend(mc_filler_2)
- for i in matataki_progression:
+ for i in matataki_required:
items.append(DarkCloudItem(i, ItemClassification.progression, ids[i], player))
for i in matataki_useful:
@@ -130,6 +128,4 @@ def create_matataki_atla(options: DarkCloudOptions, player: int) -> list["DarkCl
for i in matataki_filler:
items.append(DarkCloudItem(i, ItemClassification.filler, ids[i], player))
- # print(len(items))
- # print (items)
- return items
\ No newline at end of file
+ return items
diff --git a/worlds/dc1/data/MuskaGeoItems.py b/worlds/dc1/data/MuskaGeoItems.py
index b206a0e20651..28c2ffd60a13 100644
--- a/worlds/dc1/data/MuskaGeoItems.py
+++ b/worlds/dc1/data/MuskaGeoItems.py
@@ -63,10 +63,10 @@
def create_muska_atla(options: DarkCloudOptions, player: int) -> List["DarkCloudItem"]:
items = []
-
- muska_required = required + mc_required
- muska_useful = useful
- muska_filler = filler
+
+ muska_required = required.copy() + mc_required.copy()
+ muska_useful = useful.copy()
+ muska_filler = filler.copy()
if options.boss_goal == 4 or options.all_bosses:
muska_required.extend(chief_ids + zabo_ids + enga_ids + ["Progressive Zabo's House"])
@@ -97,4 +97,5 @@ def create_muska_atla(options: DarkCloudOptions, player: int) -> List["DarkCloud
# print(len(items))
# print (items)
- return items
\ No newline at end of file
+ return items
+
diff --git a/worlds/dc1/data/NoruneGeoItems.py b/worlds/dc1/data/NoruneGeoItems.py
index 6dbca4386924..9027c6f44af4 100644
--- a/worlds/dc1/data/NoruneGeoItems.py
+++ b/worlds/dc1/data/NoruneGeoItems.py
@@ -1,7 +1,3 @@
-# TODO for miracle chests, split out only necessary items to be required rather than the whole building.
-# Ex lamps don't yield miracle chests, but a cabin addition might
-# Grouping of item IDs per building for Norune Village
-
from BaseClasses import ItemClassification
from worlds.dc1.Items import DarkCloudItem
from worlds.dc1.Options import DarkCloudOptions
@@ -67,7 +63,6 @@
other_ids = ["Norune Trees", "Norune Trees", "Norune Bridge", "Norune Road", "Norune Road", "Norune Road",
"Norune Road", "Norune Road", "Norune River", "Norune River", "Norune River", "Norune River"]
-# TODO split these lists into 2 based on items that spawn from the first half of a dungeon or the second
# Atla that give MCs by content quality (unless handled otherwise). If MC shuffle is on, these all need to be required
mc_useful = []
mc_filler = ["Progressive Macho's House", "Progressive Macho's House", "Progressive Claude's House",
@@ -91,9 +86,9 @@ def create_norune_atla(options: DarkCloudOptions, player: int) -> list["DarkClou
"""Create atla items for Norune Village based on option settings."""
items = []
- norune_progression = required
- norune_useful = useful
- norune_filler = filler
+ norune_progression = required.copy()
+ norune_useful = useful.copy()
+ norune_filler = filler.copy()
# Dran's windmill is only full required if Dran is required
if options.all_bosses:
diff --git a/worlds/dc1/data/QueensGeoItems.py b/worlds/dc1/data/QueensGeoItems.py
index 6b8f306c77d1..8d15d63ac32b 100644
--- a/worlds/dc1/data/QueensGeoItems.py
+++ b/worlds/dc1/data/QueensGeoItems.py
@@ -66,9 +66,9 @@ def create_queens_atla(options: DarkCloudOptions, player: int) -> list["DarkClou
"""Create atla items for Norune Village based on option settings."""
items = []
- queens_required = required
- queens_useful = useful
- queens_filler = filler
+ queens_required = required.copy()
+ queens_useful = useful.copy()
+ queens_filler = filler.copy()
if options.boss_goal == 3 or options.all_bosses:
queens_required.extend(["Progressive Divining House", "Progressive Divining House", "Progressive Divining House", "Progressive Divining House"])
diff --git a/worlds/dc1/data/atla_locations.json b/worlds/dc1/data/atla_locations.json
index 1aaacd2e25e8..dfb2a68e8613 100644
--- a/worlds/dc1/data/atla_locations.json
+++ b/worlds/dc1/data/atla_locations.json
@@ -464,4 +464,4 @@
"GoT Atla 61": 971116161,
"GoT Atla 62": 971116162
}
-]
\ No newline at end of file
+]
diff --git a/worlds/dc1/data/items.json b/worlds/dc1/data/items.json
deleted file mode 100644
index b36c428cce25..000000000000
--- a/worlds/dc1/data/items.json
+++ /dev/null
@@ -1,251 +0,0 @@
-{
- "Fire": 81,
- "Ice": 82,
- "Thunder": 83,
- "Wind": 84,
- "Holy": 85,
- "Attack": 91,
- "Endurance": 92,
- "Speed": 93,
- "Magic": 94,
- "Garnet": 95,
- "Amethyst": 96,
- "Aquamarine": 97,
- "Diamond": 98,
- "Emerald": 99,
- "Pearl": 100,
- "Ruby": 101,
- "Peridot": 102,
- "Sapphire": 103,
- "Opal": 104,
- "Topaz": 105,
- "Turquoise": 106,
- "Sun": 107,
- "Dragon Slayer": 111,
- "Undead Buster": 112,
- "Sea Killer": 113,
- "Stone Breaker": 114,
- "Plant Buster": 115,
- "Beast Buster": 116,
- "Sky Hunter": 117,
- "Metal Breaker": 118,
- "Mimic Breaker": 119,
- "Mage Slayer": 120,
-
-
-
- "Antifreeze Amulet": 132,
- "Anticurse Amulet": 133,
- "Antigoo Amulet": 134,
- "Antidote Amulet": 135,
- "Fluffy Doughnut": 136,
- "Fish Candy": 137,
- "Grasscake": 138,
- "Witch Parfait": 139,
- "Scorpion Jerky": 140,
- "Carrot Cookie": 141,
- "Regular Water": 145,
- "Tasty Water": 146,
- "Premium Water": 147,
- "Bread": 148,
- "Premium Chicken": 149,
- "Stamina Drink": 150,
- "Antidote Drink": 151,
- "Holy Water": 152,
- "Soap": 153,
- "Mighty Healing": 154,
- "Cheese": 155,
- "Bomb": 159,
- "Stone": 160,
- "Fire Gem": 161,
- "Ice Gem": 162,
- "Thunder Gem": 163,
- "Wind gem": 164,
- "Holy gem": 165,
- "Throbbing Cherry": 166,
- "Gooey Peach": 167,
- "Bomb Nuts": 168,
- "Poisonous Apple": 169,
- "Mellow Banana": 170,
- "Stand-in Powder": 174,
- "Escape Powder": 175,
- "Revival Powder": 176,
- "Repair Powder": 177,
- "Powerup Powder": 178,
- "Pocket": 179,
- "Fruit of Eden": 180,
- "Treasure Chest Key": 181,
- "Gourd": 182,
- "Auto-repair Powder": 183,
- "Fishing Rod": 185,
- "Carrot": 186,
- "Potato Cake": 187,
- "Minon": 188,
- "Battan": 189,
- "Petite Fish": 190,
- "Gold Bullion": 192,
- "Evy": 193,
- "Mimi": 197,
- "Prickly": 199,
- "Candy": 200,
- "Horned Key": 207,
- "Moongrass Seed": 208,
- "Musicbox Key": 209,
- "Sun Signet": 210,
- "Moon Signet": 211,
- "Admission Ticket": 212,
- "Bone Key": 216,
- "Mustache Key": 217,
- "Ship Cabin Key": 218,
- "Stone Key": 219,
- "Handle": 220,
- "Pitch Dark Key": 221,
- "Silver Key": 222,
- "Tram Oil": 224,
- "Sundew": 225,
- "Flapping Fish": 226,
- "Rotten Fish": 227,
- "Secret Path Key": 228,
- "Bravery Launch": 229,
- "Flapping Duster": 230,
- "Crystal Eyeball": 231,
- "Map": 233,
- "Magical Crystal": 234,
- "Dran's Feather": 235,
- "Cave Key": 236,
- "Changing Potion": 237,
- "Worldmap": 238,
- "Bone Pendant": 239,
- "Oddtone Flute": 240,
- "Magical Lamp": 241,
- "Moon Orb": 242,
- "Shellring": 243,
- "Search Warrant": 244,
- "Iceblock": 245,
- "Small Ice": 246,
- "Tiny Ice": 247,
- "Hunter's Earring": 249,
- "Ointment Leaf": 250,
- "Foundation": 251,
- "Clay Doll": 252,
- "Manual": 253,
- "Sunsphere": 254,
-
-
-
- "Broken Dagger": 257,
- "Dagger": 258,
- "Baselard": 259,
- "Gladius": 260,
- "Wise Owl Sword": 261,
- "Crystal Knife": 262,
- "Antique Sword": 263,
- "Buster Sword": 264,
- "Kitchen Knife": 265,
- "Tsukikage": 266,
- "Sun Sword": 267,
- "Serpent Sword": 268,
- "Macho Sword": 269,
- "shamshir": 270,
- "Heaven's Cloud": 271,
- "Lamb's Sword": 272,
- "Dark Cloud": 273,
- "Brave Ark": 274,
- "Big Bang": 275,
- "Atlamillia Sword": 276,
- "Lamb's Sword Transformed": 277,
- "Mardan Eins": 278,
- "Mardan Twei": 279,
- "Arise Mardan": 280,
- "Aga's Sword": 281,
- "Evilcise": 282,
- "Small Sword": 283,
- "Sand Breaker": 284,
- "Drain Seeker": 285,
- "Chopper": 286,
- "Choora": 287,
- "Claymore": 288,
- "Maneater": 289,
- "Bone Rapier": 290,
- "Sax": 291,
- "Seven Branch Sword": 292,
- "Dusack": 293,
- "Cross Hinder": 294,
- "Seventh Heaven": 295,
- "Sword of Zeus": 296,
- "Chronicle Sword": 297,
- "Chronicle 2": 298,
-
- "Broken Wooden Slingshot": 299,
- "Wooden Slingshot": 300,
- "Steel Slingshot": 301,
- "Bandit Slingshot": 302,
- "Steve": 303,
- "Bone Slingshot": 304,
- "Hard Shooter": 305,
- "Double Impact": 306,
- "Dragon's Y": 307,
- "Divine Beast Title": 308,
- "Angel Shooter": 309,
- "Flamingo": 310,
- "Matador": 311,
- "Super Steve": 312,
- "Angel Gear": 313,
-
- "Broken Mallet": 314,
- "Mallet": 315,
- "Steel Hammer": 316,
- "Magical Hammer": 317,
- "Battleaxe": 318,
- "Turtle Shell": 319,
- "Big Bucks Hammer": 320,
- "Frozen Tuna": 321,
- "Gaia Hammer": 322,
- "Last Judgement": 323,
- "Tall Hammer": 324,
- "Satan's Axe": 325,
- "Plate hammer": 327,
- "Trial Hammer": 328,
- "Inferno": 329,
-
- "Broken Gold Ring": 331,
- "Gold Ring": 332,
- "Bandit's Ring": 333,
- "Crystal Ring": 334,
- "Platinum Ring": 335,
- "Goddess Ring": 336,
- "Fairy's Ring": 337,
- "Destruction Ring": 338,
- "Satan's Ring": 339,
- "Athenas Armlet": 340,
- "Mobius Ring": 341,
- "Pocklekul": 343,
- "Thorn Armlet": 344,
- "Secret Armlet": 345,
-
- "Broken Fighting Stick": 347,
- "Fighting Stick": 348,
- "Javelin": 349,
- "Halberd": 350,
- "Desanga": 351,
- "Scorpion": 352,
- "Partisan": 353,
- "Mirage": 354,
- "Terra Sword": 355,
- "Hercules' Wrath": 356,
- "Babel's Spear": 357,
- "Five Foot Nail": 359,
- "Cactus": 360,
-
- "Broken Machine Gun": 363,
- "Machine Gun": 364,
- "Jackal": 365,
- "Blessing Gun": 368,
- "Skunk": 369,
- "G Crusher": 370,
- "Hexa Blaster": 371,
- "Star Breaker": 372,
- "Supernova": 373,
- "Snail": 374,
- "Swallow": 375
-}
\ No newline at end of file
diff --git a/worlds/dc1/data/progressive - Copy.json b/worlds/dc1/data/progressive - Copy.json
deleted file mode 100644
index 4839d40dfd98..000000000000
--- a/worlds/dc1/data/progressive - Copy.json
+++ /dev/null
@@ -1,229 +0,0 @@
-{
- "Player's House": "Progressive Player's House",
- "Player's Storage": "Progressive Player's House",
- "Renee": "Progressive Player's House",
- "Player's Llama": "Progressive Player's House",
- "Player's Keg": "Progressive Player's House",
- "Player's Chimney": "Progressive Player's House",
- "Stray Cat": "Progressive Player's House",
-
- "Macho's House": "Progressive Macho's House",
- "Macho's Annex": "Progressive Macho's House",
- "Macho": "Progressive Macho's House",
- "Komacho": "Progressive Macho's House",
- "Macho's Fence": "Progressive Macho's House",
- "Macho's Lamp": "Progressive Macho's House",
- "Barbell": "Progressive Macho's House",
-
- "Laura's House": "Progressive Laura's House",
- "Laura's Cabin": "Progressive Laura's House",
- "Laura": "Progressive Laura's House",
- "Gina": "Progressive Laura's House",
- "Laura's Fence": "Progressive Laura's House",
- "Laura's Lamp": "Progressive Laura's House",
- "Tricycle": "Progressive Laura's House",
-
- "Paige's House": "Progressive Paige's House",
- "Pike": "Progressive Paige's House",
- "Paige's Cabin": "Progressive Paige's House",
- "Paige": "Progressive Paige's House",
- "Paige's Fence": "Progressive Paige's House",
- "Paige's Lamp": "Progressive Paige's House",
- "Wheels": "Progressive Paige's House",
-
- "Claude's House": "Progressive Claude's House",
- "Claude's Cabin": "Progressive Claude's House",
- "Candy Box": "Progressive Claude's House",
- "Claude": "Progressive Claude's House",
- "Claude's Fence": "Progressive Claude's House",
- "Claude's Lamp": "Progressive Claude's House",
- "Claude's Bench": "Progressive Claude's House",
-
- "Hag's House": "Progressive Hag's House",
- "Hag's Cabin": "Progressive Hag's House",
- "Hags' Jar": "Progressive Hag's House",
- "Hag": "Progressive Hag's House",
- "Hag's Bench": "Progressive Hag's House",
- "Hag's Lamp": "Progressive Hag's House",
- "Hag's Fence": "Progressive Hag's House",
-
- "Alnet's House": "Progressive Alnet's House",
- "Alnet's Stairway": "Progressive Alnet's House",
- "Alnet's Cabin": "Progressive Alnet's House",
- "Alnet": "Progressive Alnet's House",
- "Carl": "Progressive Alnet's House",
- "Alnet's Llama": "Progressive Alnet's House",
- "Alnet's Lamp": "Progressive Alnet's House",
-
- "Gaffer's Buggy": "Progressive Gaffer's Buggy",
- "Gaffer": "Progressive Gaffer's Buggy",
- "Supplies": "Progressive Gaffer's Buggy",
- "Gaffer's Sign": "Progressive Gaffer's Buggy",
- "Gaffer's Lamp": "Progressive Gaffer's Buggy",
-
- "Dran's Windmill": "Progressive Dran's Windmill",
- "Dran's Blade": "Progressive Dran's Windmill",
- "Dran's Torch": "Progressive Dran's Windmill",
- "Dran's Horn": "Progressive Dran's Windmill",
- "Dran's Sign": "Progressive Dran's Windmill",
-
-
-
- "Pao's House": "Progressive Pao's House",
- "Pao's Stairway": "Progressive Pao's House",
- "Pao": "Progressive Pao's House",
- "Pao's Torch": "Progressive Pao's House",
- "Pao's Sign": "Progressive Pao's House",
- "Pao's Laundry": "Progressive Pao's House",
-
- "Cacao's House": "Progressive Cacao's House",
- "Cacao's Stairway": "Progressive Cacao's House",
- "Cacao's Cabin": "Progressive Cacao's House",
- "Cacao": "Progressive Cacao's House",
- "Cacao's Torch": "Progressive Cacao's House",
- "Cacao's Sign": "Progressive Cacao's House",
- "Cacao's Laundry": "Progressive Cacao's House",
-
- "Bunbuku's House": "Progressive Bunbuku's House",
- "Bunbuku's Stairway": "Progressive Bunbuku's House",
- "Bunbuku's Cabin": "Progressive Bunbuku's House",
- "Bunbuku": "Progressive Bunbuku's House",
- "Kululu": "Progressive Bunbuku's House",
- "Bunbuku's Torch": "Progressive Bunbuku's House",
- "Bunbuku's Sign": "Progressive Bunbuku's House",
-
- "Kye's House": "Progressive Kye's House",
- "Kye's Stairway": "Progressive Kye's House",
- "Kye's Cabin": "Progressive Kye's House",
- "Kye": "Progressive Kye's House",
- "Momo": "Progressive Kye's House",
- "Kye's Torch": "Progressive Kye's House",
- "Kye's Sign": "Progressive Kye's House",
-
- "Baron's House": "Progressive Baron's House",
- "Baron": "Progressive Baron's House",
- "Baron's Torch": "Progressive Baron's House",
- "Baron's Sign": "Progressive Baron's House",
- "Baron's Roof": "Progressive Baron's House",
- "Baron's Branch": "Progressive Baron's House",
-
- "Couscous's House": "Progressive Couscous's House",
- "Couscous": "Progressive Couscous's House",
- "Couscous's Torch": "Progressive Couscous's House",
- "Couscous's Sign": "Progressive Couscous's House",
- "Couscous's Grass": "Progressive Couscous's House",
-
- "Gob's House": "Progressive Gob's House",
- "Gob": "Progressive Gob's House",
- "Gob's Torch": "Progressive Gob's House",
- "Gob's Sign": "Progressive Gob's House",
- "Gob's Bone": "Progressive Gob's House",
- "Gob's Tree": "Progressive Gob's House",
-
- "Mushroom House": "Progressive Mushroom House",
- "Second Floor": "Progressive Mushroom House",
- "Ro": "Progressive Mushroom House",
- "Annie": "Progressive Mushroom House",
- "Mushroom Torch": "Progressive Mushroom House",
- "Mushroom Sign": "Progressive Mushroom House",
- "Balcony": "Progressive Mushroom House",
-
- "Wise Owl Shop": "Progressive Owl Shop",
- "Owl Shop Entrance": "Progressive Owl Shop",
- "Mr. Moustache": "Progressive Owl Shop",
- "Wise Owl Torch": "Progressive Owl Shop",
- "Wise Owl Sign": "Progressive Owl Shop",
- "Wise Owl Entrance": "Progressive Owl Shop",
-
- "Watermill 1": "Progressive Watermill 1",
- "Watermill Torch 1": "Progressive Watermill 1",
- "Waterwheel 1": "Progressive Watermill 1",
-
- "Watermill 2": "Progressive Watermill 2",
- "Watermill Torch 2": "Progressive Watermill 2",
- "Waterwheel 2": "Progressive Watermill 2",
-
- "Watermill 3": "Progressive Watermill 3",
- "Watermill Torch 3": "Progressive Watermill 3",
- "Waterwheel 3": "Progressive Watermill 3",
-
- "Matataki River A": "Matataki River",
- "Matataki River B": "Matataki River",
- "Matataki River C": "Matataki River",
- "Matataki River D": "Matataki River",
- "Matataki River E": "Matataki River",
- "Matataki River F": "Matataki River",
- "Matataki River G": "Matataki River",
- "Matataki River H": "Matataki River",
-
-
-
- "Ruty's Store": "Progressive Ruty's Store",
- "Ruty": "Progressive Ruty's Store",
- "Pushcart (Fish) 1": "Progressive Ruty's Store",
- "Pushcart (Fish) 2": "Progressive Ruty's Store",
- "Pushcart (Fish) 3": "Progressive Ruty's Store",
-
- "Suzy's Store": "Progressive Suzy's Store",
- "Suzy's Pushcart": "Progressive Suzy's Store",
- "Suzy's Keg": "Progressive Suzy's Store",
- "Suzy": "Progressive Suzy's Store",
- "Suzy's Urn": "Progressive Suzy's Store",
- "Suzy's Lamp": "Progressive Suzy's Store",
-
- "Lana's Store": "Progressive Lana's Store",
- "Pushcart (Fruit) 2": "Progressive Lana's Store",
- "Pushcart (Fruit) 3": "Progressive Lana's Store",
- "Lana": "Progressive Lana's Store",
- "Pushcart (Fruit) 1": "Progressive Lana's Store",
-
- "Jack's Store": "Progressive Jack's Store",
- "Jack's Annex Room": "Progressive Jack's Store",
- "Jack": "Progressive Jack's Store",
- "Jack's Chimney": "Progressive Jack's Store",
- "Jack's Sunshade": "Progressive Jack's Store",
- "Jack's Sign": "Progressive Jack's Store",
- "Jack's Lamp": "Progressive Jack's Store",
-
- "Joker's House": "Progressive Joker's House",
- "Joker": "Progressive Joker's House",
- "Joker's Chimney": "Progressive Joker's House",
- "Joker's Sunshade": "Progressive Joker's House",
- "Joker's Sign": "Progressive Joker's House",
- "Joker's Lamp": "Progressive Joker's House",
-
- "Divining House": "Progressive Divining House",
- "Snake": "Progressive Divining House",
- "Yaya": "Progressive Divining House",
- "Yaya's Sign": "Progressive Divining House",
-
- "Cathedral": "Progressive Cathedral",
- "Phil": "Progressive Cathedral",
- "Hat Shaped Roof": "Progressive Cathedral",
- "Cathedral's Torch": "Progressive Cathedral",
- "Holy Mark": "Progressive Cathedral",
-
- "Basker's Store": "Progressive Basker's Store",
- "Basker's Second Floor": "Progressive Basker's Store",
- "Basker's Chimney": "Progressive Basker's Store",
- "Basker's Sunshade": "Progressive Basker's Store",
- "Basker": "Progressive Basker's Store",
- "Basker's Sign": "Progressive Basker's Store",
- "Basker's Lamp": "Progressive Basker's Store",
-
- "King's Hideout": "Progressive King's Hideout",
- "King's Sunshade": "Progressive King's Hideout",
- "Car": "Progressive King's Hideout",
- "Jake": "Progressive King's Hideout",
- "Stu": "Progressive King's Hideout",
- "King": "Progressive King's Hideout",
- "King's Lamp": "Progressive King's Hideout",
-
- "Sheriff's Office": "Progressive Sheriff's Office",
- "Wilder": "Progressive Sheriff's Office",
- "Sam": "Progressive Sheriff's Office",
- "Prison 1": "Progressive Sheriff's Office",
- "Prison 2": "Progressive Sheriff's Office",
- "Bicycle": "Progressive Sheriff's Office",
- "Sheriff's Sign": "Progressive Sheriff's Office"
-}
\ No newline at end of file
diff --git a/worlds/dc1/data/progressive.json b/worlds/dc1/data/progressive.json
index ec22b6f6efb5..4faf65ca32b2 100644
--- a/worlds/dc1/data/progressive.json
+++ b/worlds/dc1/data/progressive.json
@@ -1,485 +1,485 @@
-{
- "Player's House": "Progressive Player's House",
- "Player's Storage": "Progressive Player's House",
- "Renee": "Progressive Player's House",
- "Player's Llama": "Progressive Player's House",
- "Player's Keg": "Progressive Player's House",
- "Player's Chimney": "Progressive Player's House",
- "Stray Cat": "Progressive Player's House",
-
- "Macho's House": "Progressive Macho's House",
- "Macho's Annex": "Progressive Macho's House",
- "Macho": "Progressive Macho's House",
- "Komacho": "Progressive Macho's House",
- "Macho's Fence": "Progressive Macho's House",
- "Macho's Lamp": "Progressive Macho's House",
- "Barbell": "Progressive Macho's House",
-
- "Laura's House": "Progressive Laura's House",
- "Laura's Cabin": "Progressive Laura's House",
- "Laura": "Progressive Laura's House",
- "Gina": "Progressive Laura's House",
- "Laura's Fence": "Progressive Laura's House",
- "Laura's Lamp": "Progressive Laura's House",
- "Tricycle": "Progressive Laura's House",
-
- "Paige's House": "Progressive Paige's House",
- "Pike": "Progressive Paige's House",
- "Paige's Cabin": "Progressive Paige's House",
- "Paige": "Progressive Paige's House",
- "Paige's Fence": "Progressive Paige's House",
- "Paige's Lamp": "Progressive Paige's House",
- "Wheels": "Progressive Paige's House",
-
- "Claude's House": "Progressive Claude's House",
- "Claude's Cabin": "Progressive Claude's House",
- "Candy Box": "Progressive Claude's House",
- "Claude": "Progressive Claude's House",
- "Claude's Fence": "Progressive Claude's House",
- "Claude's Lamp": "Progressive Claude's House",
- "Claude's Bench": "Progressive Claude's House",
-
- "Hag's House": "Progressive Hag's House",
- "Hag's Cabin": "Progressive Hag's House",
- "Hags' Jar": "Progressive Hag's House",
- "Hag": "Progressive Hag's House",
- "Hag's Bench": "Progressive Hag's House",
- "Hag's Lamp": "Progressive Hag's House",
- "Hag's Fence": "Progressive Hag's House",
-
- "Alnet's House": "Progressive Alnet's House",
- "Alnet's Stairway": "Progressive Alnet's House",
- "Alnet's Cabin": "Progressive Alnet's House",
- "Alnet": "Progressive Alnet's House",
- "Carl": "Progressive Alnet's House",
- "Alnet's Llama": "Progressive Alnet's House",
- "Alnet's Lamp": "Progressive Alnet's House",
-
- "Gaffer's Buggy": "Progressive Gaffer's Buggy",
- "Gaffer": "Progressive Gaffer's Buggy",
- "Supplies": "Progressive Gaffer's Buggy",
- "Gaffer's Sign": "Progressive Gaffer's Buggy",
- "Gaffer's Lamp": "Progressive Gaffer's Buggy",
-
- "Dran's Windmill": "Progressive Dran's Windmill",
- "Dran's Blade": "Progressive Dran's Windmill",
- "Dran's Torch": "Progressive Dran's Windmill",
- "Dran's Horn": "Progressive Dran's Windmill",
- "Dran's Sign": "Progressive Dran's Windmill",
-
- "Windmill 1": "Progressive Windmill 1",
- "Windmill 2": "Progressive Windmill 2",
- "Windmill 3": "Progressive Windmill 3",
- "Windmill Vanes 1": "Progressive Windmill 1",
- "Windmill Vanes 2": "Progressive Windmill 2",
- "Windmill Vanes 3": "Progressive Windmill 3",
- "Windmill Ladder 1": "Progressive Windmill 1",
- "Windmill Ladder 2": "Progressive Windmill 2",
- "Windmill Ladder 3": "Progressive Windmill 3",
-
-
-
- "Pao's House": "Progressive Pao's House",
- "Pao's Stairway": "Progressive Pao's House",
- "Pao": "Progressive Pao's House",
- "Pao's Torch": "Progressive Pao's House",
- "Pao's Sign": "Progressive Pao's House",
- "Pao's Laundry": "Progressive Pao's House",
-
- "Cacao's House": "Progressive Cacao's House",
- "Cacao's Stairway": "Progressive Cacao's House",
- "Cacao's Cabin": "Progressive Cacao's House",
- "Cacao": "Progressive Cacao's House",
- "Cacao's Torch": "Progressive Cacao's House",
- "Cacao's Sign": "Progressive Cacao's House",
- "Cacao's Laundry": "Progressive Cacao's House",
-
- "Bunbuku's House": "Progressive Bunbuku's House",
- "Bunbuku's Stairway": "Progressive Bunbuku's House",
- "Bunbuku's Cabin": "Progressive Bunbuku's House",
- "Bunbuku": "Progressive Bunbuku's House",
- "Kululu": "Progressive Bunbuku's House",
- "Bunbuku's Torch": "Progressive Bunbuku's House",
- "Bunbuku's Sign": "Progressive Bunbuku's House",
-
- "Kye's House": "Progressive Kye's House",
- "Kye's Stairway": "Progressive Kye's House",
- "Kye's Cabin": "Progressive Kye's House",
- "Kye": "Progressive Kye's House",
- "Momo": "Progressive Kye's House",
- "Kye's Torch": "Progressive Kye's House",
- "Kye's Sign": "Progressive Kye's House",
-
- "Baron's House": "Progressive Baron's House",
- "Baron": "Progressive Baron's House",
- "Baron's Torch": "Progressive Baron's House",
- "Baron's Sign": "Progressive Baron's House",
- "Baron's Roof": "Progressive Baron's House",
- "Baron's Branch": "Progressive Baron's House",
-
- "Couscous's House": "Progressive Couscous's House",
- "Couscous": "Progressive Couscous's House",
- "Couscous's Torch": "Progressive Couscous's House",
- "Couscous's Sign": "Progressive Couscous's House",
- "Couscous's Grass": "Progressive Couscous's House",
-
- "Gob's House": "Progressive Gob's House",
- "Gob": "Progressive Gob's House",
- "Gob's Torch": "Progressive Gob's House",
- "Gob's Sign": "Progressive Gob's House",
- "Gob's Bone": "Progressive Gob's House",
- "Gob's Tree": "Progressive Gob's House",
-
- "Mushroom House": "Progressive Mushroom House",
- "Second Floor": "Progressive Mushroom House",
- "Ro": "Progressive Mushroom House",
- "Annie": "Progressive Mushroom House",
- "Mushroom Torch": "Progressive Mushroom House",
- "Mushroom Sign": "Progressive Mushroom House",
- "Balcony": "Progressive Mushroom House",
-
- "Wise Owl Shop": "Progressive Owl Shop",
- "Owl Shop Entrance": "Progressive Owl Shop",
- "Mr. Moustache": "Progressive Owl Shop",
- "Wise Owl Torch": "Progressive Owl Shop",
- "Wise Owl Sign": "Progressive Owl Shop",
- "Wise Owl Entrance": "Progressive Owl Shop",
-
- "Watermill 1": "Progressive Watermill 1",
- "Watermill Torch 1": "Progressive Watermill 1",
- "Waterwheel 1": "Progressive Watermill 1",
-
- "Watermill 2": "Progressive Watermill 2",
- "Watermill Torch 2": "Progressive Watermill 2",
- "Waterwheel 2": "Progressive Watermill 2",
-
- "Watermill 3": "Progressive Watermill 3",
- "Watermill Torch 3": "Progressive Watermill 3",
- "Waterwheel 3": "Progressive Watermill 3",
-
- "Well 1": "Progressive Well 1",
- "Well 1 Torch": "Progressive Well 1",
- "Well 1 Stairway": "Progressive Well 1",
- "Well 1 Roof": "Progressive Well 1",
- "Well 1 Bucket": "Progressive Well 1",
- "Well 2": "Progressive Well 2",
- "Well 2 Torch": "Progressive Well 2",
- "Well 2 Stairway": "Progressive Well 2",
- "Well 2 Roof": "Progressive Well 2",
- "Well 2 Bucket": "Progressive Well 2",
- "Well 3": "Progressive Well 3",
- "Well 3 Torch": "Progressive Well 3",
- "Well 3 Stairway": "Progressive Well 3",
- "Well 3 Roof": "Progressive Well 3",
- "Well 3 Bucket": "Progressive Well 3",
-
- "Matataki River A": "Progressive Matataki River",
- "Matataki River B": "Progressive Matataki River",
- "Matataki River C": "Progressive Matataki River",
- "Matataki River D": "Progressive Matataki River",
- "Matataki River E": "Progressive Matataki River",
- "Matataki River F": "Progressive Matataki River",
- "Matataki River G": "Progressive Matataki River",
- "Matataki River H": "Progressive Matataki River",
-
-
-
- "Ruty's Store": "Progressive Ruty's Store",
- "Ruty": "Progressive Ruty's Store",
- "Pushcart (Fish) 1": "Progressive Ruty's Store",
- "Pushcart (Fish) 2": "Progressive Ruty's Store",
- "Pushcart (Fish) 3": "Progressive Ruty's Store",
-
- "Suzy's Store": "Progressive Suzy's Store",
- "Suzy's Pushcart": "Progressive Suzy's Store",
- "Suzy's Keg": "Progressive Suzy's Store",
- "Suzy": "Progressive Suzy's Store",
- "Suzy's Urn": "Progressive Suzy's Store",
- "Suzy's Lamp": "Progressive Suzy's Store",
-
- "Lana's Store": "Progressive Lana's Store",
- "Pushcart (Fruit) 2": "Progressive Lana's Store",
- "Pushcart (Fruit) 3": "Progressive Lana's Store",
- "Lana": "Progressive Lana's Store",
- "Pushcart (Fruit) 1": "Progressive Lana's Store",
-
- "Jack's Store": "Progressive Jack's Store",
- "Jack's Annex Room": "Progressive Jack's Store",
- "Jack": "Progressive Jack's Store",
- "Jack's Chimney": "Progressive Jack's Store",
- "Jack's Sunshade": "Progressive Jack's Store",
- "Jack's Sign": "Progressive Jack's Store",
- "Jack's Lamp": "Progressive Jack's Store",
-
- "Joker's House": "Progressive Joker's House",
- "Joker": "Progressive Joker's House",
- "Joker's Chimney": "Progressive Joker's House",
- "Joker's Sunshade": "Progressive Joker's House",
- "Joker's Sign": "Progressive Joker's House",
- "Joker's Lamp": "Progressive Joker's House",
-
- "Divining House": "Progressive Divining House",
- "Snake": "Progressive Divining House",
- "Yaya": "Progressive Divining House",
- "Yaya's Sign": "Progressive Divining House",
-
- "Cathedral": "Progressive Cathedral",
- "Phil": "Progressive Cathedral",
- "Hat Shaped Roof": "Progressive Cathedral",
- "Cathedral's Torch": "Progressive Cathedral",
- "Holy Mark": "Progressive Cathedral",
-
- "Basker's Store": "Progressive Basker's Store",
- "Basker's Second Floor": "Progressive Basker's Store",
- "Basker's Chimney": "Progressive Basker's Store",
- "Basker's Sunshade": "Progressive Basker's Store",
- "Basker": "Progressive Basker's Store",
- "Basker's Sign": "Progressive Basker's Store",
- "Basker's Lamp": "Progressive Basker's Store",
-
- "King's Hideout": "Progressive King's Hideout",
- "King's Sunshade": "Progressive King's Hideout",
- "Car": "Progressive King's Hideout",
- "Jake": "Progressive King's Hideout",
- "Stu": "Progressive King's Hideout",
- "King": "Progressive King's Hideout",
- "King's Lamp": "Progressive King's Hideout",
-
- "Sheriff's Office": "Progressive Sheriff's Office",
- "Wilder": "Progressive Sheriff's Office",
- "Sam": "Progressive Sheriff's Office",
- "Prison 1": "Progressive Sheriff's Office",
- "Prison 2": "Progressive Sheriff's Office",
- "Bicycle": "Progressive Sheriff's Office",
- "Sheriff's Sign": "Progressive Sheriff's Office",
-
- "Fountain": "Progressive Fountain",
- "Water's Surface": "Progressive Fountain",
- "Pillar": "Progressive Fountain",
- "Spout": "Progressive Fountain",
-
- "Leaning Tower": "Progressive Leaning Tower",
- "Tower Second Floor": "Progressive Leaning Tower",
- "Tower Third Floor": "Progressive Leaning Tower",
-
-
-
- "Chief's House": "Progressive Chief's House",
- "Chief Bonka": "Progressive Chief's House",
- "Chief Bonka's Mini Totem Pole": "Progressive Chief's House",
- "Chief Bonka's Cabin 1": "Progressive Chief's House",
- "Chief Bonka's Cabin 2": "Progressive Chief's House",
-
- "Jibubu's House": "Progressive Jibubu's House",
- "Jibubu's Stairway": "Progressive Jibubu's House",
- "Jibubu": "Progressive Jibubu's House",
- "Jibubu's Mini Totem Pole": "Progressive Jibubu's House",
- "Jibubu's Cabin": "Progressive Jibubu's House",
- "Jibubu's Hay": "Progressive Jibubu's House",
-
- "Zabo's House": "Progressive Zabo's House",
- "Zabo": "Progressive Zabo's House",
- "Zabo's Mini Totem Pole": "Progressive Zabo's House",
- "Zabo's Roof": "Progressive Zabo's House",
- "Zabo's Odds & Ends": "Progressive Zabo's House",
- "Zabo's Hay": "Progressive Zabo's House",
-
- "3 Sisters' House": "Progressive 3 Sisters' House",
- "Nagita": "Progressive 3 Sisters' House",
- "Devia": "Progressive 3 Sisters' House",
- "Mikara": "Progressive 3 Sisters' House",
- "Sisters' Mini Totem Pole": "Progressive 3 Sisters' House",
- "Sisters' Cabin": "Progressive 3 Sisters' House",
- "Sisters' Odds & Ends": "Progressive 3 Sisters' House",
-
- "Brooke's House": "Progressive Brooke's House",
- "Brooke": "Progressive Brooke's House",
- "Brooke's Mini Totem Pole": "Progressive Brooke's House",
- "Brooke's Cabin": "Progressive Brooke's House",
- "Brooke's Hay": "Progressive Brooke's House",
-
- "Enga's House": "Progressive Enga's House",
- "Enga's Stairway": "Progressive Enga's House",
- "Enga": "Progressive Enga's House",
- "Enga's Mini Totem Pole": "Progressive Enga's House",
- "Enga's Stairway Roof": "Progressive Enga's House",
- "Enga's Odds & Ends": "Progressive Enga's House",
- "Enga's Roof": "Progressive Enga's House",
-
- "Prisoner's Cabin": "Progressive Prisoner's Cabin",
- "Gron": "Progressive Prisoner's Cabin",
- "Prisoner's Mini Totem Pole": "Progressive Prisoner's Cabin",
- "Prisoner's Stairway": "Progressive Prisoner's Cabin",
- "Prisoner's Sign": "Progressive Prisoner's Cabin",
-
- "Toto's House": "Progressive Toto's House",
- "Toto": "Progressive Toto's House",
- "Toto's Mini Totem Pole": "Progressive Toto's House",
- "Toto's Handle": "Progressive Toto's House",
- "Toto's Odds & Ends": "Progressive Toto's House",
- "Gosuke": "Progressive Toto's House",
-
- "Totem Pole A": "Progressive Totem Pole A",
- "Totem Pole A 2nd Head": "Progressive Totem Pole A",
- "Totem Pole A 3rd Head": "Progressive Totem Pole A",
-
- "Totem Pole B": "Progressive Totem Pole B",
- "Totem Pole B 2nd Head": "Progressive Totem Pole B",
- "Totem Pole B 3rd Head": "Progressive Totem Pole B",
-
- "Totem Pole C": "Progressive Totem Pole C",
- "Totem Pole C 2nd Head": "Progressive Totem Pole C",
- "Totem Pole C 3rd Head": "Progressive Totem Pole C",
-
- "Oasis": "Progressive Oasis",
- "Oasis Tree 1": "Progressive Oasis",
- "Oasis Tree 2": "Progressive Oasis",
- "Oasis Tree 3": "Progressive Oasis",
-
-
- "Parts HD": "Progressive Parts HD",
- "Crown (HD)": "Progressive Parts HD",
- "Eye (HD)": "Progressive Parts HD",
-
- "Parts AMR": "Progressive Parts AMR",
- "Elephant Emblem (AMR)": "Progressive Parts AMR",
- "Mammoth Tusk (AMR)": "Progressive Parts AMR",
- "Finger (AMR)": "Progressive Parts AMR",
- "Ammonite (AMR)": "Progressive Parts AMR",
- "Twisted Horn (AMR)": "Progressive Parts AMR",
-
- "Parts AML": "Progressive Parts AML",
- "Elephant Emblem (AML)": "Progressive Parts AML",
- "Mammoth Tusk (AML)": "Progressive Parts AML",
- "Finger (AML)": "Progressive Parts AML",
- "Ammonite (AML)": "Progressive Parts AML",
- "Twisted Horn (AML)": "Progressive Parts AML",
-
- "Parts HGR": "Progressive Parts HGR",
- "Big Urn (HGR)": "Progressive Parts HGR",
- "Eagle Head (HGR)": "Progressive Parts HGR",
- "Eagle Feather (HGR)": "Progressive Parts HGR",
- "Amuleo": "Progressive Parts HGR",
-
- "Parts HGL": "Progressive Parts HGL",
- "Big Urn (HGL)": "Progressive Parts HGL",
- "Eagle Head (HGL)": "Progressive Parts HGL",
- "Eagle Feather (HGL)": "Progressive Parts HGL",
- "Gotch": "Progressive Parts HGL",
-
- "Parts HGR2": "Progressive Parts HGR2",
- "Urn (HGR2)": "Progressive Parts HGR2",
-
- "Parts HGL2": "Progressive Parts HGL2",
- "Urn (HGL2)": "Progressive Parts HGL2",
-
- "Parts CT": "Progressive Parts CT",
- "Scale (CT)": "Progressive Parts CT",
- "Crest Armor (CT)": "Progressive Parts CT",
-
- "Parts WT": "Progressive Parts WT",
- "Skirt (WT)": "Progressive Parts WT",
- "Skull (WT)": "Progressive Parts WT",
- "Buffalo's Horn (WT)": "Progressive Parts WT",
-
- "Parts TIR": "Progressive Parts TIR",
- "Urn (TIR)": "Progressive Parts TIR",
- "Tomahon": "Progressive Parts TIR",
-
- "Parts TIL": "Progressive Parts TIL",
- "Urn (TIL)": "Progressive Parts TIL",
- "Boon": "Progressive Parts TIL",
-
- "Parts FTR": "Progressive Parts FTR",
- "Big Urn (FTR)": "Progressive Parts FTR",
- "Turtle Shell (FTR)": "Progressive Parts FTR",
- "Spine of Shell (FTR)": "Progressive Parts FTR",
- "Nail (FTR)": "Progressive Parts FTR",
-
- "Parts FTL": "Progressive Parts FTL",
- "Big Urn (FTL)": "Progressive Parts FTL",
- "Turtle Shell (FTL)": "Progressive Parts FTL",
- "Spine of Shell (FTL)": "Progressive Parts FTL",
- "Nail (FTL)": "Progressive Parts FTL",
-
- "Lookout": "Progressive Lookout",
- "Lookout Ladder 1": "Progressive Lookout",
- "Lookout Second Floor": "Progressive Lookout",
- "Lookout Ladder 2": "Progressive Lookout",
-
-
-
- "Crowning Day": "Progressive Crowning Day",
- "Bed (Crowning Day)": "Progressive Crowning Day",
- "Father (Crowning Day)": "Progressive Crowning Day",
- "Crown (Crowning Day)": "Progressive Crowning Day",
-
- "Night of the Ceremony": "Progressive Ceremony",
- "Moon (Ceremony)": "Progressive Ceremony",
- "Castle (Ceremony)": "Progressive Ceremony",
- "Torch (Ceremony)": "Progressive Ceremony",
- "Crown (Ceremony)": "Progressive Ceremony",
- "Sophia (Ceremony)": "Progressive Ceremony",
-
- "Reunion in the Storm": "Progressive Reunion",
- "Bed (Reunion)": "Progressive Reunion",
- "Minister (Reunion)": "Progressive Reunion",
- "Sophia (Reunion)": "Progressive Reunion",
- "Buggy (Reunion)": "Progressive Reunion",
-
- "First Campaign": "Progressive Campaign",
- "Horserider (Campaign)": "Progressive Campaign",
- "Sword (Campaign)": "Progressive Campaign",
- "Wizard (Campaign)": "Progressive Campaign",
- "Father (Campaign)": "Progressive Campaign",
- "Crown (Campaign)": "Progressive Campaign",
-
- "Menace of the West": "Progressive Menace",
- "Wizard (Menace)": "Progressive Menace",
- "The Broken Sword (Menace)": "Progressive Menace",
- "Horserider (Menace)": "Progressive Menace",
- "Castle (Menace)": "Progressive Menace",
- "Sophia (Menace)": "Progressive Menace",
-
- "The Deal": "Progressive The Deal",
- "Fort (The Deal)": "Progressive The Deal",
- "The Broken Sword (The Deal)": "Progressive The Deal",
- "Black Robed Man (The Deal)": "Progressive The Deal",
- "Bloody Agreement (The Deal)": "Progressive The Deal",
-
- "Dark Power": "Progressive Dark Power",
- "Black Blood (Dark Power)": "Progressive Dark Power",
- "Light (Dark Power)": "Progressive Dark Power",
- "Wizard (Dark Power)": "Progressive Dark Power",
- "Castle (Dark Power)": "Progressive Dark Power",
- "Sophia (Dark Power)": "Progressive Dark Power",
-
- "The Assassin": "Progressive Assassin",
- "Castle (Assassin)": "Progressive Assassin",
- "Wine (Assassin)": "Progressive Assassin",
- "Moon (Assassin)": "Progressive Assassin",
- "Sophia (Assassin)": "Progressive Assassin",
- "Door (Assassin)": "Progressive Assassin",
- "Assassin (Assassin)": "Progressive Assassin",
-
- "Must be Protected": "Progressive Protected",
- "Sophia (Protected)": "Progressive Protected",
- "Knife (Protected)": "Progressive Protected",
- "Bloody Dress (Protected)": "Progressive Protected",
-
- "Birth of the Demon": "Progressive Demon",
- "Castle (Demon)": "Progressive Demon",
- "Dark Cloud (Demon)": "Progressive Demon",
- "Black Blood (Demon)": "Progressive Demon",
-
- "Things Lost": "Progressive Things Lost",
- "Ruined Castle (Things Lost)": "Progressive Things Lost",
- "Grave (Things Lost)": "Progressive Things Lost",
- "The Broken Sword (Things Lost)": "Progressive Things Lost",
-
- "Departure": "Progressive Departure",
- "Sandglass (Departure)": "Progressive Departure",
- "Prophet (Departure)": "Progressive Departure",
- "Sword (Departure)": "Progressive Departure",
- "Book of Curses (Departure)": "Progressive Departure"
-}
\ No newline at end of file
+{
+ "Player's House": "Progressive Player's House",
+ "Player's Storage": "Progressive Player's House",
+ "Renee": "Progressive Player's House",
+ "Player's Llama": "Progressive Player's House",
+ "Player's Keg": "Progressive Player's House",
+ "Player's Chimney": "Progressive Player's House",
+ "Stray Cat": "Progressive Player's House",
+
+ "Macho's House": "Progressive Macho's House",
+ "Macho's Annex": "Progressive Macho's House",
+ "Macho": "Progressive Macho's House",
+ "Komacho": "Progressive Macho's House",
+ "Macho's Fence": "Progressive Macho's House",
+ "Macho's Lamp": "Progressive Macho's House",
+ "Barbell": "Progressive Macho's House",
+
+ "Laura's House": "Progressive Laura's House",
+ "Laura's Cabin": "Progressive Laura's House",
+ "Laura": "Progressive Laura's House",
+ "Gina": "Progressive Laura's House",
+ "Laura's Fence": "Progressive Laura's House",
+ "Laura's Lamp": "Progressive Laura's House",
+ "Tricycle": "Progressive Laura's House",
+
+ "Paige's House": "Progressive Paige's House",
+ "Pike": "Progressive Paige's House",
+ "Paige's Cabin": "Progressive Paige's House",
+ "Paige": "Progressive Paige's House",
+ "Paige's Fence": "Progressive Paige's House",
+ "Paige's Lamp": "Progressive Paige's House",
+ "Wheels": "Progressive Paige's House",
+
+ "Claude's House": "Progressive Claude's House",
+ "Claude's Cabin": "Progressive Claude's House",
+ "Candy Box": "Progressive Claude's House",
+ "Claude": "Progressive Claude's House",
+ "Claude's Fence": "Progressive Claude's House",
+ "Claude's Lamp": "Progressive Claude's House",
+ "Claude's Bench": "Progressive Claude's House",
+
+ "Hag's House": "Progressive Hag's House",
+ "Hag's Cabin": "Progressive Hag's House",
+ "Hags' Jar": "Progressive Hag's House",
+ "Hag": "Progressive Hag's House",
+ "Hag's Bench": "Progressive Hag's House",
+ "Hag's Lamp": "Progressive Hag's House",
+ "Hag's Fence": "Progressive Hag's House",
+
+ "Alnet's House": "Progressive Alnet's House",
+ "Alnet's Stairway": "Progressive Alnet's House",
+ "Alnet's Cabin": "Progressive Alnet's House",
+ "Alnet": "Progressive Alnet's House",
+ "Carl": "Progressive Alnet's House",
+ "Alnet's Llama": "Progressive Alnet's House",
+ "Alnet's Lamp": "Progressive Alnet's House",
+
+ "Gaffer's Buggy": "Progressive Gaffer's Buggy",
+ "Gaffer": "Progressive Gaffer's Buggy",
+ "Supplies": "Progressive Gaffer's Buggy",
+ "Gaffer's Sign": "Progressive Gaffer's Buggy",
+ "Gaffer's Lamp": "Progressive Gaffer's Buggy",
+
+ "Dran's Windmill": "Progressive Dran's Windmill",
+ "Dran's Blade": "Progressive Dran's Windmill",
+ "Dran's Torch": "Progressive Dran's Windmill",
+ "Dran's Horn": "Progressive Dran's Windmill",
+ "Dran's Sign": "Progressive Dran's Windmill",
+
+ "Windmill 1": "Progressive Windmill 1",
+ "Windmill 2": "Progressive Windmill 2",
+ "Windmill 3": "Progressive Windmill 3",
+ "Windmill Vanes 1": "Progressive Windmill 1",
+ "Windmill Vanes 2": "Progressive Windmill 2",
+ "Windmill Vanes 3": "Progressive Windmill 3",
+ "Windmill Ladder 1": "Progressive Windmill 1",
+ "Windmill Ladder 2": "Progressive Windmill 2",
+ "Windmill Ladder 3": "Progressive Windmill 3",
+
+
+
+ "Pao's House": "Progressive Pao's House",
+ "Pao's Stairway": "Progressive Pao's House",
+ "Pao": "Progressive Pao's House",
+ "Pao's Torch": "Progressive Pao's House",
+ "Pao's Sign": "Progressive Pao's House",
+ "Pao's Laundry": "Progressive Pao's House",
+
+ "Cacao's House": "Progressive Cacao's House",
+ "Cacao's Stairway": "Progressive Cacao's House",
+ "Cacao's Cabin": "Progressive Cacao's House",
+ "Cacao": "Progressive Cacao's House",
+ "Cacao's Torch": "Progressive Cacao's House",
+ "Cacao's Sign": "Progressive Cacao's House",
+ "Cacao's Laundry": "Progressive Cacao's House",
+
+ "Bunbuku's House": "Progressive Bunbuku's House",
+ "Bunbuku's Stairway": "Progressive Bunbuku's House",
+ "Bunbuku's Cabin": "Progressive Bunbuku's House",
+ "Bunbuku": "Progressive Bunbuku's House",
+ "Kululu": "Progressive Bunbuku's House",
+ "Bunbuku's Torch": "Progressive Bunbuku's House",
+ "Bunbuku's Sign": "Progressive Bunbuku's House",
+
+ "Kye's House": "Progressive Kye's House",
+ "Kye's Stairway": "Progressive Kye's House",
+ "Kye's Cabin": "Progressive Kye's House",
+ "Kye": "Progressive Kye's House",
+ "Momo": "Progressive Kye's House",
+ "Kye's Torch": "Progressive Kye's House",
+ "Kye's Sign": "Progressive Kye's House",
+
+ "Baron's House": "Progressive Baron's House",
+ "Baron": "Progressive Baron's House",
+ "Baron's Torch": "Progressive Baron's House",
+ "Baron's Sign": "Progressive Baron's House",
+ "Baron's Roof": "Progressive Baron's House",
+ "Baron's Branch": "Progressive Baron's House",
+
+ "Couscous's House": "Progressive Couscous's House",
+ "Couscous": "Progressive Couscous's House",
+ "Couscous's Torch": "Progressive Couscous's House",
+ "Couscous's Sign": "Progressive Couscous's House",
+ "Couscous's Grass": "Progressive Couscous's House",
+
+ "Gob's House": "Progressive Gob's House",
+ "Gob": "Progressive Gob's House",
+ "Gob's Torch": "Progressive Gob's House",
+ "Gob's Sign": "Progressive Gob's House",
+ "Gob's Bone": "Progressive Gob's House",
+ "Gob's Tree": "Progressive Gob's House",
+
+ "Mushroom House": "Progressive Mushroom House",
+ "Second Floor": "Progressive Mushroom House",
+ "Ro": "Progressive Mushroom House",
+ "Annie": "Progressive Mushroom House",
+ "Mushroom Torch": "Progressive Mushroom House",
+ "Mushroom Sign": "Progressive Mushroom House",
+ "Balcony": "Progressive Mushroom House",
+
+ "Wise Owl Shop": "Progressive Owl Shop",
+ "Owl Shop Entrance": "Progressive Owl Shop",
+ "Mr. Moustache": "Progressive Owl Shop",
+ "Wise Owl Torch": "Progressive Owl Shop",
+ "Wise Owl Sign": "Progressive Owl Shop",
+ "Wise Owl Entrance": "Progressive Owl Shop",
+
+ "Watermill 1": "Progressive Watermill 1",
+ "Watermill Torch 1": "Progressive Watermill 1",
+ "Waterwheel 1": "Progressive Watermill 1",
+
+ "Watermill 2": "Progressive Watermill 2",
+ "Watermill Torch 2": "Progressive Watermill 2",
+ "Waterwheel 2": "Progressive Watermill 2",
+
+ "Watermill 3": "Progressive Watermill 3",
+ "Watermill Torch 3": "Progressive Watermill 3",
+ "Waterwheel 3": "Progressive Watermill 3",
+
+ "Well 1": "Progressive Well 1",
+ "Well 1 Torch": "Progressive Well 1",
+ "Well 1 Stairway": "Progressive Well 1",
+ "Well 1 Roof": "Progressive Well 1",
+ "Well 1 Bucket": "Progressive Well 1",
+ "Well 2": "Progressive Well 2",
+ "Well 2 Torch": "Progressive Well 2",
+ "Well 2 Stairway": "Progressive Well 2",
+ "Well 2 Roof": "Progressive Well 2",
+ "Well 2 Bucket": "Progressive Well 2",
+ "Well 3": "Progressive Well 3",
+ "Well 3 Torch": "Progressive Well 3",
+ "Well 3 Stairway": "Progressive Well 3",
+ "Well 3 Roof": "Progressive Well 3",
+ "Well 3 Bucket": "Progressive Well 3",
+
+ "Matataki River A": "Progressive Matataki River",
+ "Matataki River B": "Progressive Matataki River",
+ "Matataki River C": "Progressive Matataki River",
+ "Matataki River D": "Progressive Matataki River",
+ "Matataki River E": "Progressive Matataki River",
+ "Matataki River F": "Progressive Matataki River",
+ "Matataki River G": "Progressive Matataki River",
+ "Matataki River H": "Progressive Matataki River",
+
+
+
+ "Ruty's Store": "Progressive Ruty's Store",
+ "Ruty": "Progressive Ruty's Store",
+ "Pushcart (Fish) 1": "Progressive Ruty's Store",
+ "Pushcart (Fish) 2": "Progressive Ruty's Store",
+ "Pushcart (Fish) 3": "Progressive Ruty's Store",
+
+ "Suzy's Store": "Progressive Suzy's Store",
+ "Suzy's Pushcart": "Progressive Suzy's Store",
+ "Suzy's Keg": "Progressive Suzy's Store",
+ "Suzy": "Progressive Suzy's Store",
+ "Suzy's Urn": "Progressive Suzy's Store",
+ "Suzy's Lamp": "Progressive Suzy's Store",
+
+ "Lana's Store": "Progressive Lana's Store",
+ "Pushcart (Fruit) 2": "Progressive Lana's Store",
+ "Pushcart (Fruit) 3": "Progressive Lana's Store",
+ "Lana": "Progressive Lana's Store",
+ "Pushcart (Fruit) 1": "Progressive Lana's Store",
+
+ "Jack's Store": "Progressive Jack's Store",
+ "Jack's Annex Room": "Progressive Jack's Store",
+ "Jack": "Progressive Jack's Store",
+ "Jack's Chimney": "Progressive Jack's Store",
+ "Jack's Sunshade": "Progressive Jack's Store",
+ "Jack's Sign": "Progressive Jack's Store",
+ "Jack's Lamp": "Progressive Jack's Store",
+
+ "Joker's House": "Progressive Joker's House",
+ "Joker": "Progressive Joker's House",
+ "Joker's Chimney": "Progressive Joker's House",
+ "Joker's Sunshade": "Progressive Joker's House",
+ "Joker's Sign": "Progressive Joker's House",
+ "Joker's Lamp": "Progressive Joker's House",
+
+ "Divining House": "Progressive Divining House",
+ "Snake": "Progressive Divining House",
+ "Yaya": "Progressive Divining House",
+ "Yaya's Sign": "Progressive Divining House",
+
+ "Cathedral": "Progressive Cathedral",
+ "Phil": "Progressive Cathedral",
+ "Hat Shaped Roof": "Progressive Cathedral",
+ "Cathedral's Torch": "Progressive Cathedral",
+ "Holy Mark": "Progressive Cathedral",
+
+ "Basker's Store": "Progressive Basker's Store",
+ "Basker's Second Floor": "Progressive Basker's Store",
+ "Basker's Chimney": "Progressive Basker's Store",
+ "Basker's Sunshade": "Progressive Basker's Store",
+ "Basker": "Progressive Basker's Store",
+ "Basker's Sign": "Progressive Basker's Store",
+ "Basker's Lamp": "Progressive Basker's Store",
+
+ "King's Hideout": "Progressive King's Hideout",
+ "King's Sunshade": "Progressive King's Hideout",
+ "Car": "Progressive King's Hideout",
+ "Jake": "Progressive King's Hideout",
+ "Stu": "Progressive King's Hideout",
+ "King": "Progressive King's Hideout",
+ "King's Lamp": "Progressive King's Hideout",
+
+ "Sheriff's Office": "Progressive Sheriff's Office",
+ "Wilder": "Progressive Sheriff's Office",
+ "Sam": "Progressive Sheriff's Office",
+ "Prison 1": "Progressive Sheriff's Office",
+ "Prison 2": "Progressive Sheriff's Office",
+ "Bicycle": "Progressive Sheriff's Office",
+ "Sheriff's Sign": "Progressive Sheriff's Office",
+
+ "Fountain": "Progressive Fountain",
+ "Water's Surface": "Progressive Fountain",
+ "Pillar": "Progressive Fountain",
+ "Spout": "Progressive Fountain",
+
+ "Leaning Tower": "Progressive Leaning Tower",
+ "Tower Second Floor": "Progressive Leaning Tower",
+ "Tower Third Floor": "Progressive Leaning Tower",
+
+
+
+ "Chief's House": "Progressive Chief's House",
+ "Chief Bonka": "Progressive Chief's House",
+ "Chief Bonka's Mini Totem Pole": "Progressive Chief's House",
+ "Chief Bonka's Cabin 1": "Progressive Chief's House",
+ "Chief Bonka's Cabin 2": "Progressive Chief's House",
+
+ "Jibubu's House": "Progressive Jibubu's House",
+ "Jibubu's Stairway": "Progressive Jibubu's House",
+ "Jibubu": "Progressive Jibubu's House",
+ "Jibubu's Mini Totem Pole": "Progressive Jibubu's House",
+ "Jibubu's Cabin": "Progressive Jibubu's House",
+ "Jibubu's Hay": "Progressive Jibubu's House",
+
+ "Zabo's House": "Progressive Zabo's House",
+ "Zabo": "Progressive Zabo's House",
+ "Zabo's Mini Totem Pole": "Progressive Zabo's House",
+ "Zabo's Roof": "Progressive Zabo's House",
+ "Zabo's Odds & Ends": "Progressive Zabo's House",
+ "Zabo's Hay": "Progressive Zabo's House",
+
+ "3 Sisters' House": "Progressive 3 Sisters' House",
+ "Nagita": "Progressive 3 Sisters' House",
+ "Devia": "Progressive 3 Sisters' House",
+ "Mikara": "Progressive 3 Sisters' House",
+ "Sisters' Mini Totem Pole": "Progressive 3 Sisters' House",
+ "Sisters' Cabin": "Progressive 3 Sisters' House",
+ "Sisters' Odds & Ends": "Progressive 3 Sisters' House",
+
+ "Brooke's House": "Progressive Brooke's House",
+ "Brooke": "Progressive Brooke's House",
+ "Brooke's Mini Totem Pole": "Progressive Brooke's House",
+ "Brooke's Cabin": "Progressive Brooke's House",
+ "Brooke's Hay": "Progressive Brooke's House",
+
+ "Enga's House": "Progressive Enga's House",
+ "Enga's Stairway": "Progressive Enga's House",
+ "Enga": "Progressive Enga's House",
+ "Enga's Mini Totem Pole": "Progressive Enga's House",
+ "Enga's Stairway Roof": "Progressive Enga's House",
+ "Enga's Odds & Ends": "Progressive Enga's House",
+ "Enga's Roof": "Progressive Enga's House",
+
+ "Prisoner's Cabin": "Progressive Prisoner's Cabin",
+ "Gron": "Progressive Prisoner's Cabin",
+ "Prisoner's Mini Totem Pole": "Progressive Prisoner's Cabin",
+ "Prisoner's Stairway": "Progressive Prisoner's Cabin",
+ "Prisoner's Sign": "Progressive Prisoner's Cabin",
+
+ "Toto's House": "Progressive Toto's House",
+ "Toto": "Progressive Toto's House",
+ "Toto's Mini Totem Pole": "Progressive Toto's House",
+ "Toto's Handle": "Progressive Toto's House",
+ "Toto's Odds & Ends": "Progressive Toto's House",
+ "Gosuke": "Progressive Toto's House",
+
+ "Totem Pole A": "Progressive Totem Pole A",
+ "Totem Pole A 2nd Head": "Progressive Totem Pole A",
+ "Totem Pole A 3rd Head": "Progressive Totem Pole A",
+
+ "Totem Pole B": "Progressive Totem Pole B",
+ "Totem Pole B 2nd Head": "Progressive Totem Pole B",
+ "Totem Pole B 3rd Head": "Progressive Totem Pole B",
+
+ "Totem Pole C": "Progressive Totem Pole C",
+ "Totem Pole C 2nd Head": "Progressive Totem Pole C",
+ "Totem Pole C 3rd Head": "Progressive Totem Pole C",
+
+ "Oasis": "Progressive Oasis",
+ "Oasis Tree 1": "Progressive Oasis",
+ "Oasis Tree 2": "Progressive Oasis",
+ "Oasis Tree 3": "Progressive Oasis",
+
+
+ "Parts HD": "Progressive Parts HD",
+ "Crown (HD)": "Progressive Parts HD",
+ "Eye (HD)": "Progressive Parts HD",
+
+ "Parts AMR": "Progressive Parts AMR",
+ "Elephant Emblem (AMR)": "Progressive Parts AMR",
+ "Mammoth Tusk (AMR)": "Progressive Parts AMR",
+ "Finger (AMR)": "Progressive Parts AMR",
+ "Ammonite (AMR)": "Progressive Parts AMR",
+ "Twisted Horn (AMR)": "Progressive Parts AMR",
+
+ "Parts AML": "Progressive Parts AML",
+ "Elephant Emblem (AML)": "Progressive Parts AML",
+ "Mammoth Tusk (AML)": "Progressive Parts AML",
+ "Finger (AML)": "Progressive Parts AML",
+ "Ammonite (AML)": "Progressive Parts AML",
+ "Twisted Horn (AML)": "Progressive Parts AML",
+
+ "Parts HGR": "Progressive Parts HGR",
+ "Big Urn (HGR)": "Progressive Parts HGR",
+ "Eagle Head (HGR)": "Progressive Parts HGR",
+ "Eagle Feather (HGR)": "Progressive Parts HGR",
+ "Amuleo": "Progressive Parts HGR",
+
+ "Parts HGL": "Progressive Parts HGL",
+ "Big Urn (HGL)": "Progressive Parts HGL",
+ "Eagle Head (HGL)": "Progressive Parts HGL",
+ "Eagle Feather (HGL)": "Progressive Parts HGL",
+ "Gotch": "Progressive Parts HGL",
+
+ "Parts HGR2": "Progressive Parts HGR2",
+ "Urn (HGR2)": "Progressive Parts HGR2",
+
+ "Parts HGL2": "Progressive Parts HGL2",
+ "Urn (HGL2)": "Progressive Parts HGL2",
+
+ "Parts CT": "Progressive Parts CT",
+ "Scale (CT)": "Progressive Parts CT",
+ "Crest Armor (CT)": "Progressive Parts CT",
+
+ "Parts WT": "Progressive Parts WT",
+ "Skirt (WT)": "Progressive Parts WT",
+ "Skull (WT)": "Progressive Parts WT",
+ "Buffalo's Horn (WT)": "Progressive Parts WT",
+
+ "Parts TIR": "Progressive Parts TIR",
+ "Urn (TIR)": "Progressive Parts TIR",
+ "Tomahon": "Progressive Parts TIR",
+
+ "Parts TIL": "Progressive Parts TIL",
+ "Urn (TIL)": "Progressive Parts TIL",
+ "Boon": "Progressive Parts TIL",
+
+ "Parts FTR": "Progressive Parts FTR",
+ "Big Urn (FTR)": "Progressive Parts FTR",
+ "Turtle Shell (FTR)": "Progressive Parts FTR",
+ "Spine of Shell (FTR)": "Progressive Parts FTR",
+ "Nail (FTR)": "Progressive Parts FTR",
+
+ "Parts FTL": "Progressive Parts FTL",
+ "Big Urn (FTL)": "Progressive Parts FTL",
+ "Turtle Shell (FTL)": "Progressive Parts FTL",
+ "Spine of Shell (FTL)": "Progressive Parts FTL",
+ "Nail (FTL)": "Progressive Parts FTL",
+
+ "Lookout": "Progressive Lookout",
+ "Lookout Ladder 1": "Progressive Lookout",
+ "Lookout Second Floor": "Progressive Lookout",
+ "Lookout Ladder 2": "Progressive Lookout",
+
+
+
+ "Crowning Day": "Progressive Crowning Day",
+ "Bed (Crowning Day)": "Progressive Crowning Day",
+ "Father (Crowning Day)": "Progressive Crowning Day",
+ "Crown (Crowning Day)": "Progressive Crowning Day",
+
+ "Night of the Ceremony": "Progressive Ceremony",
+ "Moon (Ceremony)": "Progressive Ceremony",
+ "Castle (Ceremony)": "Progressive Ceremony",
+ "Torch (Ceremony)": "Progressive Ceremony",
+ "Crown (Ceremony)": "Progressive Ceremony",
+ "Sophia (Ceremony)": "Progressive Ceremony",
+
+ "Reunion in the Storm": "Progressive Reunion",
+ "Bed (Reunion)": "Progressive Reunion",
+ "Minister (Reunion)": "Progressive Reunion",
+ "Sophia (Reunion)": "Progressive Reunion",
+ "Buggy (Reunion)": "Progressive Reunion",
+
+ "First Campaign": "Progressive Campaign",
+ "Horserider (Campaign)": "Progressive Campaign",
+ "Sword (Campaign)": "Progressive Campaign",
+ "Wizard (Campaign)": "Progressive Campaign",
+ "Father (Campaign)": "Progressive Campaign",
+ "Crown (Campaign)": "Progressive Campaign",
+
+ "Menace of the West": "Progressive Menace",
+ "Wizard (Menace)": "Progressive Menace",
+ "The Broken Sword (Menace)": "Progressive Menace",
+ "Horserider (Menace)": "Progressive Menace",
+ "Castle (Menace)": "Progressive Menace",
+ "Sophia (Menace)": "Progressive Menace",
+
+ "The Deal": "Progressive The Deal",
+ "Fort (The Deal)": "Progressive The Deal",
+ "The Broken Sword (The Deal)": "Progressive The Deal",
+ "Black Robed Man (The Deal)": "Progressive The Deal",
+ "Bloody Agreement (The Deal)": "Progressive The Deal",
+
+ "Dark Power": "Progressive Dark Power",
+ "Black Blood (Dark Power)": "Progressive Dark Power",
+ "Light (Dark Power)": "Progressive Dark Power",
+ "Wizard (Dark Power)": "Progressive Dark Power",
+ "Castle (Dark Power)": "Progressive Dark Power",
+ "Sophia (Dark Power)": "Progressive Dark Power",
+
+ "The Assassin": "Progressive Assassin",
+ "Castle (Assassin)": "Progressive Assassin",
+ "Wine (Assassin)": "Progressive Assassin",
+ "Moon (Assassin)": "Progressive Assassin",
+ "Sophia (Assassin)": "Progressive Assassin",
+ "Door (Assassin)": "Progressive Assassin",
+ "Assassin (Assassin)": "Progressive Assassin",
+
+ "Must be Protected": "Progressive Protected",
+ "Sophia (Protected)": "Progressive Protected",
+ "Knife (Protected)": "Progressive Protected",
+ "Bloody Dress (Protected)": "Progressive Protected",
+
+ "Birth of the Demon": "Progressive Demon",
+ "Castle (Demon)": "Progressive Demon",
+ "Dark Cloud (Demon)": "Progressive Demon",
+ "Black Blood (Demon)": "Progressive Demon",
+
+ "Things Lost": "Progressive Things Lost",
+ "Ruined Castle (Things Lost)": "Progressive Things Lost",
+ "Grave (Things Lost)": "Progressive Things Lost",
+ "The Broken Sword (Things Lost)": "Progressive Things Lost",
+
+ "Departure": "Progressive Departure",
+ "Sandglass (Departure)": "Progressive Departure",
+ "Prophet (Departure)": "Progressive Departure",
+ "Sword (Departure)": "Progressive Departure",
+ "Book of Curses (Departure)": "Progressive Departure"
+}
diff --git a/worlds/dc2/Items.py b/worlds/dc2/Items.py
index d519257c48b1..001043c43335 100644
--- a/worlds/dc2/Items.py
+++ b/worlds/dc2/Items.py
@@ -1,580 +1,597 @@
-from enum import IntEnum
-from typing import NamedTuple
-from BaseClasses import Item, MultiWorld
-
-
-class DC2ItemCategory(IntEnum):
- WEAPON_MAX_L = 0
- WEAPON_MAX_R = 1
- WEAPON_MONICA_L = 2
- WEAPON_MONICA_R = 3
- CONSUMABLE = 4
- GATE_KEY = 5
- GEORAMA_RESOURCE = 6
- GEOSTONE = 7
- MISC = 8,
- SKIP = 9,
- COIN = 10,
- GEM = 11,
- KEY_ITEM = 12
- EVENT = 13,
- REPLACE = 14,
- RIDEPOD = 15,
- MATERIAL = 16,
-
-
-class DC2ItemData(NamedTuple):
- name: str
- dc2_code: int
- category: DC2ItemCategory
-
-
-class DarkCloud2Item(Item):
- game: str = "Dark Cloud 2"
-
- @staticmethod
- def get_name_to_id() -> dict:
- base_id = 694200000
- return {item_data.name: (base_id + item_data.dc2_code if item_data.dc2_code is not None else None) for item_data in _all_items}
-
-
-key_item_names = {
-}
-
-
-_all_items = [DC2ItemData(row[0], row[1], row[2]) for row in [
- ("null", 0, DC2ItemCategory.REPLACE),
-
- ("Battle Wrench", 1, DC2ItemCategory.WEAPON_MAX_R),
- ("Drill Wrench", 2, DC2ItemCategory.WEAPON_MAX_R),
- ("Smash Wrench", 3, DC2ItemCategory.WEAPON_MAX_R),
- ("Stinger Wrench", 4, DC2ItemCategory.WEAPON_MAX_R),
- ("Poison Wrench", 5, DC2ItemCategory.WEAPON_MAX_R),
- ("Cubic Hammer", 6, DC2ItemCategory.WEAPON_MAX_R),
- ("Digi Hammer", 7, DC2ItemCategory.WEAPON_MAX_R),
- ("Heavy Hammer", 8, DC2ItemCategory.WEAPON_MAX_R),
- ("Handy Stick", 9, DC2ItemCategory.WEAPON_MAX_R),
- ("Turkey", 10, DC2ItemCategory.WEAPON_MAX_R),
- ("Swan", 11, DC2ItemCategory.WEAPON_MAX_R),
- ("Flamingo", 12, DC2ItemCategory.WEAPON_MAX_R),
- ("Falcon", 13, DC2ItemCategory.WEAPON_MAX_R),
- ("Albatross", 14, DC2ItemCategory.WEAPON_MAX_R),
- ("Turtle Shell Hammer", 15, DC2ItemCategory.WEAPON_MAX_R),
- ("Big Bucks Hammer", 16, DC2ItemCategory.WEAPON_MAX_R),
- ("Frozen Tuna", 17, DC2ItemCategory.WEAPON_MAX_R),
- ("Kubera's Hand", 18, DC2ItemCategory.WEAPON_MAX_R),
-
- ("Sigma Breaker", 19, DC2ItemCategory.WEAPON_MAX_L),
- ("Grade 0", 20, DC2ItemCategory.WEAPON_MAX_L),
- ("LEGEND", 21, DC2ItemCategory.WEAPON_MAX_L),
- ("Classic Gun", 22, DC2ItemCategory.WEAPON_MAX_L),
- ("Dryer Gun", 23, DC2ItemCategory.WEAPON_MAX_L),
- ("Trumpet Gun", 24, DC2ItemCategory.WEAPON_MAX_L),
- ("Bell Trigger", 25, DC2ItemCategory.WEAPON_MAX_L),
- ("Magic Gun", 26, DC2ItemCategory.WEAPON_MAX_L),
- ("Soul Breaker", 27, DC2ItemCategory.WEAPON_MAX_L),
- ("Grenade Launcher", 28, DC2ItemCategory.WEAPON_MAX_L),
- ("Dark Viper", 29, DC2ItemCategory.WEAPON_MAX_L),
- ("Twin Buster", 30, DC2ItemCategory.WEAPON_MAX_L),
- ("Jurak Gun", 31, DC2ItemCategory.WEAPON_MAX_L),
- ("Question Shooter", 32, DC2ItemCategory.WEAPON_MAX_L),
- ("Steal Gun", 33, DC2ItemCategory.WEAPON_MAX_L),
- ("Supernova", 34, DC2ItemCategory.WEAPON_MAX_L),
- ("Star Breaker", 35, DC2ItemCategory.WEAPON_MAX_L),
- ("Wild Cat", 36, DC2ItemCategory.WEAPON_MAX_L),
- ("Sexy Panther", 37, DC2ItemCategory.WEAPON_MAX_L),
- ("Desperado", 38, DC2ItemCategory.WEAPON_MAX_L),
- ("Sigma Bazooka", 39, DC2ItemCategory.WEAPON_MAX_L),
- ("Last Resort", 40, DC2ItemCategory.WEAPON_MAX_L),
-
- ("Long Sword", 41, DC2ItemCategory.WEAPON_MONICA_R),
- ("Broad Sword", 42, DC2ItemCategory.WEAPON_MONICA_R),
- ("Baselard", 43, DC2ItemCategory.WEAPON_MONICA_R),
- ("Gladius", 44, DC2ItemCategory.WEAPON_MONICA_R),
- ("Wise Owl Sword", 45, DC2ItemCategory.WEAPON_MONICA_R),
- ("Cliff Knife", 46, DC2ItemCategory.WEAPON_MONICA_R),
- ("Antique Sword", 47, DC2ItemCategory.WEAPON_MONICA_R),
- ("Bastard Sword", 48, DC2ItemCategory.WEAPON_MONICA_R),
- ("Kitchen Knife", 49, DC2ItemCategory.WEAPON_MONICA_R),
- ("Tsukikage", 50, DC2ItemCategory.WEAPON_MONICA_R),
- ("Sun Sword", 51, DC2ItemCategory.WEAPON_MONICA_R),
- ("Serpent Slicer", 52, DC2ItemCategory.WEAPON_MONICA_R),
- ("null", 53, DC2ItemCategory.REPLACE),
- ("Shamshir", 54, DC2ItemCategory.WEAPON_MONICA_R),
- ("Ama No Murakumo", 55, DC2ItemCategory.WEAPON_MONICA_R),
- ("Lamb's Sword", 56, DC2ItemCategory.WEAPON_MONICA_R),
- ("Dark Cloud", 57, DC2ItemCategory.WEAPON_MONICA_R),
- ("Brave Ark", 58, DC2ItemCategory.WEAPON_MONICA_R),
- ("Big Bang", 59, DC2ItemCategory.WEAPON_MONICA_R),
- ("Atlamillia Sword", 60, DC2ItemCategory.WEAPON_MONICA_R),
- ("Mardan Sword", 61, DC2ItemCategory.WEAPON_MONICA_R),
- ("Garayan Sword", 62, DC2ItemCategory.WEAPON_MONICA_R),
- ("Mardan Garayan", 63, DC2ItemCategory.WEAPON_MONICA_R),
- ("Ruler's Sword", 64, DC2ItemCategory.WEAPON_MONICA_R),
- ("Evilcise", 65, DC2ItemCategory.WEAPON_MONICA_R),
- ("Small Sword", 66, DC2ItemCategory.WEAPON_MONICA_R),
- ("Sand Breaker", 67, DC2ItemCategory.WEAPON_MONICA_R),
- ("Drain Seeker", 68, DC2ItemCategory.WEAPON_MONICA_R),
- ("Chopper", 69, DC2ItemCategory.WEAPON_MONICA_R),
- ("Choora", 70, DC2ItemCategory.WEAPON_MONICA_R),
- ("Claymore", 71, DC2ItemCategory.WEAPON_MONICA_R),
- ("Maneater", 72, DC2ItemCategory.WEAPON_MONICA_R),
- ("Bone Rapier", 73, DC2ItemCategory.WEAPON_MONICA_R),
- ("Sax", 74, DC2ItemCategory.WEAPON_MONICA_R),
- ("7 Branch Sword", 75, DC2ItemCategory.WEAPON_MONICA_R),
- ("Dusack", 76, DC2ItemCategory.WEAPON_MONICA_R),
- ("Cross Heinder", 77, DC2ItemCategory.WEAPON_MONICA_R),
- ("7th Heaven", 78, DC2ItemCategory.WEAPON_MONICA_R),
- ("Sword of Zeus", 79, DC2ItemCategory.WEAPON_MONICA_R),
- ("Chronicle Sword", 80, DC2ItemCategory.WEAPON_MONICA_R),
- ("Chronicle 2", 81, DC2ItemCategory.WEAPON_MONICA_R),
- ("Holy Daedalus Blade", 82, DC2ItemCategory.WEAPON_MONICA_R),
- ("Muramasa", 83, DC2ItemCategory.WEAPON_MONICA_R),
- ("Dark Excalibur", 84, DC2ItemCategory.WEAPON_MONICA_R),
- ("Sargatanas", 85, DC2ItemCategory.WEAPON_MONICA_R),
- ("Halloween Blade", 86, DC2ItemCategory.WEAPON_MONICA_R),
- ("Shining Bravado", 87, DC2ItemCategory.WEAPON_MONICA_R),
- ("Island King", 88, DC2ItemCategory.WEAPON_MONICA_R),
- ("Griffon Fork", 89, DC2ItemCategory.WEAPON_MONICA_R),
-
- ("True Battle Wrench", 90, DC2ItemCategory.WEAPON_MAX_R),
-
- ("Magic Brassard", 91, DC2ItemCategory.WEAPON_MONICA_L),
- ("Gold Brassard", 92, DC2ItemCategory.WEAPON_MONICA_L),
- ("Bandit Brassard", 93, DC2ItemCategory.WEAPON_MONICA_L),
- ("Crystal Brassard", 94, DC2ItemCategory.WEAPON_MONICA_L),
- ("Platinum Brassard", 95, DC2ItemCategory.WEAPON_MONICA_L),
- ("Goddess Brassard", 96, DC2ItemCategory.WEAPON_MONICA_L),
- ("Spirit Brassard", 97, DC2ItemCategory.WEAPON_MONICA_L),
- ("Destruction Brassard", 98, DC2ItemCategory.WEAPON_MONICA_L),
- ("Satan Brassard", 99, DC2ItemCategory.WEAPON_MONICA_L),
- ("Athena's Armlet", 100, DC2ItemCategory.WEAPON_MONICA_L),
- ("Mobius Bangle", 101, DC2ItemCategory.WEAPON_MONICA_L),
- ("Angel Shooter", 102, DC2ItemCategory.WEAPON_MONICA_L),
- ("Pocklekul", 103, DC2ItemCategory.WEAPON_MONICA_L),
- ("Thorn Armlet", 104, DC2ItemCategory.WEAPON_MONICA_L),
- ("Star Armlet", 105, DC2ItemCategory.WEAPON_MONICA_L),
- ("Moon Armlet", 106, DC2ItemCategory.WEAPON_MONICA_L),
- ("Sun Armlet", 107, DC2ItemCategory.WEAPON_MONICA_L),
- ("Five-Star Armlet", 108, DC2ItemCategory.WEAPON_MONICA_L),
- ("Love", 109, DC2ItemCategory.WEAPON_MONICA_L),
- ("Royal Sword", 110, DC2ItemCategory.WEAPON_MONICA_L),
-
- ("Hunting Cap", 111, DC2ItemCategory.MISC),
- ("Fashionable Cap", 112, DC2ItemCategory.MISC),
- ("Two-Tone Beret", 113, DC2ItemCategory.MISC),
- ("Maintenance Cap", 114, DC2ItemCategory.MISC),
- ("Explorer's Helmet", 115, DC2ItemCategory.MISC),
- ("Clown Hat", 116, DC2ItemCategory.MISC),
-
- ("Leather Shoes", 117, DC2ItemCategory.MISC),
- ("Wing Shoes", 118, DC2ItemCategory.MISC),
- ("Work Shoes", 119, DC2ItemCategory.MISC),
- ("Dragon Shoes", 120, DC2ItemCategory.MISC),
- ("Clown Shoes", 121, DC2ItemCategory.MISC),
- ("Explorer's Shoes", 122, DC2ItemCategory.MISC),
-
- ("Yellow Ribbon", 123, DC2ItemCategory.MISC),
- ("Striped Ribbon", 124, DC2ItemCategory.MISC),
- ("Zipangu Comb", 125, DC2ItemCategory.MISC),
- ("Swallowtail", 126, DC2ItemCategory.MISC),
- ("Princess Orb", 127, DC2ItemCategory.MISC),
- ("Kitty Bell", 128, DC2ItemCategory.MISC),
-
- ("Knight Boots", 129, DC2ItemCategory.MISC),
- ("Metal Boots", 130, DC2ItemCategory.MISC),
- ("Wing Boots", 131, DC2ItemCategory.MISC),
- ("Spike Boots", 132, DC2ItemCategory.MISC),
- ("Princess Boots", 133, DC2ItemCategory.MISC),
- ("Panther Boots", 134, DC2ItemCategory.MISC),
-
- ("Drum Can Body", 135, DC2ItemCategory.RIDEPOD),
- ("Milk Can Body", 136, DC2ItemCategory.RIDEPOD),
- ("Refrigerator Body", 137, DC2ItemCategory.RIDEPOD),
- ("Wooden Box Body", 138, DC2ItemCategory.RIDEPOD),
- ("Clown Body", 139, DC2ItemCategory.RIDEPOD),
- ("Samurai Body", 140, DC2ItemCategory.RIDEPOD),
- ("Super-Alloy Body", 141, DC2ItemCategory.RIDEPOD),
- ("Sun and moon Armor", 142, DC2ItemCategory.RIDEPOD),
-
- ("null1", 143, DC2ItemCategory.REPLACE),
- ("null2", 144, DC2ItemCategory.REPLACE),
-
- ("Cannonball Arm", 145, DC2ItemCategory.RIDEPOD),
- ("Barrel Cannon", 146, DC2ItemCategory.RIDEPOD),
- ("Drill Arm", 147, DC2ItemCategory.RIDEPOD),
- ("Missile Pod Arm", 148, DC2ItemCategory.RIDEPOD),
- ("Hammer Arm", 149, DC2ItemCategory.RIDEPOD),
- ("Machine Gun Arm", 150, DC2ItemCategory.RIDEPOD),
- ("Clown Hand", 151, DC2ItemCategory.RIDEPOD),
- ("Samurai Arm", 152, DC2ItemCategory.RIDEPOD),
- ("Laser Arm", 153, DC2ItemCategory.RIDEPOD),
- ("Nova Cannon", 154, DC2ItemCategory.RIDEPOD),
-
- ("Iron Leg", 155, DC2ItemCategory.RIDEPOD),
- ("Catterpillar", 156, DC2ItemCategory.RIDEPOD),
- ("Bucket Leg", 157, DC2ItemCategory.RIDEPOD),
- ("Roller Foot", 158, DC2ItemCategory.RIDEPOD),
- ("Buggy", 159, DC2ItemCategory.RIDEPOD),
- ("Propeller Leg", 160, DC2ItemCategory.RIDEPOD),
- ("Multi-feet", 161, DC2ItemCategory.RIDEPOD),
- ("Jet Hover", 162, DC2ItemCategory.RIDEPOD),
- ("Clown Foot", 163, DC2ItemCategory.RIDEPOD),
-
- ("null3", 164, DC2ItemCategory.REPLACE),
-
- ("Energy Pack", 165, DC2ItemCategory.RIDEPOD),
- ("Energy Pack (Barrel)", 166, DC2ItemCategory.RIDEPOD),
- ("Bucket Pack", 167, DC2ItemCategory.RIDEPOD),
- ("Cleaner Pack", 168, DC2ItemCategory.RIDEPOD),
- ("Energy Pack (Urn)", 169, DC2ItemCategory.RIDEPOD),
- ("Triple-Urn Pack", 170, DC2ItemCategory.RIDEPOD),
-
- ("null4", 171, DC2ItemCategory.REPLACE),
-
- ("Monster Notes", 172, DC2ItemCategory.MISC),
-
- ("Dynamite", 173, DC2ItemCategory.CONSUMABLE),
- ("Seal-breaking Scroll", 174, DC2ItemCategory.CONSUMABLE),
-
- ("Flame Crystal", 175, DC2ItemCategory.MATERIAL),
- ("Chill Crystal", 176, DC2ItemCategory.MATERIAL),
- ("Lightning Crystal", 177, DC2ItemCategory.MATERIAL),
- ("Hunter Crystal", 178, DC2ItemCategory.MATERIAL),
- ("Holy Crystal", 179, DC2ItemCategory.MATERIAL),
- ("Destruction Crystal", 180, DC2ItemCategory.MATERIAL),
- ("Wind Crystal", 181, DC2ItemCategory.MATERIAL),
- ("Sea Dragon Crystal", 182, DC2ItemCategory.MATERIAL),
- ("Power Crystal", 183, DC2ItemCategory.MATERIAL),
- ("Protector Crystal", 184, DC2ItemCategory.MATERIAL),
-
- ("[line1]", 185, DC2ItemCategory.SKIP),
-
- ("Garnet", 186, DC2ItemCategory.GEM),
- ("Amethyst", 187, DC2ItemCategory.GEM),
- ("Aquamarine", 188, DC2ItemCategory.GEM),
- ("Diamond", 189, DC2ItemCategory.GEM),
- ("Emerald", 190, DC2ItemCategory.GEM),
- ("Pearl", 191, DC2ItemCategory.GEM),
- ("Ruby", 192, DC2ItemCategory.GEM),
- ("Peridot", 193, DC2ItemCategory.GEM),
- ("Sapphire", 194, DC2ItemCategory.GEM),
- ("Opal", 195, DC2ItemCategory.GEM),
- ("Topaz", 196, DC2ItemCategory.GEM),
- ("Turquoise", 197, DC2ItemCategory.GEM),
- ("Sun Stone", 198, DC2ItemCategory.GEM),
- ("Moon Stone", 199, DC2ItemCategory.GEM),
-
- ("Wealth Coin", 200, DC2ItemCategory.COIN),
- ("Dark Coin", 201, DC2ItemCategory.COIN),
- ("Indestructible Coin", 202, DC2ItemCategory.COIN),
- ("Poison Coin", 203, DC2ItemCategory.COIN),
- ("Time Coin", 204, DC2ItemCategory.COIN),
- ("Bandit Coin", 205, DC2ItemCategory.COIN),
- ("Absorption Coin", 206, DC2ItemCategory.COIN),
- ("Healing Coin", 207, DC2ItemCategory.COIN),
- ("Bull's-eye Coin", 208, DC2ItemCategory.COIN),
- ("Experience Coin", 209, DC2ItemCategory.COIN),
-
- ("Rolling Log", 210, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Sturdy Rock", 211, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Rough Rock", 212, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Bundle of Hay", 213, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Sturdy Cloth", 214, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Gunpowder", 215, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Glass Material", 216, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Unknown Bone", 217, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Sticky Clay", 218, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Flour", 219, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Sugar Cane", 220, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Super Hot Pepper", 221, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Poison", 222, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Forest Dew", 223, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Scrap of Metal", 224, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Gold Bar", 225, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Silver Ball", 226, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Hunk of Copper", 227, DC2ItemCategory.GEORAMA_RESOURCE),
-
-
- ("Light Element", 228, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Holy Element", 229, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Earth Element", 230, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Water Element", 231, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Chill Element", 232, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Thunder Element", 233, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Wind Element", 234, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Fire Element", 235, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Life Element", 236, DC2ItemCategory.GEORAMA_RESOURCE),
-
- ("Paint (Red)", 237, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Blue)", 238, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Black)", 239, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Green)", 240, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Orange)", 241, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Yellow)", 242, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Purple)", 243, DC2ItemCategory.GEORAMA_RESOURCE),
- ("Paint (Pink)", 244, DC2ItemCategory.GEORAMA_RESOURCE),
-
- ("Thick Hide", 245, DC2ItemCategory.GEORAMA_RESOURCE),
-
- ("Core", 246, DC2ItemCategory.MISC),
- ("Improved Core", 247, DC2ItemCategory.MISC),
- ("Core II", 248, DC2ItemCategory.MISC),
- ("Core III", 249, DC2ItemCategory.MISC),
- ("Super Core", 250, DC2ItemCategory.MISC),
- ("Hyper Core", 251, DC2ItemCategory.MISC),
- ("Master grade Core", 252, DC2ItemCategory.MISC),
-
- ("Anti-petrify Amulet", 253, DC2ItemCategory.CONSUMABLE),
- ("Non-stop Amulet", 254, DC2ItemCategory.CONSUMABLE),
- ("Anti-curse Amulet", 255, DC2ItemCategory.CONSUMABLE),
- ("Anti-goo Amulet", 256, DC2ItemCategory.CONSUMABLE),
- ("Antidote Amulet", 257, DC2ItemCategory.CONSUMABLE),
-
- ("Green Overalls", 258, DC2ItemCategory.MISC),
- ("Red Vest", 259, DC2ItemCategory.MISC),
- ("Denim Overalls", 260, DC2ItemCategory.MISC),
- ("Explorer's Outfit", 261, DC2ItemCategory.MISC),
- ("Clown Suit", 262, DC2ItemCategory.MISC),
-
- ("Pumpkin Shorts", 263, DC2ItemCategory.MISC),
- ("Striped Dress", 264, DC2ItemCategory.MISC),
- ("Star Leotard", 265, DC2ItemCategory.MISC),
- ("Princess Dress", 266, DC2ItemCategory.MISC),
- ("Panther Ensemble", 267, DC2ItemCategory.MISC),
-
- ("Bread", 268, DC2ItemCategory.CONSUMABLE),
- ("Cheese", 269, DC2ItemCategory.CONSUMABLE),
- ("Premium Chicken", 270, DC2ItemCategory.CONSUMABLE),
- ("Double Pudding", 271, DC2ItemCategory.CONSUMABLE),
- ("Plum Rice Ball", 272, DC2ItemCategory.CONSUMABLE),
- ("Resurrection Powder", 273, DC2ItemCategory.CONSUMABLE),
- ("Stamina Drink", 274, DC2ItemCategory.CONSUMABLE),
- ("Antidote Drink", 275, DC2ItemCategory.CONSUMABLE),
- ("Holy Water", 276, DC2ItemCategory.CONSUMABLE),
- ("Soap", 277, DC2ItemCategory.CONSUMABLE),
- ("Medusa's Tear", 278, DC2ItemCategory.CONSUMABLE),
- ("Mighty Healing", 279, DC2ItemCategory.CONSUMABLE),
- ("Bomb", 280, DC2ItemCategory.CONSUMABLE),
- ("Stone", 281, DC2ItemCategory.CONSUMABLE),
- ("Flame Stone", 282, DC2ItemCategory.CONSUMABLE),
- ("Chill Stone", 283, DC2ItemCategory.CONSUMABLE),
- ("Lightning Stone", 284, DC2ItemCategory.CONSUMABLE),
- ("Wind Stone", 285, DC2ItemCategory.CONSUMABLE),
- ("Holy Stone", 286, DC2ItemCategory.CONSUMABLE),
- ("Heart-throb Cherry", 287, DC2ItemCategory.CONSUMABLE),
- ("Stone Berry", 288, DC2ItemCategory.CONSUMABLE),
- ("Gooey Peach", 289, DC2ItemCategory.CONSUMABLE),
- ("Bomb Nut", 290, DC2ItemCategory.CONSUMABLE),
- ("Poison Apple", 291, DC2ItemCategory.CONSUMABLE),
- ("Mellow Banana", 292, DC2ItemCategory.CONSUMABLE),
- ("Escape Powder", 293, DC2ItemCategory.CONSUMABLE),
- ("Weapon Powder", 294, DC2ItemCategory.CONSUMABLE),
- ("Level up Powder", 295, DC2ItemCategory.CONSUMABLE),
- ("Fruit of Eden", 296, DC2ItemCategory.CONSUMABLE),
- ("Treasure Chest Key", 297, DC2ItemCategory.CONSUMABLE),
- ("Gun Repair Powder", 298, DC2ItemCategory.CONSUMABLE),
- ("Crunchy Bread", 299, DC2ItemCategory.CONSUMABLE),
- ("Crunchy Bread", 300, DC2ItemCategory.CONSUMABLE),
- ("Roasted Chestnut", 301, DC2ItemCategory.CONSUMABLE),
-
-
- ("Fishing Rod", 302, DC2ItemCategory.KEY_ITEM),
- ("Lure Rod", 303, DC2ItemCategory.WEAPON_MAX_R),
-
- ("Gift Capsule", 304, DC2ItemCategory.CONSUMABLE),
-
- ("Map", 305, DC2ItemCategory.MISC),
- ("Magic Crystal", 306, DC2ItemCategory.MISC),
-
- ("Lightspeed", 307, DC2ItemCategory.CONSUMABLE),
-
- ("Badge Box", 308, DC2ItemCategory.MISC),
- ("Aquarium", 309, DC2ItemCategory.MISC),
-
- ("Medal Holder", 311, DC2ItemCategory.MISC),
-
- ("Priscleen", 310, DC2ItemCategory.MISC),
-
- ("Bobo", 320, DC2ItemCategory.MISC),
- ("Gobbler", 321, DC2ItemCategory.MISC),
- ("Nonky", 322, DC2ItemCategory.MISC),
- ("Kaji", 323, DC2ItemCategory.MISC),
- ("Baku Baku", 324, DC2ItemCategory.MISC),
- ("Mardan Garayan", 325, DC2ItemCategory.MISC),
- ("Gummy", 326, DC2ItemCategory.MISC),
- ("Niler", 327, DC2ItemCategory.MISC),
- ("Umadakara", 328, DC2ItemCategory.MISC),
- ("Tarton", 329, DC2ItemCategory.MISC),
- ("Piccoly", 330, DC2ItemCategory.MISC),
- ("Bon", 331, DC2ItemCategory.MISC),
- ("Hama Hama", 332, DC2ItemCategory.MISC),
- ("Negie", 333, DC2ItemCategory.MISC),
- ("Den", 334, DC2ItemCategory.MISC),
- ("Heela", 335, DC2ItemCategory.MISC),
- ("Baron Garayan", 336, DC2ItemCategory.MISC),
-
- ("Prickly", 312, DC2ItemCategory.MISC),
- ("Mimi", 313, DC2ItemCategory.MISC),
- ("Evy", 314, DC2ItemCategory.MISC),
- ("Carrot", 315, DC2ItemCategory.MISC),
- ("Potato Cake", 316, DC2ItemCategory.MISC),
- ("Minon", 317, DC2ItemCategory.MISC),
- ("Battan", 318, DC2ItemCategory.MISC),
- ("Petite Fish", 319, DC2ItemCategory.MISC),
-
- ("Key Handle", 337, DC2ItemCategory.GATE_KEY),
- ("Channel Key", 338, DC2ItemCategory.GATE_KEY),
- ("Fairy Saw", 339, DC2ItemCategory.GATE_KEY),
- ("Slash Branch", 340, DC2ItemCategory.GATE_KEY),
- ("Giant Meat", 341, DC2ItemCategory.GATE_KEY),
- ("Luna Stone", 342, DC2ItemCategory.GATE_KEY),
- ("Luna Stone Piece", 343, DC2ItemCategory.GATE_KEY),
- ("Magma Rock", 344, DC2ItemCategory.GATE_KEY),
- ("Rope", 345, DC2ItemCategory.GATE_KEY),
- ("Stone 'T'", 346, DC2ItemCategory.GATE_KEY),
- ("White Wind Vase", 347, DC2ItemCategory.GATE_KEY),
- ("Queen's Watering Pot", 348, DC2ItemCategory.GATE_KEY),
- ("Moon Clock Hand", 349, DC2ItemCategory.GATE_KEY),
- ("Trolley Oil", 350, DC2ItemCategory.GATE_KEY),
- ("Rusted Key", 351, DC2ItemCategory.GATE_KEY),
-
- ("Armband Repair Powder", 352, DC2ItemCategory.CONSUMABLE),
-
- ("Circus Ticket", 353, DC2ItemCategory.MISC),
- ("Fire Horn", 354, DC2ItemCategory.KEY_ITEM),
- ("Inside Scoop Memo", 355, DC2ItemCategory.MISC),
- ("Sundrop", 356, DC2ItemCategory.KEY_ITEM),
- ("Photo Album", 357, DC2ItemCategory.MISC),
- ("Cooking Stove", 358, DC2ItemCategory.MISC),
- ("Help Receiver", 359, DC2ItemCategory.MISC),
- ("Electric Worm", 360, DC2ItemCategory.KEY_ITEM),
- ("Lafrescia Seed", 361, DC2ItemCategory.KEY_ITEM),
- ("Star Key", 362, DC2ItemCategory.KEY_ITEM),
- ("White Windflower", 363, DC2ItemCategory.KEY_ITEM),
- ("Miracle Dumplings", 364, DC2ItemCategory.KEY_ITEM),
- ("Earth Gem", 365, DC2ItemCategory.KEY_ITEM),
- ("Water Gem", 366, DC2ItemCategory.KEY_ITEM),
- ("Wind Gem", 367, DC2ItemCategory.KEY_ITEM),
- ("Fire Gem", 368, DC2ItemCategory.KEY_ITEM),
-
- ("Camera", 369, DC2ItemCategory.MISC),
-
- ("Grape Juice", 370, DC2ItemCategory.KEY_ITEM),
- ("Starglass", 371, DC2ItemCategory.KEY_ITEM),
- ("Time Bomb", 372, DC2ItemCategory.KEY_ITEM),
- ("Shell Talkie", 373, DC2ItemCategory.KEY_ITEM),
- ("Flower of the Sun", 374, DC2ItemCategory.KEY_ITEM),
- ("Secret Dragon Remedy", 375, DC2ItemCategory.KEY_ITEM),
-
- ("Gold Paint", 376, DC2ItemCategory.MISC),
- ("Spinner", 377, DC2ItemCategory.MISC),
- ("Frog", 378, DC2ItemCategory.MISC),
- ("Minnow", 379, DC2ItemCategory.MISC),
- ("Fork", 380, DC2ItemCategory.MISC),
-
- ("Ridepod Fuel", 381, DC2ItemCategory.CONSUMABLE),
-
- ("Wrench", 382, DC2ItemCategory.MISC),
- ("Monster Drop", 383, DC2ItemCategory.MISC),
- ("Name-change Ticket", 384, DC2ItemCategory.MISC),
- ("Teal Envelope", 385, DC2ItemCategory.MISC),
- ("Notebook", 386, DC2ItemCategory.MISC),
- ("Wrench", 387, DC2ItemCategory.MISC),
-
- ("Potato Pie", 388, DC2ItemCategory.CONSUMABLE),
- ("Witch Parfait", 389, DC2ItemCategory.CONSUMABLE),
-
- ("Improved Bomb", 390, DC2ItemCategory.CONSUMABLE),
- ("Final Bomb", 391, DC2ItemCategory.CONSUMABLE),
-
- ("Cannonball Arm II", 392, DC2ItemCategory.RIDEPOD),
- ("Cannonball Arm III", 393, DC2ItemCategory.RIDEPOD),
- ("Cannonball Arm IV", 394, DC2ItemCategory.RIDEPOD),
- ("Barrel Cannon II", 395, DC2ItemCategory.RIDEPOD),
- ("Barrel Cannon III", 396, DC2ItemCategory.RIDEPOD),
- ("Barrel Cannon IV", 397, DC2ItemCategory.RIDEPOD),
- ("Drill Arm II", 398, DC2ItemCategory.RIDEPOD),
- ("Drill Arm III", 399, DC2ItemCategory.RIDEPOD),
- ("Drill Arm IV", 400, DC2ItemCategory.RIDEPOD),
- ("Missile Pod Arm II", 401, DC2ItemCategory.RIDEPOD),
- ("Missile Pod Arm III", 402, DC2ItemCategory.RIDEPOD),
- ("Missile Pod Arm IV", 403, DC2ItemCategory.RIDEPOD),
- ("Hammer Arm II", 404, DC2ItemCategory.RIDEPOD),
- ("Hammer Arm III", 405, DC2ItemCategory.RIDEPOD),
- ("Hammer Arm IV", 406, DC2ItemCategory.RIDEPOD),
- ("Machine Gun Arm II", 407, DC2ItemCategory.RIDEPOD),
- ("Machine Gun Arm III", 408, DC2ItemCategory.RIDEPOD),
- ("Machine Gun Arm IV", 409, DC2ItemCategory.RIDEPOD),
- ("Clown Hand II", 410, DC2ItemCategory.RIDEPOD),
- ("Clown Hand III", 411, DC2ItemCategory.RIDEPOD),
- ("Clown Hand IV", 412, DC2ItemCategory.RIDEPOD),
- ("Samurai Arm II", 413, DC2ItemCategory.RIDEPOD),
- ("Samurai Arm III", 414, DC2ItemCategory.RIDEPOD),
- ("Samurai Arm IV", 415, DC2ItemCategory.RIDEPOD),
- ("Laser Arm II", 416, DC2ItemCategory.RIDEPOD),
- ("Laser Arm III", 417, DC2ItemCategory.RIDEPOD),
- ("Laser Arm IV", 418, DC2ItemCategory.RIDEPOD),
- ("Nova Cannon II", 419, DC2ItemCategory.RIDEPOD),
- ("Nova Cannon III", 420, DC2ItemCategory.RIDEPOD),
- ("Nova Cannon IV", 421, DC2ItemCategory.RIDEPOD),
- ("Voice Unit", 422, DC2ItemCategory.MISC),
- ("Shield Kit", 423, DC2ItemCategory.CONSUMABLE),
-
- ("Himarra Badge", 424, DC2ItemCategory.MISC),
-
- ("Tasty Water", 425, DC2ItemCategory.CONSUMABLE),
-
- ("null5", 426, DC2ItemCategory.REPLACE),
-
- ("Sun Badge", 427, DC2ItemCategory.MISC),
- ("Moon Badge", 428, DC2ItemCategory.MISC),
-
-
-
- ("Chapter 1 Complete", 1000, DC2ItemCategory.EVENT),
- ("Chapter 2 Complete", 1001, DC2ItemCategory.EVENT),
- ("Chapter 3 Complete", 1002, DC2ItemCategory.EVENT),
- ("Chapter 4 Complete", 1003, DC2ItemCategory.EVENT),
- ("Chapter 5 Complete", 1004, DC2ItemCategory.EVENT),
-]]
-
-item_descriptions = {
-}
-
-item_dictionary = {item_data.name: item_data for item_data in _all_items}
-
-def BuildItemPool(multiworld: MultiWorld, count, options):
- item_pool = []
- remaining_count = count
-
- if options.guaranteed_items.value:
- for item_name, item_quant in options.guaranteed_items.value.items():
- item = item_dictionary[item_name]
- item_pool += [item] * item_quant
- remaining_count = remaining_count - 1
-
- key_items = [item for item in _all_items if item.name in key_item_names or item.category == DC2ItemCategory.KEY_ITEM]
- for item in key_items:
- item_pool.append(item)
- remaining_count = remaining_count - 1
-
- consumable_items = [item for item in _all_items if item.category in [DC2ItemCategory.CONSUMABLE, item.category == DC2ItemCategory.MATERIAL, item.category == DC2ItemCategory.GEM, item.category == DC2ItemCategory.COIN]]
- georama_items = [item for item in _all_items if item.category == DC2ItemCategory.GEORAMA_RESOURCE]
- weapon_items = [item for item in _all_items if item.category in [DC2ItemCategory.WEAPON_MAX_L, DC2ItemCategory.WEAPON_MAX_R, DC2ItemCategory.WEAPON_MONICA_L, DC2ItemCategory.WEAPON_MONICA_R, DC2ItemCategory.RIDEPOD]]
-
- for i in range(int(remaining_count * 0.9)):
- item = multiworld.random.choice(consumable_items)
- item_pool.append(item)
- remaining_count = remaining_count - 1
-
- for i in range(int(remaining_count * 0.5)):
- item = multiworld.random.choice(georama_items)
- item_pool.append(item)
- remaining_count = remaining_count - 1
-
- for i in range(remaining_count):
- item = multiworld.random.choice(weapon_items)
- item_pool.append(item)
- multiworld.random.shuffle(item_pool)
- return item_pool
+from enum import IntEnum
+from Options import OptionError
+from typing import NamedTuple
+from BaseClasses import Item, MultiWorld
+
+
+class DC2ItemCategory(IntEnum):
+ WEAPON_MAX_L = 0
+ WEAPON_MAX_R = 1
+ WEAPON_MONICA_L = 2
+ WEAPON_MONICA_R = 3
+ CONSUMABLE = 4
+ GATE_KEY = 5
+ GEORAMA_RESOURCE = 6
+ GEOSTONE = 7
+ MISC = 8,
+ SKIP = 9,
+ COIN = 10,
+ GEM = 11,
+ KEY_ITEM = 12
+ EVENT = 13,
+ REPLACE = 14,
+ RIDEPOD = 15,
+ MATERIAL = 16,
+ REWARD = 17
+
+
+class DC2ItemData(NamedTuple):
+ name: str
+ dc2_code: int
+ category: DC2ItemCategory
+
+
+class DarkCloud2Item(Item):
+ game: str = "Dark Cloud 2"
+
+ @staticmethod
+ def get_name_to_id() -> dict:
+ base_id = 694200000
+ return {item_data.name: (base_id + item_data.dc2_code if item_data.dc2_code is not None else None) for item_data in _all_items}
+
+
+key_item_names = {
+}
+
+
+_all_items = [DC2ItemData(row[0], row[1], row[2]) for row in [
+ ("null", 0, DC2ItemCategory.REPLACE),
+
+ ("Battle Wrench", 1, DC2ItemCategory.WEAPON_MAX_R),
+ ("Drill Wrench", 2, DC2ItemCategory.WEAPON_MAX_R),
+ ("Smash Wrench", 3, DC2ItemCategory.WEAPON_MAX_R),
+ ("Stinger Wrench", 4, DC2ItemCategory.WEAPON_MAX_R),
+ ("Poison Wrench", 5, DC2ItemCategory.WEAPON_MAX_R),
+ ("Cubic Hammer", 6, DC2ItemCategory.WEAPON_MAX_R),
+ ("Digi Hammer", 7, DC2ItemCategory.WEAPON_MAX_R),
+ ("Heavy Hammer", 8, DC2ItemCategory.WEAPON_MAX_R),
+ ("Handy Stick", 9, DC2ItemCategory.WEAPON_MAX_R),
+ ("Turkey", 10, DC2ItemCategory.WEAPON_MAX_R),
+ ("Swan", 11, DC2ItemCategory.WEAPON_MAX_R),
+ ("Flamingo", 12, DC2ItemCategory.WEAPON_MAX_R),
+ ("Falcon", 13, DC2ItemCategory.WEAPON_MAX_R),
+ ("Albatross", 14, DC2ItemCategory.WEAPON_MAX_R),
+ ("Turtle Shell Hammer", 15, DC2ItemCategory.WEAPON_MAX_R),
+ ("Big Bucks Hammer", 16, DC2ItemCategory.WEAPON_MAX_R),
+ ("Frozen Tuna", 17, DC2ItemCategory.WEAPON_MAX_R),
+ ("Kubera's Hand", 18, DC2ItemCategory.WEAPON_MAX_R),
+
+ ("Sigma Breaker", 19, DC2ItemCategory.WEAPON_MAX_L),
+ ("Grade 0", 20, DC2ItemCategory.WEAPON_MAX_L),
+ ("LEGEND", 21, DC2ItemCategory.WEAPON_MAX_L),
+ ("Classic Gun", 22, DC2ItemCategory.WEAPON_MAX_L),
+ ("Dryer Gun", 23, DC2ItemCategory.WEAPON_MAX_L),
+ ("Trumpet Gun", 24, DC2ItemCategory.WEAPON_MAX_L),
+ ("Bell Trigger", 25, DC2ItemCategory.WEAPON_MAX_L),
+ ("Magic Gun", 26, DC2ItemCategory.WEAPON_MAX_L),
+ ("Soul Breaker", 27, DC2ItemCategory.WEAPON_MAX_L),
+ ("Grenade Launcher", 28, DC2ItemCategory.WEAPON_MAX_L),
+ ("Dark Viper", 29, DC2ItemCategory.WEAPON_MAX_L),
+ ("Twin Buster", 30, DC2ItemCategory.WEAPON_MAX_L),
+ ("Jurak Gun", 31, DC2ItemCategory.WEAPON_MAX_L),
+ ("Question Shooter", 32, DC2ItemCategory.WEAPON_MAX_L),
+ ("Steal Gun", 33, DC2ItemCategory.WEAPON_MAX_L),
+ ("Supernova", 34, DC2ItemCategory.WEAPON_MAX_L),
+ ("Star Breaker", 35, DC2ItemCategory.WEAPON_MAX_L),
+ ("Wild Cat", 36, DC2ItemCategory.WEAPON_MAX_L),
+ ("Sexy Panther", 37, DC2ItemCategory.WEAPON_MAX_L),
+ ("Desperado", 38, DC2ItemCategory.WEAPON_MAX_L),
+ ("Sigma Bazooka", 39, DC2ItemCategory.WEAPON_MAX_L),
+ ("Last Resort", 40, DC2ItemCategory.WEAPON_MAX_L),
+
+ ("Long Sword", 41, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Broad Sword", 42, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Baselard", 43, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Gladius", 44, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Wise Owl Sword", 45, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Cliff Knife", 46, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Antique Sword", 47, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Bastard Sword", 48, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Kitchen Knife", 49, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Tsukikage", 50, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Sun Sword", 51, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Serpent Slicer", 52, DC2ItemCategory.WEAPON_MONICA_R),
+ ("null", 53, DC2ItemCategory.REPLACE),
+ ("Shamshir", 54, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Ama No Murakumo", 55, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Lamb's Sword", 56, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Dark Cloud", 57, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Brave Ark", 58, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Big Bang", 59, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Atlamillia Sword", 60, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Mardan Sword", 61, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Garayan Sword", 62, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Mardan Garayan", 63, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Ruler's Sword", 64, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Evilcise", 65, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Small Sword", 66, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Sand Breaker", 67, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Drain Seeker", 68, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Chopper", 69, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Choora", 70, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Claymore", 71, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Maneater", 72, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Bone Rapier", 73, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Sax", 74, DC2ItemCategory.WEAPON_MONICA_R),
+ ("7 Branch Sword", 75, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Dusack", 76, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Cross Heinder", 77, DC2ItemCategory.WEAPON_MONICA_R),
+ ("7th Heaven", 78, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Sword of Zeus", 79, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Chronicle Sword", 80, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Chronicle 2", 81, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Holy Daedalus Blade", 82, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Muramasa", 83, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Dark Excalibur", 84, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Sargatanas", 85, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Halloween Blade", 86, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Shining Bravado", 87, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Island King", 88, DC2ItemCategory.WEAPON_MONICA_R),
+ ("Griffon Fork", 89, DC2ItemCategory.WEAPON_MONICA_R),
+
+ ("True Battle Wrench", 90, DC2ItemCategory.WEAPON_MAX_R),
+
+ ("Magic Brassard", 91, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Gold Brassard", 92, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Bandit Brassard", 93, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Crystal Brassard", 94, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Platinum Brassard", 95, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Goddess Brassard", 96, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Spirit Brassard", 97, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Destruction Brassard", 98, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Satan Brassard", 99, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Athena's Armlet", 100, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Mobius Bangle", 101, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Angel Shooter", 102, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Pocklekul", 103, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Thorn Armlet", 104, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Star Armlet", 105, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Moon Armlet", 106, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Sun Armlet", 107, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Five-Star Armlet", 108, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Love", 109, DC2ItemCategory.WEAPON_MONICA_L),
+ ("Royal Sword", 110, DC2ItemCategory.WEAPON_MONICA_L),
+
+ ("Hunting Cap", 111, DC2ItemCategory.MISC),
+ ("Fashionable Cap", 112, DC2ItemCategory.MISC),
+ ("Two-Tone Beret", 113, DC2ItemCategory.MISC),
+ ("Maintenance Cap", 114, DC2ItemCategory.MISC),
+ ("Explorer's Helmet", 115, DC2ItemCategory.MISC),
+ ("Clown Hat", 116, DC2ItemCategory.MISC),
+
+ ("Leather Shoes", 117, DC2ItemCategory.MISC),
+ ("Wing Shoes", 118, DC2ItemCategory.MISC),
+ ("Work Shoes", 119, DC2ItemCategory.MISC),
+ ("Dragon Shoes", 120, DC2ItemCategory.MISC),
+ ("Clown Shoes", 121, DC2ItemCategory.MISC),
+ ("Explorer's Shoes", 122, DC2ItemCategory.MISC),
+
+ ("Yellow Ribbon", 123, DC2ItemCategory.MISC),
+ ("Striped Ribbon", 124, DC2ItemCategory.MISC),
+ ("Zipangu Comb", 125, DC2ItemCategory.MISC),
+ ("Swallowtail", 126, DC2ItemCategory.MISC),
+ ("Princess Orb", 127, DC2ItemCategory.MISC),
+ ("Kitty Bell", 128, DC2ItemCategory.MISC),
+
+ ("Knight Boots", 129, DC2ItemCategory.MISC),
+ ("Metal Boots", 130, DC2ItemCategory.MISC),
+ ("Wing Boots", 131, DC2ItemCategory.MISC),
+ ("Spike Boots", 132, DC2ItemCategory.MISC),
+ ("Princess Boots", 133, DC2ItemCategory.MISC),
+ ("Panther Boots", 134, DC2ItemCategory.MISC),
+
+ ("Drum Can Body", 135, DC2ItemCategory.RIDEPOD),
+ ("Milk Can Body", 136, DC2ItemCategory.RIDEPOD),
+ ("Refrigerator Body", 137, DC2ItemCategory.RIDEPOD),
+ ("Wooden Box Body", 138, DC2ItemCategory.RIDEPOD),
+ ("Clown Body", 139, DC2ItemCategory.RIDEPOD),
+ ("Samurai Body", 140, DC2ItemCategory.RIDEPOD),
+ ("Super-Alloy Body", 141, DC2ItemCategory.RIDEPOD),
+ ("Sun and moon Armor", 142, DC2ItemCategory.RIDEPOD),
+
+ ("null1", 143, DC2ItemCategory.REPLACE),
+ ("null2", 144, DC2ItemCategory.REPLACE),
+
+ ("Cannonball Arm", 145, DC2ItemCategory.RIDEPOD),
+ ("Barrel Cannon", 146, DC2ItemCategory.RIDEPOD),
+ ("Drill Arm", 147, DC2ItemCategory.RIDEPOD),
+ ("Missile Pod Arm", 148, DC2ItemCategory.RIDEPOD),
+ ("Hammer Arm", 149, DC2ItemCategory.RIDEPOD),
+ ("Machine Gun Arm", 150, DC2ItemCategory.RIDEPOD),
+ ("Clown Hand", 151, DC2ItemCategory.RIDEPOD),
+ ("Samurai Arm", 152, DC2ItemCategory.RIDEPOD),
+ ("Laser Arm", 153, DC2ItemCategory.RIDEPOD),
+ ("Nova Cannon", 154, DC2ItemCategory.RIDEPOD),
+
+ ("Iron Leg", 155, DC2ItemCategory.RIDEPOD),
+ ("Catterpillar", 156, DC2ItemCategory.RIDEPOD),
+ ("Bucket Leg", 157, DC2ItemCategory.RIDEPOD),
+ ("Roller Foot", 158, DC2ItemCategory.RIDEPOD),
+ ("Buggy", 159, DC2ItemCategory.RIDEPOD),
+ ("Propeller Leg", 160, DC2ItemCategory.RIDEPOD),
+ ("Multi-feet", 161, DC2ItemCategory.RIDEPOD),
+ ("Jet Hover", 162, DC2ItemCategory.RIDEPOD),
+ ("Clown Foot", 163, DC2ItemCategory.RIDEPOD),
+
+ ("null3", 164, DC2ItemCategory.REPLACE),
+
+ ("Energy Pack", 165, DC2ItemCategory.RIDEPOD),
+ ("Energy Pack (Barrel)", 166, DC2ItemCategory.RIDEPOD),
+ ("Bucket Pack", 167, DC2ItemCategory.RIDEPOD),
+ ("Cleaner Pack", 168, DC2ItemCategory.RIDEPOD),
+ ("Energy Pack (Urn)", 169, DC2ItemCategory.RIDEPOD),
+ ("Triple-Urn Pack", 170, DC2ItemCategory.RIDEPOD),
+
+ ("null4", 171, DC2ItemCategory.REPLACE),
+
+ ("Monster Notes", 172, DC2ItemCategory.MISC),
+
+ ("Dynamite", 173, DC2ItemCategory.CONSUMABLE),
+ ("Seal-breaking Scroll", 174, DC2ItemCategory.CONSUMABLE),
+
+ ("Flame Crystal", 175, DC2ItemCategory.MATERIAL),
+ ("Chill Crystal", 176, DC2ItemCategory.MATERIAL),
+ ("Lightning Crystal", 177, DC2ItemCategory.MATERIAL),
+ ("Hunter Crystal", 178, DC2ItemCategory.MATERIAL),
+ ("Holy Crystal", 179, DC2ItemCategory.MATERIAL),
+ ("Destruction Crystal", 180, DC2ItemCategory.MATERIAL),
+ ("Wind Crystal", 181, DC2ItemCategory.MATERIAL),
+ ("Sea Dragon Crystal", 182, DC2ItemCategory.MATERIAL),
+ ("Power Crystal", 183, DC2ItemCategory.MATERIAL),
+ ("Protector Crystal", 184, DC2ItemCategory.MATERIAL),
+
+ ("[line1]", 185, DC2ItemCategory.SKIP),
+
+ ("Garnet", 186, DC2ItemCategory.GEM),
+ ("Amethyst", 187, DC2ItemCategory.GEM),
+ ("Aquamarine", 188, DC2ItemCategory.GEM),
+ ("Diamond", 189, DC2ItemCategory.GEM),
+ ("Emerald", 190, DC2ItemCategory.GEM),
+ ("Pearl", 191, DC2ItemCategory.GEM),
+ ("Ruby", 192, DC2ItemCategory.GEM),
+ ("Peridot", 193, DC2ItemCategory.GEM),
+ ("Sapphire", 194, DC2ItemCategory.GEM),
+ ("Opal", 195, DC2ItemCategory.GEM),
+ ("Topaz", 196, DC2ItemCategory.GEM),
+ ("Turquoise", 197, DC2ItemCategory.GEM),
+ ("Sun Stone", 198, DC2ItemCategory.GEM),
+ ("Moon Stone", 199, DC2ItemCategory.GEM),
+
+ ("Wealth Coin", 200, DC2ItemCategory.COIN),
+ ("Dark Coin", 201, DC2ItemCategory.COIN),
+ ("Indestructible Coin", 202, DC2ItemCategory.COIN),
+ ("Poison Coin", 203, DC2ItemCategory.COIN),
+ ("Time Coin", 204, DC2ItemCategory.COIN),
+ ("Bandit Coin", 205, DC2ItemCategory.COIN),
+ ("Absorption Coin", 206, DC2ItemCategory.COIN),
+ ("Healing Coin", 207, DC2ItemCategory.COIN),
+ ("Bull's-eye Coin", 208, DC2ItemCategory.COIN),
+ ("Experience Coin", 209, DC2ItemCategory.COIN),
+
+ ("Rolling Log", 210, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Sturdy Rock", 211, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Rough Rock", 212, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Bundle of Hay", 213, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Sturdy Cloth", 214, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Gunpowder", 215, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Glass Material", 216, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Unknown Bone", 217, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Sticky Clay", 218, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Flour", 219, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Sugar Cane", 220, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Super Hot Pepper", 221, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Poison", 222, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Forest Dew", 223, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Scrap of Metal", 224, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Gold Bar", 225, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Silver Ball", 226, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Hunk of Copper", 227, DC2ItemCategory.GEORAMA_RESOURCE),
+
+
+ ("Light Element", 228, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Holy Element", 229, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Earth Element", 230, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Water Element", 231, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Chill Element", 232, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Thunder Element", 233, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Wind Element", 234, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Fire Element", 235, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Life Element", 236, DC2ItemCategory.GEORAMA_RESOURCE),
+
+ ("Paint (Red)", 237, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Blue)", 238, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Black)", 239, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Green)", 240, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Orange)", 241, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Yellow)", 242, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Purple)", 243, DC2ItemCategory.GEORAMA_RESOURCE),
+ ("Paint (Pink)", 244, DC2ItemCategory.GEORAMA_RESOURCE),
+
+ ("Thick Hide", 245, DC2ItemCategory.GEORAMA_RESOURCE),
+
+ ("Core", 246, DC2ItemCategory.MISC),
+ ("Improved Core", 247, DC2ItemCategory.MISC),
+ ("Core II", 248, DC2ItemCategory.MISC),
+ ("Core III", 249, DC2ItemCategory.MISC),
+ ("Super Core", 250, DC2ItemCategory.MISC),
+ ("Hyper Core", 251, DC2ItemCategory.MISC),
+ ("Master grade Core", 252, DC2ItemCategory.MISC),
+
+ ("Anti-petrify Amulet", 253, DC2ItemCategory.CONSUMABLE),
+ ("Non-stop Amulet", 254, DC2ItemCategory.CONSUMABLE),
+ ("Anti-curse Amulet", 255, DC2ItemCategory.CONSUMABLE),
+ ("Anti-goo Amulet", 256, DC2ItemCategory.CONSUMABLE),
+ ("Antidote Amulet", 257, DC2ItemCategory.CONSUMABLE),
+
+ ("Green Overalls", 258, DC2ItemCategory.MISC),
+ ("Red Vest", 259, DC2ItemCategory.MISC),
+ ("Denim Overalls", 260, DC2ItemCategory.MISC),
+ ("Explorer's Outfit", 261, DC2ItemCategory.MISC),
+ ("Clown Suit", 262, DC2ItemCategory.MISC),
+
+ ("Pumpkin Shorts", 263, DC2ItemCategory.MISC),
+ ("Striped Dress", 264, DC2ItemCategory.MISC),
+ ("Star Leotard", 265, DC2ItemCategory.MISC),
+ ("Princess Dress", 266, DC2ItemCategory.MISC),
+ ("Panther Ensemble", 267, DC2ItemCategory.MISC),
+
+ ("Bread", 268, DC2ItemCategory.CONSUMABLE),
+ ("Cheese", 269, DC2ItemCategory.CONSUMABLE),
+ ("Premium Chicken", 270, DC2ItemCategory.CONSUMABLE),
+ ("Double Pudding", 271, DC2ItemCategory.CONSUMABLE),
+ ("Plum Rice Ball", 272, DC2ItemCategory.CONSUMABLE),
+ ("Resurrection Powder", 273, DC2ItemCategory.CONSUMABLE),
+ ("Stamina Drink", 274, DC2ItemCategory.CONSUMABLE),
+ ("Antidote Drink", 275, DC2ItemCategory.CONSUMABLE),
+ ("Holy Water", 276, DC2ItemCategory.CONSUMABLE),
+ ("Soap", 277, DC2ItemCategory.CONSUMABLE),
+ ("Medusa's Tear", 278, DC2ItemCategory.CONSUMABLE),
+ ("Mighty Healing", 279, DC2ItemCategory.CONSUMABLE),
+ ("Bomb", 280, DC2ItemCategory.CONSUMABLE),
+ ("Stone", 281, DC2ItemCategory.CONSUMABLE),
+ ("Flame Stone", 282, DC2ItemCategory.CONSUMABLE),
+ ("Chill Stone", 283, DC2ItemCategory.CONSUMABLE),
+ ("Lightning Stone", 284, DC2ItemCategory.CONSUMABLE),
+ ("Wind Stone", 285, DC2ItemCategory.CONSUMABLE),
+ ("Holy Stone", 286, DC2ItemCategory.CONSUMABLE),
+ ("Heart-throb Cherry", 287, DC2ItemCategory.CONSUMABLE),
+ ("Stone Berry", 288, DC2ItemCategory.CONSUMABLE),
+ ("Gooey Peach", 289, DC2ItemCategory.CONSUMABLE),
+ ("Bomb Nut", 290, DC2ItemCategory.CONSUMABLE),
+ ("Poison Apple", 291, DC2ItemCategory.CONSUMABLE),
+ ("Mellow Banana", 292, DC2ItemCategory.CONSUMABLE),
+ ("Escape Powder", 293, DC2ItemCategory.CONSUMABLE),
+ ("Weapon Powder", 294, DC2ItemCategory.CONSUMABLE),
+ ("Level up Powder", 295, DC2ItemCategory.CONSUMABLE),
+ ("Fruit of Eden", 296, DC2ItemCategory.CONSUMABLE),
+ ("Treasure Chest Key", 297, DC2ItemCategory.CONSUMABLE),
+ ("Gun Repair Powder", 298, DC2ItemCategory.CONSUMABLE),
+ ("Crunchy Bread", 299, DC2ItemCategory.CONSUMABLE),
+ ("Crunchy Bread", 300, DC2ItemCategory.CONSUMABLE),
+ ("Roasted Chestnut", 301, DC2ItemCategory.CONSUMABLE),
+
+
+ ("Fishing Rod", 302, DC2ItemCategory.KEY_ITEM),
+ ("Lure Rod", 303, DC2ItemCategory.WEAPON_MAX_R),
+
+ ("Gift Capsule", 304, DC2ItemCategory.CONSUMABLE),
+
+ ("Map", 305, DC2ItemCategory.MISC),
+ ("Magic Crystal", 306, DC2ItemCategory.MISC),
+
+ ("Lightspeed", 307, DC2ItemCategory.CONSUMABLE),
+
+ ("Badge Box", 308, DC2ItemCategory.MISC),
+ ("Aquarium", 309, DC2ItemCategory.MISC),
+
+ ("Medal Holder", 311, DC2ItemCategory.MISC),
+
+ ("Priscleen", 310, DC2ItemCategory.MISC),
+
+ ("Bobo", 320, DC2ItemCategory.MISC),
+ ("Gobbler", 321, DC2ItemCategory.MISC),
+ ("Nonky", 322, DC2ItemCategory.MISC),
+ ("Kaji", 323, DC2ItemCategory.MISC),
+ ("Baku Baku", 324, DC2ItemCategory.MISC),
+ ("Mardan Garayan", 325, DC2ItemCategory.MISC),
+ ("Gummy", 326, DC2ItemCategory.MISC),
+ ("Niler", 327, DC2ItemCategory.MISC),
+ ("Umadakara", 328, DC2ItemCategory.MISC),
+ ("Tarton", 329, DC2ItemCategory.MISC),
+ ("Piccoly", 330, DC2ItemCategory.MISC),
+ ("Bon", 331, DC2ItemCategory.MISC),
+ ("Hama Hama", 332, DC2ItemCategory.MISC),
+ ("Negie", 333, DC2ItemCategory.MISC),
+ ("Den", 334, DC2ItemCategory.MISC),
+ ("Heela", 335, DC2ItemCategory.MISC),
+ ("Baron Garayan", 336, DC2ItemCategory.MISC),
+
+ ("Prickly", 312, DC2ItemCategory.MISC),
+ ("Mimi", 313, DC2ItemCategory.MISC),
+ ("Evy", 314, DC2ItemCategory.MISC),
+ ("Carrot", 315, DC2ItemCategory.MISC),
+ ("Potato Cake", 316, DC2ItemCategory.MISC),
+ ("Minon", 317, DC2ItemCategory.MISC),
+ ("Battan", 318, DC2ItemCategory.MISC),
+ ("Petite Fish", 319, DC2ItemCategory.MISC),
+
+ ("Key Handle", 337, DC2ItemCategory.GATE_KEY),
+ ("Channel Key", 338, DC2ItemCategory.GATE_KEY),
+ ("Fairy Saw", 339, DC2ItemCategory.GATE_KEY),
+ ("Slash Branch", 340, DC2ItemCategory.GATE_KEY),
+ ("Giant Meat", 341, DC2ItemCategory.GATE_KEY),
+ ("Luna Stone", 342, DC2ItemCategory.GATE_KEY),
+ ("Luna Stone Piece", 343, DC2ItemCategory.GATE_KEY),
+ ("Magma Rock", 344, DC2ItemCategory.GATE_KEY),
+ ("Rope", 345, DC2ItemCategory.GATE_KEY),
+ ("Stone 'T'", 346, DC2ItemCategory.GATE_KEY),
+ ("White Wind Vase", 347, DC2ItemCategory.GATE_KEY),
+ ("Queen's Watering Pot", 348, DC2ItemCategory.GATE_KEY),
+ ("Moon Clock Hand", 349, DC2ItemCategory.GATE_KEY),
+ ("Trolley Oil", 350, DC2ItemCategory.GATE_KEY),
+ ("Rusted Key", 351, DC2ItemCategory.GATE_KEY),
+
+ ("Armband Repair Powder", 352, DC2ItemCategory.CONSUMABLE),
+
+ ("Circus Ticket", 353, DC2ItemCategory.MISC),
+ ("Fire Horn", 354, DC2ItemCategory.KEY_ITEM),
+ ("Inside Scoop Memo", 355, DC2ItemCategory.MISC),
+ ("Sundrop", 356, DC2ItemCategory.KEY_ITEM),
+ ("Photo Album", 357, DC2ItemCategory.MISC),
+ ("Cooking Stove", 358, DC2ItemCategory.MISC),
+ ("Help Receiver", 359, DC2ItemCategory.MISC),
+ ("Electric Worm", 360, DC2ItemCategory.KEY_ITEM),
+ ("Lafrescia Seed", 361, DC2ItemCategory.KEY_ITEM),
+ ("Star Key", 362, DC2ItemCategory.KEY_ITEM),
+ ("White Windflower", 363, DC2ItemCategory.KEY_ITEM),
+ ("Miracle Dumplings", 364, DC2ItemCategory.KEY_ITEM),
+ ("Earth Gem", 365, DC2ItemCategory.KEY_ITEM),
+ ("Water Gem", 366, DC2ItemCategory.KEY_ITEM),
+ ("Wind Gem", 367, DC2ItemCategory.KEY_ITEM),
+ ("Fire Gem", 368, DC2ItemCategory.KEY_ITEM),
+
+ ("Camera", 369, DC2ItemCategory.MISC),
+
+ ("Grape Juice", 370, DC2ItemCategory.KEY_ITEM),
+ ("Starglass", 371, DC2ItemCategory.KEY_ITEM),
+ ("Time Bomb", 372, DC2ItemCategory.KEY_ITEM),
+ ("Shell Talkie", 373, DC2ItemCategory.KEY_ITEM),
+ ("Flower of the Sun", 374, DC2ItemCategory.KEY_ITEM),
+ ("Secret Dragon Remedy", 375, DC2ItemCategory.KEY_ITEM),
+
+ ("Gold Paint", 376, DC2ItemCategory.MISC),
+ ("Spinner", 377, DC2ItemCategory.MISC),
+ ("Frog", 378, DC2ItemCategory.MISC),
+ ("Minnow", 379, DC2ItemCategory.MISC),
+ ("Fork", 380, DC2ItemCategory.MISC),
+
+ ("Ridepod Fuel", 381, DC2ItemCategory.CONSUMABLE),
+
+ ("Wrench", 382, DC2ItemCategory.MISC),
+ ("Monster Drop", 383, DC2ItemCategory.MISC),
+ ("Name-change Ticket", 384, DC2ItemCategory.MISC),
+ ("Teal Envelope", 385, DC2ItemCategory.MISC),
+ ("Notebook", 386, DC2ItemCategory.MISC),
+ ("Wrench", 387, DC2ItemCategory.MISC),
+
+ ("Potato Pie", 388, DC2ItemCategory.CONSUMABLE),
+ ("Witch Parfait", 389, DC2ItemCategory.CONSUMABLE),
+
+ ("Improved Bomb", 390, DC2ItemCategory.CONSUMABLE),
+ ("Final Bomb", 391, DC2ItemCategory.CONSUMABLE),
+
+ ("Cannonball Arm II", 392, DC2ItemCategory.RIDEPOD),
+ ("Cannonball Arm III", 393, DC2ItemCategory.RIDEPOD),
+ ("Cannonball Arm IV", 394, DC2ItemCategory.RIDEPOD),
+ ("Barrel Cannon II", 395, DC2ItemCategory.RIDEPOD),
+ ("Barrel Cannon III", 396, DC2ItemCategory.RIDEPOD),
+ ("Barrel Cannon IV", 397, DC2ItemCategory.RIDEPOD),
+ ("Drill Arm II", 398, DC2ItemCategory.RIDEPOD),
+ ("Drill Arm III", 399, DC2ItemCategory.RIDEPOD),
+ ("Drill Arm IV", 400, DC2ItemCategory.RIDEPOD),
+ ("Missile Pod Arm II", 401, DC2ItemCategory.RIDEPOD),
+ ("Missile Pod Arm III", 402, DC2ItemCategory.RIDEPOD),
+ ("Missile Pod Arm IV", 403, DC2ItemCategory.RIDEPOD),
+ ("Hammer Arm II", 404, DC2ItemCategory.RIDEPOD),
+ ("Hammer Arm III", 405, DC2ItemCategory.RIDEPOD),
+ ("Hammer Arm IV", 406, DC2ItemCategory.RIDEPOD),
+ ("Machine Gun Arm II", 407, DC2ItemCategory.RIDEPOD),
+ ("Machine Gun Arm III", 408, DC2ItemCategory.RIDEPOD),
+ ("Machine Gun Arm IV", 409, DC2ItemCategory.RIDEPOD),
+ ("Clown Hand II", 410, DC2ItemCategory.RIDEPOD),
+ ("Clown Hand III", 411, DC2ItemCategory.RIDEPOD),
+ ("Clown Hand IV", 412, DC2ItemCategory.RIDEPOD),
+ ("Samurai Arm II", 413, DC2ItemCategory.RIDEPOD),
+ ("Samurai Arm III", 414, DC2ItemCategory.RIDEPOD),
+ ("Samurai Arm IV", 415, DC2ItemCategory.RIDEPOD),
+ ("Laser Arm II", 416, DC2ItemCategory.RIDEPOD),
+ ("Laser Arm III", 417, DC2ItemCategory.RIDEPOD),
+ ("Laser Arm IV", 418, DC2ItemCategory.RIDEPOD),
+ ("Nova Cannon II", 419, DC2ItemCategory.RIDEPOD),
+ ("Nova Cannon III", 420, DC2ItemCategory.RIDEPOD),
+ ("Nova Cannon IV", 421, DC2ItemCategory.RIDEPOD),
+ ("Voice Unit", 422, DC2ItemCategory.MISC),
+ ("Shield Kit", 423, DC2ItemCategory.CONSUMABLE),
+
+ ("Himarra Badge", 424, DC2ItemCategory.MISC),
+
+ ("Tasty Water", 425, DC2ItemCategory.CONSUMABLE),
+
+ ("null5", 426, DC2ItemCategory.REPLACE),
+
+ ("Sun Badge", 427, DC2ItemCategory.MISC),
+ ("Moon Badge", 428, DC2ItemCategory.MISC),
+
+ ("Chapter 1 Complete", 1000, DC2ItemCategory.EVENT),
+ ("Chapter 2 Complete", 1001, DC2ItemCategory.EVENT),
+ ("Chapter 3 Complete", 1002, DC2ItemCategory.EVENT),
+ ("Chapter 4 Complete", 1003, DC2ItemCategory.EVENT),
+ ("Chapter 5 Complete", 1004, DC2ItemCategory.EVENT),
+
+
+ ("Essential Pack", 2000, DC2ItemCategory.REWARD),
+ ("Gem Pack A", 2001, DC2ItemCategory.REWARD),
+ ("Gem Pack B", 2002, DC2ItemCategory.REWARD),
+ ("Coin Pack A", 2003, DC2ItemCategory.REWARD),
+ ("Coin Pack B", 2004, DC2ItemCategory.REWARD),
+ ("Crystal Pack", 2005, DC2ItemCategory.REWARD),
+]]
+
+item_descriptions = {
+}
+
+item_dictionary = {item_data.name: item_data for item_data in _all_items}
+
+def BuildItemPool(multiworld: MultiWorld, count, options):
+ item_pool = []
+ remaining_count = count
+
+ if options.guaranteed_items.value:
+ for item_name, item_quant in options.guaranteed_items.value.items():
+ item = item_dictionary[item_name]
+ item_pool += [item] * item_quant
+ remaining_count = remaining_count - 1
+
+ key_items = [item for item in _all_items if item.name in key_item_names or item.category == DC2ItemCategory.KEY_ITEM]
+ for item in key_items:
+ item_pool.append(item)
+ remaining_count = remaining_count - 1
+
+ reward_items = [item for item in _all_items if item.category == DC2ItemCategory.REWARD]
+ consumable_items = [item for item in _all_items if item.category in [DC2ItemCategory.CONSUMABLE, item.category == DC2ItemCategory.MATERIAL, item.category == DC2ItemCategory.GEM, item.category == DC2ItemCategory.COIN]]
+ georama_items = [item for item in _all_items if item.category == DC2ItemCategory.GEORAMA_RESOURCE]
+ weapon_items = [item for item in _all_items if item.category in [DC2ItemCategory.WEAPON_MAX_L, DC2ItemCategory.WEAPON_MAX_R, DC2ItemCategory.WEAPON_MONICA_L, DC2ItemCategory.WEAPON_MONICA_R, DC2ItemCategory.RIDEPOD]]
+
+ if options.resource_pack_count.value > 0:
+ if options.resource_pack_count.value > remaining_count:
+ raise OptionError("Resource pack count exceeds remaining item count")
+ for i in range(options.resource_pack_count.value):
+ item = multiworld.random.choice(reward_items)
+ item_pool.append(item)
+ remaining_count = remaining_count - 1
+
+ for i in range(int(remaining_count * 0.9)):
+ item = multiworld.random.choice(consumable_items)
+ item_pool.append(item)
+ remaining_count = remaining_count - 1
+
+ for i in range(int(remaining_count * 0.5)):
+ item = multiworld.random.choice(georama_items)
+ item_pool.append(item)
+ remaining_count = remaining_count - 1
+
+ for i in range(remaining_count):
+ item = multiworld.random.choice(weapon_items)
+ item_pool.append(item)
+ multiworld.random.shuffle(item_pool)
+ return item_pool
diff --git a/worlds/dc2/Locations.py b/worlds/dc2/Locations.py
index 17e5ad170864..62d82d4e6a63 100644
--- a/worlds/dc2/Locations.py
+++ b/worlds/dc2/Locations.py
@@ -1,383 +1,383 @@
-from enum import IntEnum
-from typing import Optional, NamedTuple, Dict
-
-from BaseClasses import Location, Region
-
-
-class DC2LocationCategory(IntEnum):
- FLOOR = 0
- DUNGEON = 1
- RECRUIT = 2
- GEORAMA = 3
- MIRACLE_CHEST = 4
- BOSS = 5
- MISC = 6
- GEOSTONE = 7
- EVENT = 8
- SKIP = 9,
- KEY_ITEM = 10
-
-
-class DC2LocationData(NamedTuple):
- id: int
- name: str
- default_item: str
- category: DC2LocationCategory
-
-
-class DarkCloud2Location(Location):
- game: str = "Dark Cloud 2"
- category: DC2LocationCategory
- default_item_name: str
-
- def __init__(
- self,
- player: int,
- name: str,
- category: DC2LocationCategory,
- 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 = 694200000
- table_offset = 1000
-
- table_order = [
- "Palm Brinks",
- "Underground Water Channel",
- "Sindain",
- "Jurak Mall",
- "Rainbow Butterfly Wood",
- "Balance Valley",
- "Starlight Temple",
- "Starlight Canyon",
- "Veniccio",
- "Lunatic Wisdom Laboratories",
- "Ocean's Roar Cave",
- "Heim Rada",
- "Gundorada Workshop",
- "Mount Gundor",
- "Moon Flower Palace"
- ]
-
- 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: location_data.id for location_data in location_tables[region_name]})
- return output
-
-location_skip_categories = {
-DC2LocationCategory.EVENT, DC2LocationCategory.SKIP
-}
-
-location_tables = {
- "Palm Brinks": [
- DC2LocationData(694201000, "PB: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201001, "PB: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201002, "PB: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- ],
- "Underground Water Channel": [
- DC2LocationData(694202000, "UWC: Floor 1 - To the Outside World", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202001, "UWC: Floor 2 - Battle with Rats", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202002, "UWC: Floor 3 - Ghosts in the Channel", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202003, "UWC: Pump Room", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203000, "UWC: Linda", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202004, "UWC: Floor 4 - Steve's Battle", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202005, "UWC: Floor 5 - Ghost in the Channel", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203001, "UWC: Halloween", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202006, "UWC: Chapter 1 Complete", "Chapter 1 Complete", DC2LocationCategory.EVENT),
- ],
- "Sindain": [
- DC2LocationData(694204000, "S: Grape Juice", "Grape Juice", DC2LocationCategory.KEY_ITEM),
- ],
- "Jurak Mall": [
- DC2LocationData(694201003, "JM: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201004, "JM: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201005, "JM: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201006, "JM: Miracle chest 4", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201007, "JM: Miracle chest 5", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201008, "JM: Miracle chest 6", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201009, "JM: Miracle chest 7", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201010, "JM: Miracle chest 8", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201011, "JM: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201012, "JM: Miracle chest 10", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201013, "JM: Miracle chest 11", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201014, "JM: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201015, "JM: Miracle chest 13", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201016, "JM: Miracle chest 14", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201017, "JM: Miracle chest 15", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201018, "JM: Miracle chest 16", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
-
- DC2LocationData(694204001, "JM: Lafrescia Seed", "Lafrescia Seed", DC2LocationCategory.KEY_ITEM),
- ],
- "Rainbow Butterfly Wood": [
- DC2LocationData(694202007, "RBW: Floor 1 - Frightening Forest", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202008, "RBW: Floor 2 - Strange Tree", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202009, "RBW: Floor 3 - Rolling Shells", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202010, "RBW: Fish Monster Swamp", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202011, "RBW: Floor 4 - This is a Geostone?", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202012, "RBW: Floor 5 - Noise in the Forest", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202013, "RBW: Floor 6 - I'm a Pixie", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202014, "RBW: Floor 7 - Legendary Killer Snake", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202015, "RBW: Floor 8 - Grotesque Spider Lady", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203002, "RBW: Rainbow Butterfly", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202016, "RBW: Chapter 2 Complete", "Chapter 2 Complete", DC2LocationCategory.EVENT),
-
- # Star Floors
- DC2LocationData(694202017, "RBW: Floor 9 - Looking for the Earth Gem", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202018, "RBW: Floor 10 - Something Rare Here!", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202019, "RBW: Floor 11 - Scary Tree", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203003, "RBW: Trentos", "Bread", DC2LocationCategory.BOSS),
-
- DC2LocationData(694204002, "RBW: Fishing Rod", "Fishing Rod", DC2LocationCategory.KEY_ITEM),
- DC2LocationData(694204003, "RBW: Earth Gem", "Earth Gem", DC2LocationCategory.KEY_ITEM),
- ],
- "Balance Valley": [
-
- ],
- "Starlight Temple": [
- DC2LocationData(694201019, "ST: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201020, "ST: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
-
- # Missable
- DC2LocationData(694201021, "ST: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201022, "ST: Miracle chest 4", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201023, "ST: Miracle chest 5", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201024, "ST: Miracle chest 6", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201025, "ST: Miracle chest 7", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201026, "ST: Miracle chest 8", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201027, "ST: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201028, "ST: Miracle chest 10", "Emerald", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201029, "ST: Miracle chest 11", "Pearl", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201030, "ST: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201031, "ST: Miracle chest 13", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201032, "ST: Miracle chest 14", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201033, "ST: Miracle chest 15", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201034, "ST: Miracle chest 16", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201035, "ST: Miracle chest 17", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201036, "ST: Miracle chest 18", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201037, "ST: Miracle chest 19", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201038, "ST: Miracle chest 20", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201039, "ST: Miracle chest 21", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201040, "ST: Miracle chest 22", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
-
- DC2LocationData(694204004, "ST: Starglass", "Starglass", DC2LocationCategory.KEY_ITEM),
- DC2LocationData(694204005, "ST: Miracle Dumplings", "Miracle Dumplings", DC2LocationCategory.KEY_ITEM),
- ],
- "Starlight Canyon": [
- DC2LocationData(694202020, "SC: Floor 1 - Headlong Dash", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202021, "SC: Floor 2 - Fire and Ice Don't Mix", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202022, "SC: Floor 3 - Earth-Shaking Demon", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202023, "SC: Floor 4 - Powerful Yo-Yo Robot", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202024, "SC: Floor 5 - Elephant Army in the Valley", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202025, "SC: Floor 6 - Dangerous Treasure Chest", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202026, "SC: Floor 7 - Little Dragon Counterattack", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202027, "SC: Barga's Valley", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202028, "SC: Floor 8 - Warrior in Starlight Canyon", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202029, "SC: Floor 9 - Smiling Fairy Village", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202030, "SC: Floor 10 - Cursed Mask", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202031, "SC: Floor 11 - We're the Roly-Poly Brothers", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202032, "SC: Floor 12 - Dragon Slayer", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203004, "SC: Memo Eater", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202033, "SC: Floor 13 - Rama Priests Like Cheese", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202034, "SC: Floor 14 - Nature's Threat", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202035, "SC: Floor 15 - Moon Baron", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202036, "SC: Floor 16 - Lighthouse Appears", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202037, "SC: Evil Flame and Gaspard", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202038, "SC: Chapter 3 Complete", "Chapter 3 Complete", DC2LocationCategory.EVENT),
-
- # Star path floors
- DC2LocationData(694202039, "SC: Floor 17 - Looking for the Wind Gem", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202040, "SC: Floor 18 - Evil Spirit in the Valley", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202041, "SC: Floor 19 - Brave Warriors in the Valley", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203005, "SC: Lapis Garter", "Bread", DC2LocationCategory.BOSS),
-
- DC2LocationData(694204006, "SC: White Windflower", "White Windflower", DC2LocationCategory.KEY_ITEM),
- DC2LocationData(694204007, "SC: Wind Gem", "Wind Gem", DC2LocationCategory.KEY_ITEM),
- ],
- "Veniccio": [
-
- ],
- "Lunatic Wisdom Laboratories": [
- DC2LocationData(694201041, "LWL: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201042, "LWL: Miracle chest 2", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201043, "LWL: Miracle chest 3", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201044, "LWL: Miracle chest 4", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201045, "LWL: Miracle chest 5", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201046, "LWL: Miracle chest 6", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201047, "LWL: Miracle chest 7", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201048, "LWL: Miracle chest 8", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201049, "LWL: Miracle chest 9", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201050, "LWL: Miracle chest 10", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201051, "LWL: Miracle chest 11", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201052, "LWL: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201053, "LWL: Miracle chest 13", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201054, "LWL: Miracle chest 14", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201055, "LWL: Miracle chest 15", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201056, "LWL: Miracle chest 16", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201057, "LWL: Miracle chest 17", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201058, "LWL: Miracle chest 18", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201059, "LWL: Miracle chest 19", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201060, "LWL: Miracle chest 20", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201061, "LWL: Miracle chest 21", "Ruby", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201062, "LWL: Miracle chest 22", "Peridot", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201063, "LWL: Miracle chest 23", "Sapphire", DC2LocationCategory.MIRACLE_CHEST),
-
- DC2LocationData(694204008, "LWL: Electric Worm", "Electric Worm", DC2LocationCategory.KEY_ITEM),
- DC2LocationData(694204009, "LWL: Shell Talkie", "Shell Talkie", DC2LocationCategory.KEY_ITEM),
- ],
- "Ocean's Roar Cave": [
- DC2LocationData(694202042, "ORC: Floor 1 - Pirates!", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202043, "ORC: Floor 2 - Tons of Fish", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202044, "ORC: Floor 3 - Tank and Boss", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202045, "ORC: Floor 4 - Water Monster", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202046, "ORC: Floor 5 - Scary Auntie Medusa", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202047, "ORC: Floor 6 - Sand Molers", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202048, "ORC: Floor 7 - Bat Den", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202049, "ORC: Floor 8 - Pirates' Hideout", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202050, "ORC: Cave of Ancient Murals", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202051, "ORC: Floor 9 - Wandering Zappy", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202052, "ORC: Floor 10 - Banquet of the Dead", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202053, "ORC: Floor 11 - Improvements", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202054, "ORC: Floor 12 - Return of the Serpent", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202055, "ORC: Floor 13 - Cursed Sea", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202056, "ORC: Floor 14 - Sea of Atrocity", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203006, "ORC: Dr. Jaming", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202057, "ORC: Chapter 4 Complete", "Chapter 4 Complete", DC2LocationCategory.EVENT),
-
- # Star path floors
- DC2LocationData(694202058, "ORC: Floor 15 - Looking for the Water Gem", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202059, "ORC: Floor 16 - Pirates' Revenge", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202060, "ORC: Floor 17 - Death Ocean", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203007, "ORC: Sea Dragon", "Bread", DC2LocationCategory.BOSS),
-
- DC2LocationData(694204010, "ORC: Secret Dragon Remedy", "Secret Dragon Remedy", DC2LocationCategory.KEY_ITEM),
- DC2LocationData(694204011, "ORC: Water Gem", "Water Gem", DC2LocationCategory.KEY_ITEM),
- ],
- "Heim Rada": [
-
- ],
- "Gundorada Workshop": [
- DC2LocationData(694201064, "GW: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201065, "GW: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201066, "GW: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201067, "GW: Miracle chest 4", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201068, "GW: Miracle chest 5", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201069, "GW: Miracle chest 6", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201070, "GW: Miracle chest 7", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201071, "GW: Miracle chest 8", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201072, "GW: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201073, "GW: Miracle chest 10", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201074, "GW: Miracle chest 11", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201075, "GW: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201076, "GW: Miracle chest 13", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201077, "GW: Miracle chest 14", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201078, "GW: Miracle chest 15", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201079, "GW: Miracle chest 16", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201080, "GW: Miracle chest 17", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201081, "GW: Miracle chest 18", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201082, "GW: Miracle chest 19", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201083, "GW: Miracle chest 20", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201084, "GW: Miracle chest 21", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201085, "GW: Miracle chest 22", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201086, "GW: Miracle chest 23", "Diamond", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201087, "GW: Miracle chest 24", "Turquoise", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201088, "GW: Miracle chest 25", "Topaz", DC2LocationCategory.MIRACLE_CHEST),
-
- DC2LocationData(694204012, "GW: Time Bomb", "Time Bomb", DC2LocationCategory.KEY_ITEM),
- DC2LocationData(694204013, "GW: Fire Horn", "Fire Horn", DC2LocationCategory.KEY_ITEM)
- ],
- "Mount Gundor": [
- DC2LocationData(694202061, "MG: Floor 1 - Battle with Griffon's Army", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202062, "MG: Floor 2 - Mt. Gundor Wind", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202063, "MG: Floor 3 - Little Dragons on the Mountain", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202064, "MG: Floor 4 - Steam Goyone", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202065, "MG: Floor 5 - Mountain Baddie Appears", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202066, "MG: Floor 6 - Magmanoff", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202067, "MG: Floor 7 - Danger Zone", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202068, "MG: Floor 8 - Secret of Fire Mountain", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202069, "MG: Floor 9 - Deathtrap", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202070, "MG: Floor 10 - Desperation on the Mountain", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202071, "MG: Floor 11 - Pains in the Neck", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202072, "MG: Mount Gundor Peak", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202073, "MG: Floor 12 - Walking the Path of Flames", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202074, "MG: Floor 13 - Burning Undead", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202075, "MG: Floor 14 - Fire Dragon", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202076, "MG: Floor 15 - Treasure Chest Danger Zone", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202077, "MG: Floor 16 - Road to the River of Flames", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203008, "MG: Gaspard", "Bread", DC2LocationCategory.BOSS),
- DC2LocationData(694202078, "MG: Chapter 5 Complete", "Chapter 5 Complete", DC2LocationCategory.EVENT),
-
- # Star path floors
- DC2LocationData(694202079, "MG: Floor 17 - Looking for the Fire Gem", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202080, "MG: Floor 18 - Explosive Hot Spring", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202081, "MG: Floor 19 - Crazy Mountain", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694203009, "MG: Inferno", "Bread", DC2LocationCategory.BOSS),
-
- DC2LocationData(694204014, "MG: Fire Gem", "Fire Gem", DC2LocationCategory.KEY_ITEM),
- ],
- "Moon Flower Palace": [
- DC2LocationData(694201089, "MFP: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201090, "MFP: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201091, "MFP: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201092, "MFP: Miracle chest 4", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201093, "MFP: Miracle chest 5", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201094, "MFP: Miracle chest 6", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201095, "MFP: Miracle chest 7", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201096, "MFP: Miracle chest 8", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201097, "MFP: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201098, "MFP: Miracle chest 10", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201099, "MFP: Miracle chest 11", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201100, "MFP: Miracle chest 12", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201101, "MFP: Miracle chest 13", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201102, "MFP: Miracle chest 14", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201103, "MFP: Miracle chest 15", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201104, "MFP: Miracle chest 16", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201105, "MFP: Miracle chest 17", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201106, "MFP: Miracle chest 18", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201107, "MFP: Miracle chest 19", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201108, "MFP: Miracle chest 20", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201109, "MFP: Miracle chest 21", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
- DC2LocationData(694201110, "MFP: Miracle chest 22", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
-
- DC2LocationData(694202082, "MFP: Floor 1 - Ancient Wind", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202083, "MFP: Floor 2 - Card Warriors Gather", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202084, "MFP: Floor 3 - Dangerous Treasure", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202085, "MFP: Floor 4 - Zombie Zone", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202086, "MFP: Floor 5 - Feeling Out of Place", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202087, "MFP: Floor 6 - Living Statue", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202088, "MFP: Floor 7 - Danger Zone", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202089, "MFP: Floor 8 - Scary Women", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202090, "MFP: Floor 9 - Hell Elephant", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202091, "MFP: Floor 10 - Crush the Undead", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202092, "MFP: Garden", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202093, "MFP: Floor 11 - Missing Gem Dealer", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202094, "MFP: Floor 12 - Max's Longest Day", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202095, "MFP: Floor 13 - Hell's Corridor", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202096, "MFP: Floor 14 - Monica All Alone", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202097, "MFP: Floor 15 - Raging Spirits", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202098, "MFP: Floor 16 - Lonely Machine", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202099, "MFP: Floor 17 - Nobility", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202100, "MFP: Floor 18 - Palace Watchdog", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202101, "MFP: Floor 19 - Road to Memories", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202102, "MFP: Alexandra's Room", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202103, "MFP: Floor 20 - Final Trump Card", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202104, "MFP: Floor 21 - Elemental Party", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202105, "MFP: Floor 22 - Wandering Knight's Soul", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202106, "MFP: Floor 23 - Beware Carelessness", "Bread", DC2LocationCategory.FLOOR),
- DC2LocationData(694202107, "MFP: Floor 24 - Final Battle", "Bread", DC2LocationCategory.FLOOR),
-
- DC2LocationData(694204015, "MFP: Flower of the Sun", "Flower of the Sun", DC2LocationCategory.KEY_ITEM)
- ],
-
-}
-
-location_dictionary: Dict[str, DC2LocationData] = {}
-for location_table in location_tables.values():
- location_dictionary.update({location_data.name: location_data for location_data in location_table})
+from enum import IntEnum
+from typing import Optional, NamedTuple, Dict
+
+from BaseClasses import Location, Region
+
+
+class DC2LocationCategory(IntEnum):
+ FLOOR = 0
+ DUNGEON = 1
+ RECRUIT = 2
+ GEORAMA = 3
+ MIRACLE_CHEST = 4
+ BOSS = 5
+ MISC = 6
+ GEOSTONE = 7
+ EVENT = 8
+ SKIP = 9,
+ KEY_ITEM = 10
+
+
+class DC2LocationData(NamedTuple):
+ id: int
+ name: str
+ default_item: str
+ category: DC2LocationCategory
+
+
+class DarkCloud2Location(Location):
+ game: str = "Dark Cloud 2"
+ category: DC2LocationCategory
+ default_item_name: str
+
+ def __init__(
+ self,
+ player: int,
+ name: str,
+ category: DC2LocationCategory,
+ 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 = 694200000
+ table_offset = 1000
+
+ table_order = [
+ "Palm Brinks",
+ "Underground Water Channel",
+ "Sindain",
+ "Jurak Mall",
+ "Rainbow Butterfly Wood",
+ "Balance Valley",
+ "Starlight Temple",
+ "Starlight Canyon",
+ "Veniccio",
+ "Lunatic Wisdom Laboratories",
+ "Ocean's Roar Cave",
+ "Heim Rada",
+ "Gundorada Workshop",
+ "Mount Gundor",
+ "Moon Flower Palace"
+ ]
+
+ 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: location_data.id for location_data in location_tables[region_name]})
+ return output
+
+location_skip_categories = {
+DC2LocationCategory.EVENT, DC2LocationCategory.SKIP
+}
+
+location_tables = {
+ "Palm Brinks": [
+ DC2LocationData(694201000, "PB: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201001, "PB: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201002, "PB: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ ],
+ "Underground Water Channel": [
+ DC2LocationData(694202000, "UWC: Floor 1 - To the Outside World", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202001, "UWC: Floor 2 - Battle with Rats", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202002, "UWC: Floor 3 - Ghosts in the Channel", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202003, "UWC: Pump Room", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203000, "UWC: Linda", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202004, "UWC: Floor 4 - Steve's Battle", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202005, "UWC: Floor 5 - Ghost in the Channel", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203001, "UWC: Halloween", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202006, "UWC: Chapter 1 Complete", "Chapter 1 Complete", DC2LocationCategory.EVENT),
+ ],
+ "Sindain": [
+ DC2LocationData(694204000, "S: Grape Juice", "Grape Juice", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Jurak Mall": [
+ DC2LocationData(694201003, "JM: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201004, "JM: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201005, "JM: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201006, "JM: Miracle chest 4", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201007, "JM: Miracle chest 5", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201008, "JM: Miracle chest 6", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201009, "JM: Miracle chest 7", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201010, "JM: Miracle chest 8", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201011, "JM: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201012, "JM: Miracle chest 10", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201013, "JM: Miracle chest 11", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201014, "JM: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201015, "JM: Miracle chest 13", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201016, "JM: Miracle chest 14", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201017, "JM: Miracle chest 15", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201018, "JM: Miracle chest 16", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+
+ DC2LocationData(694204001, "JM: Lafrescia Seed", "Lafrescia Seed", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Rainbow Butterfly Wood": [
+ DC2LocationData(694202007, "RBW: Floor 1 - Frightening Forest", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202008, "RBW: Floor 2 - Strange Tree", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202009, "RBW: Floor 3 - Rolling Shells", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202010, "RBW: Fish Monster Swamp", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202011, "RBW: Floor 4 - This is a Geostone?", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202012, "RBW: Floor 5 - Noise in the Forest", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202013, "RBW: Floor 6 - I'm a Pixie", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202014, "RBW: Floor 7 - Legendary Killer Snake", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202015, "RBW: Floor 8 - Grotesque Spider Lady", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203002, "RBW: Rainbow Butterfly", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202016, "RBW: Chapter 2 Complete", "Chapter 2 Complete", DC2LocationCategory.EVENT),
+
+ # Star Floors
+ DC2LocationData(694202017, "RBW: Floor 9 - Looking for the Earth Gem", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202018, "RBW: Floor 10 - Something Rare Here!", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202019, "RBW: Floor 11 - Scary Tree", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203003, "RBW: Trentos", "Bread", DC2LocationCategory.BOSS),
+
+ DC2LocationData(694204002, "RBW: Fishing Rod", "Fishing Rod", DC2LocationCategory.KEY_ITEM),
+ DC2LocationData(694204003, "RBW: Earth Gem", "Earth Gem", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Balance Valley": [
+
+ ],
+ "Starlight Temple": [
+ DC2LocationData(694201019, "ST: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201020, "ST: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+
+ # Missable
+ DC2LocationData(694201021, "ST: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201022, "ST: Miracle chest 4", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201023, "ST: Miracle chest 5", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201024, "ST: Miracle chest 6", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201025, "ST: Miracle chest 7", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201026, "ST: Miracle chest 8", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201027, "ST: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201028, "ST: Miracle chest 10", "Emerald", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201029, "ST: Miracle chest 11", "Pearl", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201030, "ST: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201031, "ST: Miracle chest 13", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201032, "ST: Miracle chest 14", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201033, "ST: Miracle chest 15", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201034, "ST: Miracle chest 16", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201035, "ST: Miracle chest 17", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201036, "ST: Miracle chest 18", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201037, "ST: Miracle chest 19", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201038, "ST: Miracle chest 20", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201039, "ST: Miracle chest 21", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201040, "ST: Miracle chest 22", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+
+ DC2LocationData(694204004, "ST: Starglass", "Starglass", DC2LocationCategory.KEY_ITEM),
+ DC2LocationData(694204005, "ST: Miracle Dumplings", "Miracle Dumplings", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Starlight Canyon": [
+ DC2LocationData(694202020, "SC: Floor 1 - Headlong Dash", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202021, "SC: Floor 2 - Fire and Ice Don't Mix", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202022, "SC: Floor 3 - Earth-Shaking Demon", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202023, "SC: Floor 4 - Powerful Yo-Yo Robot", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202024, "SC: Floor 5 - Elephant Army in the Valley", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202025, "SC: Floor 6 - Dangerous Treasure Chest", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202026, "SC: Floor 7 - Little Dragon Counterattack", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202027, "SC: Barga's Valley", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202028, "SC: Floor 8 - Warrior in Starlight Canyon", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202029, "SC: Floor 9 - Smiling Fairy Village", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202030, "SC: Floor 10 - Cursed Mask", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202031, "SC: Floor 11 - We're the Roly-Poly Brothers", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202032, "SC: Floor 12 - Dragon Slayer", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203004, "SC: Memo Eater", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202033, "SC: Floor 13 - Rama Priests Like Cheese", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202034, "SC: Floor 14 - Nature's Threat", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202035, "SC: Floor 15 - Moon Baron", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202036, "SC: Floor 16 - Lighthouse Appears", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202037, "SC: Evil Flame and Gaspard", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202038, "SC: Chapter 3 Complete", "Chapter 3 Complete", DC2LocationCategory.EVENT),
+
+ # Star path floors
+ DC2LocationData(694202039, "SC: Floor 17 - Looking for the Wind Gem", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202040, "SC: Floor 18 - Evil Spirit in the Valley", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202041, "SC: Floor 19 - Brave Warriors in the Valley", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203005, "SC: Lapis Garter", "Bread", DC2LocationCategory.BOSS),
+
+ DC2LocationData(694204006, "SC: White Windflower", "White Windflower", DC2LocationCategory.KEY_ITEM),
+ DC2LocationData(694204007, "SC: Wind Gem", "Wind Gem", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Veniccio": [
+
+ ],
+ "Lunatic Wisdom Laboratories": [
+ DC2LocationData(694201041, "LWL: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201042, "LWL: Miracle chest 2", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201043, "LWL: Miracle chest 3", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201044, "LWL: Miracle chest 4", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201045, "LWL: Miracle chest 5", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201046, "LWL: Miracle chest 6", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201047, "LWL: Miracle chest 7", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201048, "LWL: Miracle chest 8", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201049, "LWL: Miracle chest 9", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201050, "LWL: Miracle chest 10", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201051, "LWL: Miracle chest 11", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201052, "LWL: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201053, "LWL: Miracle chest 13", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201054, "LWL: Miracle chest 14", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201055, "LWL: Miracle chest 15", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201056, "LWL: Miracle chest 16", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201057, "LWL: Miracle chest 17", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201058, "LWL: Miracle chest 18", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201059, "LWL: Miracle chest 19", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201060, "LWL: Miracle chest 20", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201061, "LWL: Miracle chest 21", "Ruby", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201062, "LWL: Miracle chest 22", "Peridot", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201063, "LWL: Miracle chest 23", "Sapphire", DC2LocationCategory.MIRACLE_CHEST),
+
+ DC2LocationData(694204008, "LWL: Electric Worm", "Electric Worm", DC2LocationCategory.KEY_ITEM),
+ DC2LocationData(694204009, "LWL: Shell Talkie", "Shell Talkie", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Ocean's Roar Cave": [
+ DC2LocationData(694202042, "ORC: Floor 1 - Pirates!", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202043, "ORC: Floor 2 - Tons of Fish", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202044, "ORC: Floor 3 - Tank and Boss", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202045, "ORC: Floor 4 - Water Monster", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202046, "ORC: Floor 5 - Scary Auntie Medusa", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202047, "ORC: Floor 6 - Sand Molers", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202048, "ORC: Floor 7 - Bat Den", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202049, "ORC: Floor 8 - Pirates' Hideout", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202050, "ORC: Cave of Ancient Murals", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202051, "ORC: Floor 9 - Wandering Zappy", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202052, "ORC: Floor 10 - Banquet of the Dead", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202053, "ORC: Floor 11 - Improvements", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202054, "ORC: Floor 12 - Return of the Serpent", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202055, "ORC: Floor 13 - Cursed Sea", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202056, "ORC: Floor 14 - Sea of Atrocity", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203006, "ORC: Dr. Jaming", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202057, "ORC: Chapter 4 Complete", "Chapter 4 Complete", DC2LocationCategory.EVENT),
+
+ # Star path floors
+ DC2LocationData(694202058, "ORC: Floor 15 - Looking for the Water Gem", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202059, "ORC: Floor 16 - Pirates' Revenge", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202060, "ORC: Floor 17 - Death Ocean", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203007, "ORC: Sea Dragon", "Bread", DC2LocationCategory.BOSS),
+
+ DC2LocationData(694204010, "ORC: Secret Dragon Remedy", "Secret Dragon Remedy", DC2LocationCategory.KEY_ITEM),
+ DC2LocationData(694204011, "ORC: Water Gem", "Water Gem", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Heim Rada": [
+
+ ],
+ "Gundorada Workshop": [
+ DC2LocationData(694201064, "GW: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201065, "GW: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201066, "GW: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201067, "GW: Miracle chest 4", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201068, "GW: Miracle chest 5", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201069, "GW: Miracle chest 6", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201070, "GW: Miracle chest 7", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201071, "GW: Miracle chest 8", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201072, "GW: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201073, "GW: Miracle chest 10", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201074, "GW: Miracle chest 11", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201075, "GW: Miracle chest 12", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201076, "GW: Miracle chest 13", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201077, "GW: Miracle chest 14", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201078, "GW: Miracle chest 15", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201079, "GW: Miracle chest 16", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201080, "GW: Miracle chest 17", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201081, "GW: Miracle chest 18", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201082, "GW: Miracle chest 19", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201083, "GW: Miracle chest 20", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201084, "GW: Miracle chest 21", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201085, "GW: Miracle chest 22", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201086, "GW: Miracle chest 23", "Diamond", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201087, "GW: Miracle chest 24", "Turquoise", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201088, "GW: Miracle chest 25", "Topaz", DC2LocationCategory.MIRACLE_CHEST),
+
+ DC2LocationData(694204012, "GW: Time Bomb", "Time Bomb", DC2LocationCategory.KEY_ITEM),
+ DC2LocationData(694204013, "GW: Fire Horn", "Fire Horn", DC2LocationCategory.KEY_ITEM)
+ ],
+ "Mount Gundor": [
+ DC2LocationData(694202061, "MG: Floor 1 - Battle with Griffon's Army", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202062, "MG: Floor 2 - Mt. Gundor Wind", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202063, "MG: Floor 3 - Little Dragons on the Mountain", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202064, "MG: Floor 4 - Steam Goyone", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202065, "MG: Floor 5 - Mountain Baddie Appears", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202066, "MG: Floor 6 - Magmanoff", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202067, "MG: Floor 7 - Danger Zone", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202068, "MG: Floor 8 - Secret of Fire Mountain", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202069, "MG: Floor 9 - Deathtrap", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202070, "MG: Floor 10 - Desperation on the Mountain", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202071, "MG: Floor 11 - Pains in the Neck", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202072, "MG: Mount Gundor Peak", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202073, "MG: Floor 12 - Walking the Path of Flames", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202074, "MG: Floor 13 - Burning Undead", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202075, "MG: Floor 14 - Fire Dragon", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202076, "MG: Floor 15 - Treasure Chest Danger Zone", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202077, "MG: Floor 16 - Road to the River of Flames", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203008, "MG: Gaspard", "Bread", DC2LocationCategory.BOSS),
+ DC2LocationData(694202078, "MG: Chapter 5 Complete", "Chapter 5 Complete", DC2LocationCategory.EVENT),
+
+ # Star path floors
+ DC2LocationData(694202079, "MG: Floor 17 - Looking for the Fire Gem", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202080, "MG: Floor 18 - Explosive Hot Spring", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202081, "MG: Floor 19 - Crazy Mountain", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694203009, "MG: Inferno", "Bread", DC2LocationCategory.BOSS),
+
+ DC2LocationData(694204014, "MG: Fire Gem", "Fire Gem", DC2LocationCategory.KEY_ITEM),
+ ],
+ "Moon Flower Palace": [
+ DC2LocationData(694201089, "MFP: Miracle chest 1", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201090, "MFP: Miracle chest 2", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201091, "MFP: Miracle chest 3", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201092, "MFP: Miracle chest 4", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201093, "MFP: Miracle chest 5", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201094, "MFP: Miracle chest 6", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201095, "MFP: Miracle chest 7", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201096, "MFP: Miracle chest 8", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201097, "MFP: Miracle chest 9", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201098, "MFP: Miracle chest 10", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201099, "MFP: Miracle chest 11", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201100, "MFP: Miracle chest 12", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201101, "MFP: Miracle chest 13", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201102, "MFP: Miracle chest 14", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201103, "MFP: Miracle chest 15", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201104, "MFP: Miracle chest 16", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201105, "MFP: Miracle chest 17", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201106, "MFP: Miracle chest 18", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201107, "MFP: Miracle chest 19", "Fruit of Eden", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201108, "MFP: Miracle chest 20", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201109, "MFP: Miracle chest 21", "Potato Pie", DC2LocationCategory.MIRACLE_CHEST),
+ DC2LocationData(694201110, "MFP: Miracle chest 22", "Witch Parfait", DC2LocationCategory.MIRACLE_CHEST),
+
+ DC2LocationData(694202082, "MFP: Floor 1 - Ancient Wind", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202083, "MFP: Floor 2 - Card Warriors Gather", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202084, "MFP: Floor 3 - Dangerous Treasure", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202085, "MFP: Floor 4 - Zombie Zone", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202086, "MFP: Floor 5 - Feeling Out of Place", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202087, "MFP: Floor 6 - Living Statue", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202088, "MFP: Floor 7 - Danger Zone", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202089, "MFP: Floor 8 - Scary Women", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202090, "MFP: Floor 9 - Hell Elephant", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202091, "MFP: Floor 10 - Crush the Undead", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202092, "MFP: Garden", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202093, "MFP: Floor 11 - Missing Gem Dealer", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202094, "MFP: Floor 12 - Max's Longest Day", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202095, "MFP: Floor 13 - Hell's Corridor", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202096, "MFP: Floor 14 - Monica All Alone", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202097, "MFP: Floor 15 - Raging Spirits", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202098, "MFP: Floor 16 - Lonely Machine", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202099, "MFP: Floor 17 - Nobility", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202100, "MFP: Floor 18 - Palace Watchdog", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202101, "MFP: Floor 19 - Road to Memories", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202102, "MFP: Alexandra's Room", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202103, "MFP: Floor 20 - Final Trump Card", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202104, "MFP: Floor 21 - Elemental Party", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202105, "MFP: Floor 22 - Wandering Knight's Soul", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202106, "MFP: Floor 23 - Beware Carelessness", "Bread", DC2LocationCategory.FLOOR),
+ DC2LocationData(694202107, "MFP: Floor 24 - Final Battle", "Bread", DC2LocationCategory.FLOOR),
+
+ DC2LocationData(694204015, "MFP: Flower of the Sun", "Flower of the Sun", DC2LocationCategory.KEY_ITEM)
+ ],
+
+}
+
+location_dictionary: Dict[str, DC2LocationData] = {}
+for location_table in location_tables.values():
+ location_dictionary.update({location_data.name: location_data for location_data in location_table})
diff --git a/worlds/dc2/Options.py b/worlds/dc2/Options.py
index 3c1a9a5bd6b1..d177a8b3fb6b 100644
--- a/worlds/dc2/Options.py
+++ b/worlds/dc2/Options.py
@@ -1,97 +1,80 @@
-import typing
-from dataclasses import dataclass
-from Options import Toggle, DefaultOnToggle, Option, Range, Choice, ItemDict, DeathLink, PerGameCommonOptions
-
-
-class FishsanityOption(DefaultOnToggle):
- """Include catching all fish"""
- display_name = "Randomize Fish Locations"
-
-class SphedasanityOption(DefaultOnToggle):
- """Include completing all spheda"""
- display_name = "Randomize Spheda Locations"
-
-class MedalsanityOption(DefaultOnToggle):
- """Include completing all Medals"""
- display_name = "Randomize Medal Locations"
-
-class GeoramasanityOption(DefaultOnToggle):
- """Include 100% completing all Georama"""
- display_name = "Randomize Georama Locations"
-
-class PhotosanityOption(DefaultOnToggle):
- """Include taking all photos"""
- display_name = "Randomize Photo ideas Locations"
-
-class InventionsanityOption(DefaultOnToggle):
- """Include discovering all inventions"""
- display_name = "Randomize invention Locations"
-
-
-class IncludeResourcePacksOption(Range):
- """Adds packs of useful items for georama"""
- display_name = "Number of resource packs to include in the pool"
- range_start = 0
- range_end = 100
- default = 20
-
-class IncludeWeaponUpgradePacksOption(Range):
- """Adds packs of useful items for weapon upgrades"""
- display_name = "Number of weapon upgrade packs to include in the pool"
- range_start = 0
- range_end = 100
- default = 20
-
-class IncludeElementPacksOption(Range):
- """Adds packs of useful items for georama"""
- display_name = "Number of element packs to include in the pool"
- range_start = 0
- range_end = 100
- default = 20
-
-class ChapterGoalCountOption(Range):
- """Amount of chapters to complete for the goal"""
- display_name = "Number of chapters that must be completed"
- range_start = 1
- range_end = 8
- default = 4
-
-class ABSMultiplierOption(Range):
- """Multiply ABS by this amount"""
- display_name = "Multiplier for received ABS (weapon exp)"
- range_start = 1
- range_end = 10
- default = 1
-
-class GildaMultiplierOption(Range):
- """Multiply Gilda dropped by enemies by this amount"""
- display_name = "Multiplier for enemy-dropped gilda"
- range_start = 1
- range_end = 10
- default = 1
-
-class GuaranteedItemsOption(ItemDict):
- """Guarantees that the specified items will be in the item pool"""
- display_name = "Guaranteed Items"
-
-class EnableEnemyRandomiserOption(DefaultOnToggle):
- """Randomise all enemies"""
- display_name = "Enable Enemy Randomiser"
-
-@dataclass
-class DC2Option(PerGameCommonOptions):
- #fishsanity: FishsanityOption
- #sphedasanity: SphedasanityOption
- #medalsanity: MedalsanityOption
- #georamasanity: GeoramasanityOption
- #photosanity: PhotosanityOption
- #inventionsanity: InventionsanityOption
- #resource_pack_count: IncludeResourcePacksOption
- #weapon_upgrade_pack_count: IncludeWeaponUpgradePacksOption
- #element_pack_count: IncludeElementPacksOption
- #chapter_goal_count: ChapterGoalCountOption
- abs_multiplier: ABSMultiplierOption
- gilda_multiplier: GildaMultiplierOption
- guaranteed_items: GuaranteedItemsOption
- enable_enemy_randomiser: EnableEnemyRandomiserOption
-
+import typing
+from dataclasses import dataclass
+from Options import Toggle, DefaultOnToggle, Option, Range, Choice, ItemDict, DeathLink, PerGameCommonOptions
+
+
+class FishsanityOption(DefaultOnToggle):
+ """Include catching all fish"""
+ display_name = "Randomize Fish Locations"
+
+class SphedasanityOption(DefaultOnToggle):
+ """Include completing all spheda"""
+ display_name = "Randomize Spheda Locations"
+
+class MedalsanityOption(DefaultOnToggle):
+ """Include completing all Medals"""
+ display_name = "Randomize Medal Locations"
+
+class GeoramasanityOption(DefaultOnToggle):
+ """Include 100% completing all Georama"""
+ display_name = "Randomize Georama Locations"
+
+class PhotosanityOption(DefaultOnToggle):
+ """Include taking all photos"""
+ display_name = "Randomize Photo ideas Locations"
+
+class InventionsanityOption(DefaultOnToggle):
+ """Include discovering all inventions"""
+ display_name = "Randomize invention Locations"
+
+
+class IncludeResourcePacksOption(Range):
+ """Adds packs of useful items"""
+ display_name = "Number of resource packs to include in the pool"
+ range_start = 0
+ range_end = 100
+ default = 20
+
+class ChapterGoalCountOption(Range):
+ """Amount of chapters to complete for the goal"""
+ display_name = "Number of chapters that must be completed"
+ range_start = 1
+ range_end = 8
+ default = 4
+
+class ABSMultiplierOption(Range):
+ """Multiply ABS by this amount"""
+ display_name = "Multiplier for received ABS (weapon exp)"
+ range_start = 1
+ range_end = 10
+ default = 1
+
+class GildaMultiplierOption(Range):
+ """Multiply Gilda dropped by enemies by this amount"""
+ display_name = "Multiplier for enemy-dropped gilda"
+ range_start = 1
+ range_end = 10
+ default = 1
+
+class GuaranteedItemsOption(ItemDict):
+ """Guarantees that the specified items will be in the item pool"""
+ display_name = "Guaranteed Items"
+
+class EnableEnemyRandomiserOption(DefaultOnToggle):
+ """Randomise all enemies"""
+ display_name = "Enable Enemy Randomiser"
+
+@dataclass
+class DC2Option(PerGameCommonOptions):
+ #fishsanity: FishsanityOption
+ #sphedasanity: SphedasanityOption
+ #medalsanity: MedalsanityOption
+ #georamasanity: GeoramasanityOption
+ #photosanity: PhotosanityOption
+ #inventionsanity: InventionsanityOption
+ resource_pack_count: IncludeResourcePacksOption
+ abs_multiplier: ABSMultiplierOption
+ gilda_multiplier: GildaMultiplierOption
+ guaranteed_items: GuaranteedItemsOption
+ enable_enemy_randomiser: EnableEnemyRandomiserOption
+
diff --git a/worlds/dc2/__init__.py b/worlds/dc2/__init__.py
index 3155ecda407d..468bc7e42bad 100644
--- a/worlds/dc2/__init__.py
+++ b/worlds/dc2/__init__.py
@@ -1,433 +1,433 @@
-# world/dc2/__init__.py
-from typing import Dict, Set, List
-
-from BaseClasses import MultiWorld, Region, Item, Entrance, Tutorial, ItemClassification
-from Options import Toggle
-
-from worlds.AutoWorld import World, WebWorld
-from worlds.generic.Rules import set_rule, add_rule, add_item_rule
-
-from .Items import DarkCloud2Item, DC2ItemCategory, item_dictionary, key_item_names, item_descriptions, BuildItemPool
-from .Locations import DarkCloud2Location, DC2LocationCategory, location_tables, location_dictionary, location_skip_categories
-from .Options import DC2Option
-
-class DarkCloud2Web(WebWorld):
- bug_report_page = ""
- theme = "stone"
- setup_en = Tutorial(
- "Multiworld Setup Guide",
- "A guide to setting up the Archipelago Dark Cloud 2 randomizer on your computer.",
- "English",
- "setup_en.md",
- "setup/en",
- ["ArsonAssassin"]
- )
-
-
- tutorials = [setup_en]
-
-
-class DarkCloud2World(World):
- """
- Dark Cloud 2 is a game.
- """
-
- game: str = "Dark Cloud 2"
- is_experimental = True
- options_dataclass = DC2Option
- options: DC2Option
- topology_present: bool = True
- web = DarkCloud2Web()
- data_version = 0
- base_id = 694200000
- enabled_location_categories: Set[DC2LocationCategory]
- required_client_version = (0, 5, 1)
- item_name_to_id = DarkCloud2Item.get_name_to_id()
- location_name_to_id = DarkCloud2Location.get_name_to_id()
- item_name_groups = {
- }
- item_descriptions = item_descriptions
- explicit_indirect_conditions = False
-
- 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):
- self.enabled_location_categories.add(DC2LocationCategory.FLOOR)
- self.enabled_location_categories.add(DC2LocationCategory.RECRUIT)
- self.enabled_location_categories.add(DC2LocationCategory.GEORAMA)
- self.enabled_location_categories.add(DC2LocationCategory.MIRACLE_CHEST)
- self.enabled_location_categories.add(DC2LocationCategory.BOSS)
- self.enabled_location_categories.add(DC2LocationCategory.MISC)
- self.enabled_location_categories.add(DC2LocationCategory.GEOSTONE)
- # self.enabled_location_categories.add(DC2LocationCategory.EVENT)
- self.enabled_location_categories.add(DC2LocationCategory.KEY_ITEM)
-
-
- def create_regions(self):
- # Create Regions
- regions: Dict[str, Region] = {}
- regions["Menu"] = self.create_region("Menu", [])
- regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [
- "Palm Brinks",
- "Underground Water Channel",
- "Sindain",
- "Rainbow Butterfly Wood",
- "Jurak Mall",
- "Balance Valley",
- "Starlight Canyon",
- "Starlight Temple",
- "Veniccio",
- "Ocean's Roar Cave",
- "Lunatic Wisdom Laboratories",
- "Heim Rada",
- "Mount Gundor",
- "Gundorada Workshop",
- "Moon Flower Palace"
- ]})
-
-
- # Connect Regions
- def create_connection(from_region: str, to_region: str):
- connection = Entrance(self.player, f"{from_region} -> {to_region}", regions[from_region])
- regions[from_region].exits.append(connection)
- connection.connect(regions[to_region])
-
- create_connection("Menu", "Palm Brinks")
-
- create_connection("Palm Brinks", "Underground Water Channel")
-
- create_connection("Palm Brinks", "Sindain")
- create_connection("Palm Brinks", "Balance Valley")
- create_connection("Palm Brinks", "Veniccio")
- create_connection("Palm Brinks", "Heim Rada")
- create_connection("Palm Brinks", "Moon Flower Palace")
-
- create_connection("Sindain", "Rainbow Butterfly Wood")
- create_connection("Sindain", "Jurak Mall")
-
- create_connection("Balance Valley", "Starlight Canyon")
- create_connection("Balance Valley", "Starlight Temple")
-
- create_connection("Veniccio", "Ocean's Roar Cave")
- create_connection("Veniccio", "Lunatic Wisdom Laboratories")
-
- create_connection("Heim Rada", "Mount Gundor")
- create_connection("Heim Rada", "Gundorada Workshop")
-
-
- # 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)
- #print("location table size: " + str(len(location_table)))
- for location in location_table:
- #print("Creating location: " + location.name)
- if location.category in self.enabled_location_categories and location.category not in location_skip_categories:
- #print("Adding location: " + location.name + " with default item " + location.default_item)
- new_location = DarkCloud2Location(
- self.player,
- location.name,
- location.category,
- location.default_item,
- self.location_name_to_id[location.name],
- new_region
- )
- else:
- # Replace non-randomized progression items with events
- event_item = self.create_item(location.default_item)
- #if event_item.classification != ItemClassification.progression:
- # continue
- #print("Adding Location: " + location.name + " as an event with default item " + location.default_item)
- new_location = DarkCloud2Location(
- self.player,
- location.name,
- location.category,
- location.default_item,
- None,
- new_region
- )
- event_item.code = None
- new_location.place_locked_item(event_item)
- #print("Placing event: " + event_item.name + " in location: " + location.name)
-
- new_region.locations.append(new_location)
- #print("created " + str(len(new_region.locations)) + " locations")
- self.multiworld.regions.append(new_region)
- #print("adding region: " + region_name)
- return new_region
-
-
- def create_items(self):
- skip_items: List[DarkCloud2Item] = []
- itempool: List[DarkCloud2Item] = []
- itempoolSize = 0
-
- #print("Creating items")
- for location in self.multiworld.get_locations(self.player):
- item_data = item_dictionary[location.default_item_name]
- if item_data.category in location_skip_categories:
- print("Adding skip item: " + location.default_item_name)
- skip_items.append(self.create_item(location.default_item_name))
- elif location.category in self.enabled_location_categories:
- print("Adding item: " + location.default_item_name)
- itempoolSize += 1
- itempool.append(self.create_item(location.default_item_name))
-
- print("Requesting itempool size: " + str(itempoolSize))
- foo = BuildItemPool(self.multiworld, itempoolSize, self.options)
- print("Created item pool size: " + str(len(foo)))
-
- removable_items = [item for item in itempool if item.classification != ItemClassification.progression]
- #print("marked " + str(len(removable_items)) + " items as removable")
-
- for item in removable_items:
- #print("removable item: " + item.name)
- itempool.remove(item)
- itempool.append(self.create_item(foo.pop().name))
-
- # Add regular items to itempool
- self.multiworld.itempool += itempool
-
- # Handle SKIP items separately
- #for skip_item in skip_items:
- # location = next(loc for loc in self.multiworld.get_locations(self.player) if loc.default_item_name == skip_item.name)
- # location.place_locked_item(skip_item)
- #self.multiworld.itempool.append(skip_item)
- #print("Placing skip item: " + skip_item.name + " in location: " + location.name)
-
- #print("Final Item pool: ")
- #for item in self.multiworld.itempool:
- #print(item.name)
-
-
-
-
- def create_item(self, name: str) -> Item:
- useful_categories = {
- # DC2ItemCategory.GEORAMA_RESOURCE, DC2ItemCategory.GEM, DC2ItemCategory.COIN
- }
- data = self.item_name_to_id[name]
-
- if name in key_item_names or item_dictionary[name].category in [DC2ItemCategory.KEY_ITEM]:
- item_classification = ItemClassification.progression
- elif item_dictionary[name].category in useful_categories:
- item_classification = ItemClassification.useful
- else:
- item_classification = ItemClassification.filler
-
- return DarkCloud2Item(name, item_classification, data, self.player)
-
-
- def get_filler_item_name(self) -> str:
- return "Bread"
-
- def set_rules(self) -> None:
- # Define the access rules to the entrances
- set_rule(self.multiworld.get_entrance("Palm Brinks -> Underground Water Channel", self.player), lambda state: True)
- set_rule(self.multiworld.get_entrance("Palm Brinks -> Sindain", self.player), lambda state: state.can_reach_location("UWC: Chapter 1 Complete", self.player))
- set_rule(self.multiworld.get_entrance("Sindain -> Rainbow Butterfly Wood", self.player), lambda state: state.has("Grape Juice", self.player))
- set_rule(self.multiworld.get_entrance("Palm Brinks -> Balance Valley", self.player), lambda state: state.can_reach_location("RBW: Chapter 2 Complete", self.player))
- set_rule(self.multiworld.get_entrance("Palm Brinks -> Veniccio", self.player), lambda state: state.can_reach_location("SC: Chapter 3 Complete", self.player))
- set_rule(self.multiworld.get_entrance("Palm Brinks -> Heim Rada", self.player), lambda state: state.can_reach_location("ORC: Chapter 4 Complete", self.player))
- set_rule(self.multiworld.get_entrance("Palm Brinks -> Moon Flower Palace", self.player), lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player))
-
- set_rule(self.multiworld.get_entrance("Sindain -> Jurak Mall", self.player), lambda state: state.has("Grape Juice", self.player) and state.can_reach_location("S: Grape Juice", self.player))
-
- # Chapter 1 floors
- set_rule(self.multiworld.get_location("UWC: Floor 1 - To the Outside World", self.player), lambda state: True)
- set_rule(self.multiworld.get_location("UWC: Floor 2 - Battle with Rats", self.player), lambda state: state.can_reach_location("UWC: Floor 1 - To the Outside World", self.player))
- set_rule(self.multiworld.get_location("UWC: Floor 3 - Ghosts in the Channel", self.player), lambda state: state.can_reach_location("UWC: Floor 2 - Battle with Rats", self.player))
- set_rule(self.multiworld.get_location("UWC: Pump Room", self.player), lambda state: state.can_reach_location("UWC: Floor 3 - Ghosts in the Channel", self.player))
- set_rule(self.multiworld.get_location("UWC: Linda", self.player), lambda state: state.can_reach_location("UWC: Floor 3 - Ghosts in the Channel", self.player))
- set_rule(self.multiworld.get_location("UWC: Floor 4 - Steve's Battle", self.player), lambda state: state.can_reach_location("UWC: Pump Room", self.player))
- set_rule(self.multiworld.get_location("UWC: Floor 5 - Ghost in the Channel", self.player), lambda state: state.can_reach_location("UWC: Floor 4 - Steve's Battle", self.player))
- set_rule(self.multiworld.get_location("UWC: Halloween", self.player), lambda state: state.can_reach_location("UWC: Floor 5 - Ghost in the Channel", self.player))
- set_rule(self.multiworld.get_location("UWC: Chapter 1 Complete", self.player), lambda state: state.can_reach_location("UWC: Halloween", self.player))
-
- # Chapter 2 floors
- set_rule(self.multiworld.get_location("RBW: Floor 1 - Frightening Forest", self.player), lambda state: state.can_reach_location("UWC: Chapter 1 Complete", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 2 - Strange Tree", self.player), lambda state: state.can_reach_location("RBW: Floor 1 - Frightening Forest", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 3 - Rolling Shells", self.player), lambda state: state.can_reach_location("RBW: Floor 2 - Strange Tree", self.player))
- set_rule(self.multiworld.get_location("RBW: Fish Monster Swamp", self.player), lambda state: state.can_reach_location("RBW: Floor 3 - Rolling Shells", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 4 - This is a Geostone?", self.player), lambda state: state.can_reach_location("RBW: Fish Monster Swamp", self.player) and state.has("Fishing Rod", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 5 - Noise in the Forest", self.player), lambda state: state.can_reach_location("RBW: Floor 4 - This is a Geostone?", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 6 - I'm a Pixie", self.player), lambda state: state.can_reach_location("RBW: Floor 5 - Noise in the Forest", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 7 - Legendary Killer Snake", self.player), lambda state: state.can_reach_location("RBW: Floor 6 - I'm a Pixie", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 8 - Grotesque Spider Lady", self.player), lambda state: state.can_reach_location("RBW: Floor 7 - Legendary Killer Snake", self.player))
- set_rule(self.multiworld.get_location("RBW: Rainbow Butterfly", self.player), lambda state: state.can_reach_location("RBW: Floor 8 - Grotesque Spider Lady", self.player) and state.has("Lafrescia Seed", self.player) and state.has("Sundrop", self.player))
- set_rule(self.multiworld.get_location("RBW: Chapter 2 Complete", self.player), lambda state: state.can_reach_location("RBW: Rainbow Butterfly", self.player))
-
- # RBW Star path floors
- set_rule(self.multiworld.get_location("RBW: Floor 9 - Looking for the Earth Gem", self.player), lambda state: state.can_reach_location("RBW: Chapter 2 Complete", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 10 - Something Rare Here!", self.player), lambda state: state.can_reach_location("RBW: Floor 9 - Looking for the Earth Gem", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("RBW: Floor 11 - Scary Tree", self.player), lambda state: state.can_reach_location("RBW: Floor 10 - Something Rare Here!", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("RBW: Trentos", self.player), lambda state: state.can_reach_location("RBW: Floor 11 - Scary Tree", self.player) and state.has("Star Key", self.player))
-
- # Chapter 3 floors
- set_rule(self.multiworld.get_location("SC: Floor 1 - Headlong Dash", self.player), lambda state: state.can_reach_location("RBW: Chapter 2 Complete", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 2 - Fire and Ice Don't Mix", self.player), lambda state: state.can_reach_location("SC: Floor 1 - Headlong Dash", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 3 - Earth-Shaking Demon", self.player), lambda state: state.can_reach_location("SC: Floor 2 - Fire and Ice Don't Mix", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 4 - Powerful Yo-Yo Robot", self.player), lambda state: state.can_reach_location("SC: Floor 3 - Earth-Shaking Demon", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 5 - Elephant Army in the Valley", self.player), lambda state: state.can_reach_location("SC: Floor 4 - Powerful Yo-Yo Robot", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 6 - Dangerous Treasure Chest", self.player), lambda state: state.can_reach_location("SC: Floor 5 - Elephant Army in the Valley", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 7 - Little Dragon Counterattack", self.player), lambda state: state.can_reach_location("SC: Floor 6 - Dangerous Treasure Chest", self.player))
- set_rule(self.multiworld.get_location("SC: Barga's Valley", self.player), lambda state: state.can_reach_location("SC: Floor 7 - Little Dragon Counterattack", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 8 - Warrior in Starlight Canyon", self.player), lambda state: state.can_reach_location("SC: Barga's Valley", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 9 - Smiling Fairy Village", self.player), lambda state: state.can_reach_location("SC: Floor 8 - Warrior in Starlight Canyon", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 10 - Cursed Mask", self.player), lambda state: state.can_reach_location("SC: Floor 9 - Smiling Fairy Village", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 11 - We're the Roly-Poly Brothers", self.player), lambda state: state.can_reach_location("SC: Floor 10 - Cursed Mask", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 12 - Dragon Slayer", self.player), lambda state: state.can_reach_location("SC: Floor 11 - We're the Roly-Poly Brothers", self.player))
- set_rule(self.multiworld.get_location("SC: Memo Eater", self.player), lambda state: state.can_reach_location("SC: Floor 12 - Dragon Slayer", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 13 - Rama Priests Like Cheese", self.player), lambda state: state.can_reach_location("SC: Memo Eater", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 14 - Nature's Threat", self.player), lambda state: state.can_reach_location("SC: Floor 13 - Rama Priests Like Cheese", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 15 - Moon Baron", self.player), lambda state: state.can_reach_location("SC: Floor 14 - Nature's Threat", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 16 - Lighthouse Appears", self.player), lambda state: state.can_reach_location("SC: Floor 15 - Moon Baron", self.player))
- set_rule(self.multiworld.get_location("SC: Evil Flame and Gaspard", self.player), lambda state: state.can_reach_location("SC: Floor 16 - Lighthouse Appears", self.player))
- set_rule(self.multiworld.get_location("SC: Chapter 3 Complete", self.player), lambda state: state.can_reach_location("SC: Evil Flame and Gaspard", self.player))
-
- # SC Star path floors
- set_rule(self.multiworld.get_location("SC: Floor 17 - Looking for the Wind Gem", self.player), lambda state: state.can_reach_location("SC: Chapter 3 Complete", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 18 - Evil Spirit in the Valley", self.player), lambda state: state.can_reach_location("SC: Floor 17 - Looking for the Wind Gem", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("SC: Floor 19 - Brave Warriors in the Valley", self.player), lambda state: state.can_reach_location("SC: Floor 18 - Evil Spirit in the Valley", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("SC: Lapis Garter", self.player), lambda state: state.can_reach_location("SC: Floor 19 - Brave Warriors in the Valley", self.player) and state.has("Star Key", self.player))
-
- # Chapter 4 floors
- set_rule(self.multiworld.get_location("ORC: Floor 1 - Pirates!", self.player), lambda state: state.can_reach_location("SC: Chapter 3 Complete", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 2 - Tons of Fish", self.player), lambda state: state.can_reach_location("ORC: Floor 1 - Pirates!", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 3 - Tank and Boss", self.player), lambda state: state.can_reach_location("ORC: Floor 2 - Tons of Fish", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 4 - Water Monster", self.player), lambda state: state.can_reach_location("ORC: Floor 3 - Tank and Boss", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 5 - Scary Auntie Medusa", self.player), lambda state: state.can_reach_location("ORC: Floor 4 - Water Monster", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 6 - Sand Molers", self.player), lambda state: state.can_reach_location("ORC: Floor 5 - Scary Auntie Medusa", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 7 - Bat Den", self.player), lambda state: state.can_reach_location("ORC: Floor 6 - Sand Molers", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 8 - Pirates' Hideout", self.player), lambda state: state.can_reach_location("ORC: Floor 7 - Bat Den", self.player))
- set_rule(self.multiworld.get_location("ORC: Cave of Ancient Murals", self.player), lambda state: state.can_reach_location("ORC: Floor 8 - Pirates' Hideout", self.player) and state.has("Electric Worm", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 9 - Wandering Zappy", self.player), lambda state: state.can_reach_location("ORC: Cave of Ancient Murals", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 10 - Banquet of the Dead", self.player), lambda state: state.can_reach_location("ORC: Floor 9 - Wandering Zappy", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 11 - Improvements", self.player), lambda state: state.can_reach_location("ORC: Floor 10 - Banquet of the Dead", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 12 - Return of the Serpent", self.player), lambda state: state.can_reach_location("ORC: Floor 11 - Improvements", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 13 - Cursed Sea", self.player), lambda state: state.can_reach_location("ORC: Floor 12 - Return of the Serpent", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 14 - Sea of Atrocity", self.player), lambda state: state.can_reach_location("ORC: Floor 13 - Cursed Sea", self.player))
- set_rule(self.multiworld.get_location("ORC: Dr. Jaming", self.player), lambda state: state.can_reach_location("ORC: Floor 14 - Sea of Atrocity", self.player) and state.has("Secret Dragon Remedy", self.player) and state.has("Shell Talkie", self.player))
- set_rule(self.multiworld.get_location("ORC: Chapter 4 Complete", self.player), lambda state: state.can_reach_location("ORC: Dr. Jaming", self.player))
-
- # ORC Star path floors
- set_rule(self.multiworld.get_location("ORC: Floor 15 - Looking for the Water Gem", self.player), lambda state: state.can_reach_location("ORC: Chapter 4 Complete", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 16 - Pirates' Revenge", self.player), lambda state: state.can_reach_location("ORC: Floor 15 - Looking for the Water Gem", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("ORC: Floor 17 - Death Ocean", self.player), lambda state: state.can_reach_location("ORC: Floor 16 - Pirates' Revenge", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("ORC: Sea Dragon", self.player), lambda state: state.can_reach_location("ORC: Floor 17 - Death Ocean", self.player) and state.has("Star Key", self.player))
-
- # Chapter 5 floors
- set_rule(self.multiworld.get_location("MG: Floor 1 - Battle with Griffon's Army", self.player), lambda state: state.can_reach_location("ORC: Chapter 4 Complete", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 2 - Mt. Gundor Wind", self.player), lambda state: state.can_reach_location("MG: Floor 1 - Battle with Griffon's Army", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 3 - Little Dragons on the Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 2 - Mt. Gundor Wind", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 4 - Steam Goyone", self.player), lambda state: state.can_reach_location("MG: Floor 3 - Little Dragons on the Mountain", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 5 - Mountain Baddie Appears", self.player), lambda state: state.can_reach_location("MG: Floor 4 - Steam Goyone", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 6 - Magmanoff", self.player), lambda state: state.can_reach_location("MG: Floor 5 - Mountain Baddie Appears", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 7 - Danger Zone", self.player), lambda state: state.can_reach_location("MG: Floor 6 - Magmanoff", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 8 - Secret of Fire Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 7 - Danger Zone", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 9 - Deathtrap", self.player), lambda state: state.can_reach_location("MG: Floor 8 - Secret of Fire Mountain", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 10 - Desperation on the Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 9 - Deathtrap", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 11 - Pains in the Neck", self.player), lambda state: state.can_reach_location("MG: Floor 10 - Desperation on the Mountain", self.player))
- set_rule(self.multiworld.get_location("MG: Mount Gundor Peak", self.player), lambda state: state.can_reach_location("MG: Floor 11 - Pains in the Neck", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 12 - Walking the Path of Flames", self.player), lambda state: state.can_reach_location("MG: Mount Gundor Peak", self.player) and state.has("Time Bomb", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 13 - Burning Undead", self.player), lambda state: state.can_reach_location("MG: Floor 12 - Walking the Path of Flames", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 14 - Fire Dragon", self.player), lambda state: state.can_reach_location("MG: Floor 13 - Burning Undead", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 15 - Treasure Chest Danger Zone", self.player), lambda state: state.can_reach_location("MG: Floor 14 - Fire Dragon", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 16 - Road to the River of Flames", self.player), lambda state: state.can_reach_location("MG: Floor 15 - Treasure Chest Danger Zone", self.player))
- set_rule(self.multiworld.get_location("MG: Gaspard", self.player), lambda state: state.can_reach_location("MG: Floor 16 - Road to the River of Flames", self.player) and state.has("Fire Horn", self.player))
- set_rule(self.multiworld.get_location("MG: Chapter 5 Complete", self.player), lambda state: state.can_reach_location("MG: Gaspard", self.player))
- # MG Star path floors
- set_rule(self.multiworld.get_location("MG: Floor 17 - Looking for the Fire Gem", self.player), lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 18 - Explosive Hot Spring", self.player), lambda state: state.can_reach_location("MG: Floor 17 - Looking for the Fire Gem", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("MG: Floor 19 - Crazy Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 18 - Explosive Hot Spring", self.player) and state.has("Star Key", self.player))
- set_rule(self.multiworld.get_location("MG: Inferno", self.player), lambda state: state.can_reach_location("MG: Floor 19 - Crazy Mountain", self.player) and state.has("Star Key", self.player))
-
- # Moon Flower Palace floors
- set_rule(self.multiworld.get_location("MFP: Floor 1 - Ancient Wind", self.player), lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 2 - Card Warriors Gather", self.player), lambda state: state.can_reach_location("MFP: Floor 1 - Ancient Wind", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 3 - Dangerous Treasure", self.player), lambda state: state.can_reach_location("MFP: Floor 2 - Card Warriors Gather", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 4 - Zombie Zone", self.player), lambda state: state.can_reach_location("MFP: Floor 3 - Dangerous Treasure", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 5 - Feeling Out of Place", self.player), lambda state: state.can_reach_location("MFP: Floor 4 - Zombie Zone", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 6 - Living Statue", self.player), lambda state: state.can_reach_location("MFP: Floor 5 - Feeling Out of Place", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 7 - Danger Zone", self.player), lambda state: state.can_reach_location("MFP: Floor 6 - Living Statue", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 8 - Scary Women", self.player), lambda state: state.can_reach_location("MFP: Floor 7 - Danger Zone", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 9 - Hell Elephant", self.player), lambda state: state.can_reach_location("MFP: Floor 8 - Scary Women", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 10 - Crush the Undead", self.player), lambda state: state.can_reach_location("MFP: Floor 9 - Hell Elephant", self.player))
- set_rule(self.multiworld.get_location("MFP: Garden", self.player), lambda state: state.can_reach_location("MFP: Floor 10 - Crush the Undead", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 11 - Missing Gem Dealer", self.player), lambda state: state.can_reach_location("MFP: Garden", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 12 - Max's Longest Day", self.player), lambda state: state.can_reach_location("MFP: Floor 11 - Missing Gem Dealer", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 13 - Hell's Corridor", self.player), lambda state: state.can_reach_location("MFP: Floor 12 - Max's Longest Day", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 14 - Monica All Alone", self.player), lambda state: state.can_reach_location("MFP: Floor 13 - Hell's Corridor", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 15 - Raging Spirits", self.player), lambda state: state.can_reach_location("MFP: Floor 14 - Monica All Alone", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 16 - Lonely Machine", self.player), lambda state: state.can_reach_location("MFP: Floor 15 - Raging Spirits", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 17 - Nobility", self.player), lambda state: state.can_reach_location("MFP: Floor 16 - Lonely Machine", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 18 - Palace Watchdog", self.player), lambda state: state.can_reach_location("MFP: Floor 17 - Nobility", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 19 - Road to Memories", self.player), lambda state: state.can_reach_location("MFP: Floor 18 - Palace Watchdog", self.player))
- set_rule(self.multiworld.get_location("MFP: Alexandra's Room", self.player), lambda state: state.can_reach_location("MFP: Floor 19 - Road to Memories", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 20 - Final Trump Card", self.player), lambda state: state.can_reach_location("MFP: Alexandra's Room", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 21 - Elemental Party", self.player), lambda state: state.can_reach_location("MFP: Floor 20 - Final Trump Card", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 22 - Wandering Knight's Soul", self.player), lambda state: state.can_reach_location("MFP: Floor 21 - Elemental Party", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 23 - Beware Carelessness", self.player), lambda state: state.can_reach_location("MFP: Floor 22 - Wandering Knight's Soul", self.player))
- set_rule(self.multiworld.get_location("MFP: Floor 24 - Final Battle", self.player), lambda state: state.can_reach_location("MFP: Floor 23 - Beware Carelessness", self.player))
-
- self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player)
-
-
- def fill_slot_data(self) -> Dict[str, object]:
- slot_data: Dict[str, object] = {}
- name_to_dc2_code = {item.name: item.dc2_code for item in item_dictionary.values()}
- # 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():
- if location.item.player == self.player:
- #we are the receiver of the item
- items_id.append(location.item.code)
- items_address.append(name_to_dc2_code[location.item.name])
-
-
- if location.player == self.player:
- #we are the sender of the location check
- locations_address.append(item_dictionary[location_dictionary[location.name].default_item].dc2_code)
- locations_id.append(location.address)
- if location.item.player == self.player:
- locations_target.append(name_to_dc2_code[location.item.name])
- else:
- locations_target.append(0)
-
- slot_data = {
- "options": {
- #"fishsanity": self.options.fishsanity.value,
- #"sphedasanity": self.options.sphedasanity.value,
- #"medalsanity": self.options.medalsanity.value,
- #"georamasanity": self.options.georamasanity.value,
- #"photosanity": self.options.photosanity.value,
- #"inventionsanity": self.options.inventionsanity.value,
- #"resource_pack_count": self.options.resource_pack_count.value,
- #"weapon_upgrade_pack_count": self.options.weapon_upgrade_pack_count.value,
- #"element_pack_count": self.options.element_pack_count.value,
- #"chapter_goal_count": self.options.chapter_goal_count.value,
- "abs_multiplier": self.options.abs_multiplier.value,
- "gilda_multiplier": self.options.gilda_multiplier.value,
- "guaranteed_items": self.options.guaranteed_items.value,
- "enable_enemy_randomiser": self.options.enable_enemy_randomiser.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
- }
-
- return slot_data
+# world/dc2/__init__.py
+from typing import Dict, Set, List
+
+from BaseClasses import MultiWorld, Region, Item, Entrance, Tutorial, ItemClassification
+from Options import Toggle
+
+from worlds.AutoWorld import World, WebWorld
+from worlds.generic.Rules import set_rule, add_rule, add_item_rule
+
+from .Items import DarkCloud2Item, DC2ItemCategory, item_dictionary, key_item_names, item_descriptions, BuildItemPool
+from .Locations import DarkCloud2Location, DC2LocationCategory, location_tables, location_dictionary, location_skip_categories
+from .Options import DC2Option
+
+class DarkCloud2Web(WebWorld):
+ bug_report_page = ""
+ theme = "stone"
+ setup_en = Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up the Archipelago Dark Cloud 2 randomizer on your computer.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["ArsonAssassin"]
+ )
+
+
+ tutorials = [setup_en]
+
+
+class DarkCloud2World(World):
+ """
+ Dark Cloud 2 is a game.
+ """
+
+ game: str = "Dark Cloud 2"
+ is_experimental = True
+ options_dataclass = DC2Option
+ options: DC2Option
+ topology_present: bool = True
+ web = DarkCloud2Web()
+ data_version = 0
+ base_id = 694200000
+ enabled_location_categories: Set[DC2LocationCategory]
+ required_client_version = (0, 5, 1)
+ item_name_to_id = DarkCloud2Item.get_name_to_id()
+ location_name_to_id = DarkCloud2Location.get_name_to_id()
+ item_name_groups = {
+ }
+ item_descriptions = item_descriptions
+ explicit_indirect_conditions = False
+
+ 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):
+ self.enabled_location_categories.add(DC2LocationCategory.FLOOR)
+ self.enabled_location_categories.add(DC2LocationCategory.RECRUIT)
+ self.enabled_location_categories.add(DC2LocationCategory.GEORAMA)
+ self.enabled_location_categories.add(DC2LocationCategory.MIRACLE_CHEST)
+ self.enabled_location_categories.add(DC2LocationCategory.BOSS)
+ self.enabled_location_categories.add(DC2LocationCategory.MISC)
+ self.enabled_location_categories.add(DC2LocationCategory.GEOSTONE)
+ # self.enabled_location_categories.add(DC2LocationCategory.EVENT)
+ self.enabled_location_categories.add(DC2LocationCategory.KEY_ITEM)
+
+
+ def create_regions(self):
+ # Create Regions
+ regions: Dict[str, Region] = {}
+ regions["Menu"] = self.create_region("Menu", [])
+ regions.update({region_name: self.create_region(region_name, location_tables[region_name]) for region_name in [
+ "Palm Brinks",
+ "Underground Water Channel",
+ "Sindain",
+ "Rainbow Butterfly Wood",
+ "Jurak Mall",
+ "Balance Valley",
+ "Starlight Canyon",
+ "Starlight Temple",
+ "Veniccio",
+ "Ocean's Roar Cave",
+ "Lunatic Wisdom Laboratories",
+ "Heim Rada",
+ "Mount Gundor",
+ "Gundorada Workshop",
+ "Moon Flower Palace"
+ ]})
+
+
+ # Connect Regions
+ def create_connection(from_region: str, to_region: str):
+ connection = Entrance(self.player, f"{from_region} -> {to_region}", regions[from_region])
+ regions[from_region].exits.append(connection)
+ connection.connect(regions[to_region])
+
+ create_connection("Menu", "Palm Brinks")
+
+ create_connection("Palm Brinks", "Underground Water Channel")
+
+ create_connection("Palm Brinks", "Sindain")
+ create_connection("Palm Brinks", "Balance Valley")
+ create_connection("Palm Brinks", "Veniccio")
+ create_connection("Palm Brinks", "Heim Rada")
+ create_connection("Palm Brinks", "Moon Flower Palace")
+
+ create_connection("Sindain", "Rainbow Butterfly Wood")
+ create_connection("Sindain", "Jurak Mall")
+
+ create_connection("Balance Valley", "Starlight Canyon")
+ create_connection("Balance Valley", "Starlight Temple")
+
+ create_connection("Veniccio", "Ocean's Roar Cave")
+ create_connection("Veniccio", "Lunatic Wisdom Laboratories")
+
+ create_connection("Heim Rada", "Mount Gundor")
+ create_connection("Heim Rada", "Gundorada Workshop")
+
+
+ # 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)
+ #print("location table size: " + str(len(location_table)))
+ for location in location_table:
+ #print("Creating location: " + location.name)
+ if location.category in self.enabled_location_categories and location.category not in location_skip_categories:
+ #print("Adding location: " + location.name + " with default item " + location.default_item)
+ new_location = DarkCloud2Location(
+ self.player,
+ location.name,
+ location.category,
+ location.default_item,
+ self.location_name_to_id[location.name],
+ new_region
+ )
+ else:
+ # Replace non-randomized progression items with events
+ event_item = self.create_item(location.default_item)
+ #if event_item.classification != ItemClassification.progression:
+ # continue
+ #print("Adding Location: " + location.name + " as an event with default item " + location.default_item)
+ new_location = DarkCloud2Location(
+ self.player,
+ location.name,
+ location.category,
+ location.default_item,
+ None,
+ new_region
+ )
+ event_item.code = None
+ new_location.place_locked_item(event_item)
+ #print("Placing event: " + event_item.name + " in location: " + location.name)
+
+ new_region.locations.append(new_location)
+ #print("created " + str(len(new_region.locations)) + " locations")
+ self.multiworld.regions.append(new_region)
+ #print("adding region: " + region_name)
+ return new_region
+
+
+ def create_items(self):
+ skip_items: List[DarkCloud2Item] = []
+ itempool: List[DarkCloud2Item] = []
+ itempoolSize = 0
+
+ #print("Creating items")
+ for location in self.multiworld.get_locations(self.player):
+ item_data = item_dictionary[location.default_item_name]
+ if item_data.category in location_skip_categories:
+ print("Adding skip item: " + location.default_item_name)
+ skip_items.append(self.create_item(location.default_item_name))
+ elif location.category in self.enabled_location_categories:
+ print("Adding item: " + location.default_item_name)
+ itempoolSize += 1
+ itempool.append(self.create_item(location.default_item_name))
+
+ print("Requesting itempool size: " + str(itempoolSize))
+ foo = BuildItemPool(self.multiworld, itempoolSize, self.options)
+ print("Created item pool size: " + str(len(foo)))
+
+ removable_items = [item for item in itempool if item.classification != ItemClassification.progression]
+ #print("marked " + str(len(removable_items)) + " items as removable")
+
+ for item in removable_items:
+ #print("removable item: " + item.name)
+ itempool.remove(item)
+ itempool.append(self.create_item(foo.pop().name))
+
+ # Add regular items to itempool
+ self.multiworld.itempool += itempool
+
+ # Handle SKIP items separately
+ #for skip_item in skip_items:
+ # location = next(loc for loc in self.multiworld.get_locations(self.player) if loc.default_item_name == skip_item.name)
+ # location.place_locked_item(skip_item)
+ #self.multiworld.itempool.append(skip_item)
+ #print("Placing skip item: " + skip_item.name + " in location: " + location.name)
+
+ #print("Final Item pool: ")
+ #for item in self.multiworld.itempool:
+ #print(item.name)
+
+
+
+
+ def create_item(self, name: str) -> Item:
+ useful_categories = {
+ # DC2ItemCategory.GEORAMA_RESOURCE, DC2ItemCategory.GEM, DC2ItemCategory.COIN
+ }
+ data = self.item_name_to_id[name]
+
+ if name in key_item_names or item_dictionary[name].category in [DC2ItemCategory.KEY_ITEM]:
+ item_classification = ItemClassification.progression
+ elif item_dictionary[name].category in useful_categories:
+ item_classification = ItemClassification.useful
+ else:
+ item_classification = ItemClassification.filler
+
+ return DarkCloud2Item(name, item_classification, data, self.player)
+
+
+ def get_filler_item_name(self) -> str:
+ return "Bread"
+
+ def set_rules(self) -> None:
+ # Define the access rules to the entrances
+ set_rule(self.multiworld.get_entrance("Palm Brinks -> Underground Water Channel", self.player), lambda state: True)
+ set_rule(self.multiworld.get_entrance("Palm Brinks -> Sindain", self.player), lambda state: state.can_reach_location("UWC: Chapter 1 Complete", self.player))
+ set_rule(self.multiworld.get_entrance("Sindain -> Rainbow Butterfly Wood", self.player), lambda state: state.has("Grape Juice", self.player))
+ set_rule(self.multiworld.get_entrance("Palm Brinks -> Balance Valley", self.player), lambda state: state.can_reach_location("RBW: Chapter 2 Complete", self.player))
+ set_rule(self.multiworld.get_entrance("Palm Brinks -> Veniccio", self.player), lambda state: state.can_reach_location("SC: Chapter 3 Complete", self.player))
+ set_rule(self.multiworld.get_entrance("Palm Brinks -> Heim Rada", self.player), lambda state: state.can_reach_location("ORC: Chapter 4 Complete", self.player))
+ set_rule(self.multiworld.get_entrance("Palm Brinks -> Moon Flower Palace", self.player), lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player))
+
+ set_rule(self.multiworld.get_entrance("Sindain -> Jurak Mall", self.player), lambda state: state.has("Grape Juice", self.player) and state.can_reach_location("S: Grape Juice", self.player))
+
+ # Chapter 1 floors
+ set_rule(self.multiworld.get_location("UWC: Floor 1 - To the Outside World", self.player), lambda state: True)
+ set_rule(self.multiworld.get_location("UWC: Floor 2 - Battle with Rats", self.player), lambda state: state.can_reach_location("UWC: Floor 1 - To the Outside World", self.player))
+ set_rule(self.multiworld.get_location("UWC: Floor 3 - Ghosts in the Channel", self.player), lambda state: state.can_reach_location("UWC: Floor 2 - Battle with Rats", self.player))
+ set_rule(self.multiworld.get_location("UWC: Pump Room", self.player), lambda state: state.can_reach_location("UWC: Floor 3 - Ghosts in the Channel", self.player))
+ set_rule(self.multiworld.get_location("UWC: Linda", self.player), lambda state: state.can_reach_location("UWC: Floor 3 - Ghosts in the Channel", self.player))
+ set_rule(self.multiworld.get_location("UWC: Floor 4 - Steve's Battle", self.player), lambda state: state.can_reach_location("UWC: Pump Room", self.player))
+ set_rule(self.multiworld.get_location("UWC: Floor 5 - Ghost in the Channel", self.player), lambda state: state.can_reach_location("UWC: Floor 4 - Steve's Battle", self.player))
+ set_rule(self.multiworld.get_location("UWC: Halloween", self.player), lambda state: state.can_reach_location("UWC: Floor 5 - Ghost in the Channel", self.player))
+ set_rule(self.multiworld.get_location("UWC: Chapter 1 Complete", self.player), lambda state: state.can_reach_location("UWC: Halloween", self.player))
+
+ # Chapter 2 floors
+ set_rule(self.multiworld.get_location("RBW: Floor 1 - Frightening Forest", self.player), lambda state: state.can_reach_location("UWC: Chapter 1 Complete", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 2 - Strange Tree", self.player), lambda state: state.can_reach_location("RBW: Floor 1 - Frightening Forest", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 3 - Rolling Shells", self.player), lambda state: state.can_reach_location("RBW: Floor 2 - Strange Tree", self.player))
+ set_rule(self.multiworld.get_location("RBW: Fish Monster Swamp", self.player), lambda state: state.can_reach_location("RBW: Floor 3 - Rolling Shells", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 4 - This is a Geostone?", self.player), lambda state: state.can_reach_location("RBW: Fish Monster Swamp", self.player) and state.has("Fishing Rod", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 5 - Noise in the Forest", self.player), lambda state: state.can_reach_location("RBW: Floor 4 - This is a Geostone?", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 6 - I'm a Pixie", self.player), lambda state: state.can_reach_location("RBW: Floor 5 - Noise in the Forest", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 7 - Legendary Killer Snake", self.player), lambda state: state.can_reach_location("RBW: Floor 6 - I'm a Pixie", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 8 - Grotesque Spider Lady", self.player), lambda state: state.can_reach_location("RBW: Floor 7 - Legendary Killer Snake", self.player))
+ set_rule(self.multiworld.get_location("RBW: Rainbow Butterfly", self.player), lambda state: state.can_reach_location("RBW: Floor 8 - Grotesque Spider Lady", self.player) and state.has("Lafrescia Seed", self.player) and state.has("Sundrop", self.player))
+ set_rule(self.multiworld.get_location("RBW: Chapter 2 Complete", self.player), lambda state: state.can_reach_location("RBW: Rainbow Butterfly", self.player))
+
+ # RBW Star path floors
+ set_rule(self.multiworld.get_location("RBW: Floor 9 - Looking for the Earth Gem", self.player), lambda state: state.can_reach_location("RBW: Chapter 2 Complete", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 10 - Something Rare Here!", self.player), lambda state: state.can_reach_location("RBW: Floor 9 - Looking for the Earth Gem", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("RBW: Floor 11 - Scary Tree", self.player), lambda state: state.can_reach_location("RBW: Floor 10 - Something Rare Here!", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("RBW: Trentos", self.player), lambda state: state.can_reach_location("RBW: Floor 11 - Scary Tree", self.player) and state.has("Star Key", self.player))
+
+ # Chapter 3 floors
+ set_rule(self.multiworld.get_location("SC: Floor 1 - Headlong Dash", self.player), lambda state: state.can_reach_location("RBW: Chapter 2 Complete", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 2 - Fire and Ice Don't Mix", self.player), lambda state: state.can_reach_location("SC: Floor 1 - Headlong Dash", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 3 - Earth-Shaking Demon", self.player), lambda state: state.can_reach_location("SC: Floor 2 - Fire and Ice Don't Mix", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 4 - Powerful Yo-Yo Robot", self.player), lambda state: state.can_reach_location("SC: Floor 3 - Earth-Shaking Demon", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 5 - Elephant Army in the Valley", self.player), lambda state: state.can_reach_location("SC: Floor 4 - Powerful Yo-Yo Robot", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 6 - Dangerous Treasure Chest", self.player), lambda state: state.can_reach_location("SC: Floor 5 - Elephant Army in the Valley", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 7 - Little Dragon Counterattack", self.player), lambda state: state.can_reach_location("SC: Floor 6 - Dangerous Treasure Chest", self.player))
+ set_rule(self.multiworld.get_location("SC: Barga's Valley", self.player), lambda state: state.can_reach_location("SC: Floor 7 - Little Dragon Counterattack", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 8 - Warrior in Starlight Canyon", self.player), lambda state: state.can_reach_location("SC: Barga's Valley", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 9 - Smiling Fairy Village", self.player), lambda state: state.can_reach_location("SC: Floor 8 - Warrior in Starlight Canyon", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 10 - Cursed Mask", self.player), lambda state: state.can_reach_location("SC: Floor 9 - Smiling Fairy Village", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 11 - We're the Roly-Poly Brothers", self.player), lambda state: state.can_reach_location("SC: Floor 10 - Cursed Mask", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 12 - Dragon Slayer", self.player), lambda state: state.can_reach_location("SC: Floor 11 - We're the Roly-Poly Brothers", self.player))
+ set_rule(self.multiworld.get_location("SC: Memo Eater", self.player), lambda state: state.can_reach_location("SC: Floor 12 - Dragon Slayer", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 13 - Rama Priests Like Cheese", self.player), lambda state: state.can_reach_location("SC: Memo Eater", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 14 - Nature's Threat", self.player), lambda state: state.can_reach_location("SC: Floor 13 - Rama Priests Like Cheese", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 15 - Moon Baron", self.player), lambda state: state.can_reach_location("SC: Floor 14 - Nature's Threat", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 16 - Lighthouse Appears", self.player), lambda state: state.can_reach_location("SC: Floor 15 - Moon Baron", self.player))
+ set_rule(self.multiworld.get_location("SC: Evil Flame and Gaspard", self.player), lambda state: state.can_reach_location("SC: Floor 16 - Lighthouse Appears", self.player))
+ set_rule(self.multiworld.get_location("SC: Chapter 3 Complete", self.player), lambda state: state.can_reach_location("SC: Evil Flame and Gaspard", self.player))
+
+ # SC Star path floors
+ set_rule(self.multiworld.get_location("SC: Floor 17 - Looking for the Wind Gem", self.player), lambda state: state.can_reach_location("SC: Chapter 3 Complete", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 18 - Evil Spirit in the Valley", self.player), lambda state: state.can_reach_location("SC: Floor 17 - Looking for the Wind Gem", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("SC: Floor 19 - Brave Warriors in the Valley", self.player), lambda state: state.can_reach_location("SC: Floor 18 - Evil Spirit in the Valley", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("SC: Lapis Garter", self.player), lambda state: state.can_reach_location("SC: Floor 19 - Brave Warriors in the Valley", self.player) and state.has("Star Key", self.player))
+
+ # Chapter 4 floors
+ set_rule(self.multiworld.get_location("ORC: Floor 1 - Pirates!", self.player), lambda state: state.can_reach_location("SC: Chapter 3 Complete", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 2 - Tons of Fish", self.player), lambda state: state.can_reach_location("ORC: Floor 1 - Pirates!", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 3 - Tank and Boss", self.player), lambda state: state.can_reach_location("ORC: Floor 2 - Tons of Fish", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 4 - Water Monster", self.player), lambda state: state.can_reach_location("ORC: Floor 3 - Tank and Boss", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 5 - Scary Auntie Medusa", self.player), lambda state: state.can_reach_location("ORC: Floor 4 - Water Monster", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 6 - Sand Molers", self.player), lambda state: state.can_reach_location("ORC: Floor 5 - Scary Auntie Medusa", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 7 - Bat Den", self.player), lambda state: state.can_reach_location("ORC: Floor 6 - Sand Molers", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 8 - Pirates' Hideout", self.player), lambda state: state.can_reach_location("ORC: Floor 7 - Bat Den", self.player))
+ set_rule(self.multiworld.get_location("ORC: Cave of Ancient Murals", self.player), lambda state: state.can_reach_location("ORC: Floor 8 - Pirates' Hideout", self.player) and state.has("Electric Worm", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 9 - Wandering Zappy", self.player), lambda state: state.can_reach_location("ORC: Cave of Ancient Murals", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 10 - Banquet of the Dead", self.player), lambda state: state.can_reach_location("ORC: Floor 9 - Wandering Zappy", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 11 - Improvements", self.player), lambda state: state.can_reach_location("ORC: Floor 10 - Banquet of the Dead", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 12 - Return of the Serpent", self.player), lambda state: state.can_reach_location("ORC: Floor 11 - Improvements", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 13 - Cursed Sea", self.player), lambda state: state.can_reach_location("ORC: Floor 12 - Return of the Serpent", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 14 - Sea of Atrocity", self.player), lambda state: state.can_reach_location("ORC: Floor 13 - Cursed Sea", self.player))
+ set_rule(self.multiworld.get_location("ORC: Dr. Jaming", self.player), lambda state: state.can_reach_location("ORC: Floor 14 - Sea of Atrocity", self.player) and state.has("Secret Dragon Remedy", self.player) and state.has("Shell Talkie", self.player))
+ set_rule(self.multiworld.get_location("ORC: Chapter 4 Complete", self.player), lambda state: state.can_reach_location("ORC: Dr. Jaming", self.player))
+
+ # ORC Star path floors
+ set_rule(self.multiworld.get_location("ORC: Floor 15 - Looking for the Water Gem", self.player), lambda state: state.can_reach_location("ORC: Chapter 4 Complete", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 16 - Pirates' Revenge", self.player), lambda state: state.can_reach_location("ORC: Floor 15 - Looking for the Water Gem", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("ORC: Floor 17 - Death Ocean", self.player), lambda state: state.can_reach_location("ORC: Floor 16 - Pirates' Revenge", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("ORC: Sea Dragon", self.player), lambda state: state.can_reach_location("ORC: Floor 17 - Death Ocean", self.player) and state.has("Star Key", self.player))
+
+ # Chapter 5 floors
+ set_rule(self.multiworld.get_location("MG: Floor 1 - Battle with Griffon's Army", self.player), lambda state: state.can_reach_location("ORC: Chapter 4 Complete", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 2 - Mt. Gundor Wind", self.player), lambda state: state.can_reach_location("MG: Floor 1 - Battle with Griffon's Army", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 3 - Little Dragons on the Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 2 - Mt. Gundor Wind", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 4 - Steam Goyone", self.player), lambda state: state.can_reach_location("MG: Floor 3 - Little Dragons on the Mountain", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 5 - Mountain Baddie Appears", self.player), lambda state: state.can_reach_location("MG: Floor 4 - Steam Goyone", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 6 - Magmanoff", self.player), lambda state: state.can_reach_location("MG: Floor 5 - Mountain Baddie Appears", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 7 - Danger Zone", self.player), lambda state: state.can_reach_location("MG: Floor 6 - Magmanoff", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 8 - Secret of Fire Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 7 - Danger Zone", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 9 - Deathtrap", self.player), lambda state: state.can_reach_location("MG: Floor 8 - Secret of Fire Mountain", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 10 - Desperation on the Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 9 - Deathtrap", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 11 - Pains in the Neck", self.player), lambda state: state.can_reach_location("MG: Floor 10 - Desperation on the Mountain", self.player))
+ set_rule(self.multiworld.get_location("MG: Mount Gundor Peak", self.player), lambda state: state.can_reach_location("MG: Floor 11 - Pains in the Neck", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 12 - Walking the Path of Flames", self.player), lambda state: state.can_reach_location("MG: Mount Gundor Peak", self.player) and state.has("Time Bomb", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 13 - Burning Undead", self.player), lambda state: state.can_reach_location("MG: Floor 12 - Walking the Path of Flames", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 14 - Fire Dragon", self.player), lambda state: state.can_reach_location("MG: Floor 13 - Burning Undead", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 15 - Treasure Chest Danger Zone", self.player), lambda state: state.can_reach_location("MG: Floor 14 - Fire Dragon", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 16 - Road to the River of Flames", self.player), lambda state: state.can_reach_location("MG: Floor 15 - Treasure Chest Danger Zone", self.player))
+ set_rule(self.multiworld.get_location("MG: Gaspard", self.player), lambda state: state.can_reach_location("MG: Floor 16 - Road to the River of Flames", self.player) and state.has("Fire Horn", self.player))
+ set_rule(self.multiworld.get_location("MG: Chapter 5 Complete", self.player), lambda state: state.can_reach_location("MG: Gaspard", self.player))
+ # MG Star path floors
+ set_rule(self.multiworld.get_location("MG: Floor 17 - Looking for the Fire Gem", self.player), lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 18 - Explosive Hot Spring", self.player), lambda state: state.can_reach_location("MG: Floor 17 - Looking for the Fire Gem", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("MG: Floor 19 - Crazy Mountain", self.player), lambda state: state.can_reach_location("MG: Floor 18 - Explosive Hot Spring", self.player) and state.has("Star Key", self.player))
+ set_rule(self.multiworld.get_location("MG: Inferno", self.player), lambda state: state.can_reach_location("MG: Floor 19 - Crazy Mountain", self.player) and state.has("Star Key", self.player))
+
+ # Moon Flower Palace floors
+ set_rule(self.multiworld.get_location("MFP: Floor 1 - Ancient Wind", self.player), lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 2 - Card Warriors Gather", self.player), lambda state: state.can_reach_location("MFP: Floor 1 - Ancient Wind", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 3 - Dangerous Treasure", self.player), lambda state: state.can_reach_location("MFP: Floor 2 - Card Warriors Gather", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 4 - Zombie Zone", self.player), lambda state: state.can_reach_location("MFP: Floor 3 - Dangerous Treasure", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 5 - Feeling Out of Place", self.player), lambda state: state.can_reach_location("MFP: Floor 4 - Zombie Zone", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 6 - Living Statue", self.player), lambda state: state.can_reach_location("MFP: Floor 5 - Feeling Out of Place", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 7 - Danger Zone", self.player), lambda state: state.can_reach_location("MFP: Floor 6 - Living Statue", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 8 - Scary Women", self.player), lambda state: state.can_reach_location("MFP: Floor 7 - Danger Zone", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 9 - Hell Elephant", self.player), lambda state: state.can_reach_location("MFP: Floor 8 - Scary Women", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 10 - Crush the Undead", self.player), lambda state: state.can_reach_location("MFP: Floor 9 - Hell Elephant", self.player))
+ set_rule(self.multiworld.get_location("MFP: Garden", self.player), lambda state: state.can_reach_location("MFP: Floor 10 - Crush the Undead", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 11 - Missing Gem Dealer", self.player), lambda state: state.can_reach_location("MFP: Garden", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 12 - Max's Longest Day", self.player), lambda state: state.can_reach_location("MFP: Floor 11 - Missing Gem Dealer", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 13 - Hell's Corridor", self.player), lambda state: state.can_reach_location("MFP: Floor 12 - Max's Longest Day", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 14 - Monica All Alone", self.player), lambda state: state.can_reach_location("MFP: Floor 13 - Hell's Corridor", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 15 - Raging Spirits", self.player), lambda state: state.can_reach_location("MFP: Floor 14 - Monica All Alone", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 16 - Lonely Machine", self.player), lambda state: state.can_reach_location("MFP: Floor 15 - Raging Spirits", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 17 - Nobility", self.player), lambda state: state.can_reach_location("MFP: Floor 16 - Lonely Machine", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 18 - Palace Watchdog", self.player), lambda state: state.can_reach_location("MFP: Floor 17 - Nobility", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 19 - Road to Memories", self.player), lambda state: state.can_reach_location("MFP: Floor 18 - Palace Watchdog", self.player))
+ set_rule(self.multiworld.get_location("MFP: Alexandra's Room", self.player), lambda state: state.can_reach_location("MFP: Floor 19 - Road to Memories", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 20 - Final Trump Card", self.player), lambda state: state.can_reach_location("MFP: Alexandra's Room", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 21 - Elemental Party", self.player), lambda state: state.can_reach_location("MFP: Floor 20 - Final Trump Card", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 22 - Wandering Knight's Soul", self.player), lambda state: state.can_reach_location("MFP: Floor 21 - Elemental Party", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 23 - Beware Carelessness", self.player), lambda state: state.can_reach_location("MFP: Floor 22 - Wandering Knight's Soul", self.player))
+ set_rule(self.multiworld.get_location("MFP: Floor 24 - Final Battle", self.player), lambda state: state.can_reach_location("MFP: Floor 23 - Beware Carelessness", self.player))
+
+ self.multiworld.completion_condition[self.player] = lambda state: state.can_reach_location("MG: Chapter 5 Complete", self.player)
+
+
+ def fill_slot_data(self) -> Dict[str, object]:
+ slot_data: Dict[str, object] = {}
+ name_to_dc2_code = {item.name: item.dc2_code for item in item_dictionary.values()}
+ # 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():
+ if location.item.player == self.player:
+ #we are the receiver of the item
+ items_id.append(location.item.code)
+ items_address.append(name_to_dc2_code[location.item.name])
+
+
+ if location.player == self.player:
+ #we are the sender of the location check
+ locations_address.append(item_dictionary[location_dictionary[location.name].default_item].dc2_code)
+ locations_id.append(location.address)
+ if location.item.player == self.player:
+ locations_target.append(name_to_dc2_code[location.item.name])
+ else:
+ locations_target.append(0)
+
+ slot_data = {
+ "options": {
+ #"fishsanity": self.options.fishsanity.value,
+ #"sphedasanity": self.options.sphedasanity.value,
+ #"medalsanity": self.options.medalsanity.value,
+ #"georamasanity": self.options.georamasanity.value,
+ #"photosanity": self.options.photosanity.value,
+ #"inventionsanity": self.options.inventionsanity.value,
+ #"resource_pack_count": self.options.resource_pack_count.value,
+ #"weapon_upgrade_pack_count": self.options.weapon_upgrade_pack_count.value,
+ #"element_pack_count": self.options.element_pack_count.value,
+ #"chapter_goal_count": self.options.chapter_goal_count.value,
+ "abs_multiplier": self.options.abs_multiplier.value,
+ "gilda_multiplier": self.options.gilda_multiplier.value,
+ "guaranteed_items": self.options.guaranteed_items.value,
+ "enable_enemy_randomiser": self.options.enable_enemy_randomiser.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
+ }
+
+ return slot_data
diff --git a/worlds/dc2/archipelago.json b/worlds/dc2/archipelago.json
new file mode 100644
index 000000000000..fdd2a956fdb8
--- /dev/null
+++ b/worlds/dc2/archipelago.json
@@ -0,0 +1,10 @@
+{
+ "version": 7,
+ "authors": [
+ "ArsonAssassin"
+ ],
+ "game": "Dark Cloud 2",
+ "compatible_version": 7,
+ "minimum_ap_version": "0.6.4",
+ "world_version": "0.0.4"
+}
diff --git a/worlds/diddy_kong_racing/DKRClient.py b/worlds/diddy_kong_racing/DKRClient.py
index 85110b365537..b4cf449fc10e 100644
--- a/worlds/diddy_kong_racing/DKRClient.py
+++ b/worlds/diddy_kong_racing/DKRClient.py
@@ -1,414 +1,414 @@
-from __future__ import annotations
-
-import asyncio
-import copy
-import json
-import multiprocessing
-import sys
-
-# CommonClient import first to trigger ModuleUpdater
-from CommonClient import ClientCommandProcessor, CommonContext, get_base_parser, gui_enabled, logger, server_loop
-from Utils import async_start, init_logging
-from worlds import network_data_package
-from .RomPatcher import apply_patch
-
-SYSTEM_MESSAGE_ID = 0
-
-CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_diddy_kong_racing.lua"
-CONNECTION_REFUSED_STATUS = "Connection refused. Please start your emulator and make sure connector_diddy_kong_racing.lua is running"
-CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart connector_diddy_kong_racing.lua"
-CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
-CONNECTION_CONNECTED_STATUS = "Connected"
-CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
-
-"""
-Payload: lua -> client
-{
- playerName: string,
- locations: dict,
- gameComplete: bool
-}
-
-Payload: client -> lua
-{
- items: list,
- playerNames: list,
- messages: string
-}
-
-"""
-
-logger.info(network_data_package["games"].keys())
-dkr_loc_name_to_id: dict[str, int] = network_data_package["games"]["Diddy Kong Racing"]["location_name_to_id"]
-dkr_itm_name_to_id: dict[str, int] = network_data_package["games"]["Diddy Kong Racing"]["item_name_to_id"]
-
-version_number: str = "v1.1.0"
-apworld_version: str = "DKR" + version_number
-patched_rom_filename: str = "Diddy-Kong-Racing-AP-" + version_number + ".z64"
-vanilla_rom_md5: str = "4f0e07f0eeac7e5d7ce3a75461888d03"
-vanilla_swapped_rom_md5: str = "e00c0e6bfb0ce740e3e1c50ba82bc01a"
-patched_rom_md5: str = "f137ca9527a7dbe93f1c30bf205f0e5a"
-
-
-class DiddyKongRacingCommandProcessor(ClientCommandProcessor):
- def __init__(self, ctx: CommonContext) -> None:
- super().__init__(ctx)
-
- def _cmd_n64(self) -> None:
- """Check N64 Connection State"""
- if isinstance(self.ctx, DiddyKongRacingContext):
- logger.info(f"N64 Status: {self.ctx.n64_status}")
-
-
-class DiddyKongRacingContext(CommonContext):
- command_processor = DiddyKongRacingCommandProcessor
- items_handling = 0b111 # full
-
- def __init__(self, server_address: str | None, password: str | None) -> None:
- super().__init__(server_address, password)
- self.game = "Diddy Kong Racing"
- self.n64_streams: (asyncio.StreamReader, asyncio.StreamWriter) = None # type: ignore
- self.n64_sync_task = None
- self.n64_status = CONNECTION_INITIAL_STATUS
- self.awaiting_rom = False
- self.location_table = {}
- self.version_warning = False
- self.messages = {}
- self.slot_data = {}
- self.sendSlot = False
- self.sync_ready = False
- self.startup = False
- self.current_map = 0
-
- async def server_auth(self, password_requested: bool = False) -> None:
- if password_requested and not self.password:
- await super(DiddyKongRacingContext, self).server_auth(password_requested)
-
- if not self.auth:
- await self.get_username()
- await self.send_connect()
- self.awaiting_rom = True
-
- return
-
- return
-
- def set_message(self, msg: dict) -> None:
- self.messages.update({len(self.messages) + 1: msg})
-
- def run_gui(self) -> None:
- from kvui import GameManager
-
- class DiddyKongRacingManager(GameManager):
- logging_pairs = [
- ("Client", "Archipelago")
- ]
- base_title = "Archipelago Diddy Kong Racing Client"
-
- self.ui = DiddyKongRacingManager(self)
- self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
- asyncio.create_task(apply_patch(version_number))
-
- def on_package(self, cmd: str, args: dict) -> None:
- if cmd == "Connected":
- self.slot_data = args.get("slot_data")
- generated_apworld_version = self.slot_data["apworld_version"]
-
- if apworld_version != generated_apworld_version:
- error_message = "Your Diddy Kong Racing apworld version (" + apworld_version + ") does not match the generated world's apworld version (" + generated_apworld_version + ")."
- logger.error(error_message)
- raise Exception(error_message)
-
- logger.info("Please open Diddy Kong Racing and load connector_diddy_kong_racing.lua")
- self.n64_sync_task = asyncio.create_task(n64_sync_task(self), name="N64 Sync")
- elif cmd == "ReceivedItems":
- if not self.startup:
- for item in args["items"]:
- player = ""
- item_name = ""
- for (i, name) in self.player_names.items():
- if i == item.player:
- player = name
- break
-
- for (name, i) in dkr_itm_name_to_id.items():
- if item.item == i:
- item_name = name
- break
- logger.info(player + " sent " + item_name)
- logger.info("The above items will be sent when Diddy Kong Racing is loaded.")
- self.startup = True
-
- def on_print_json(self, args: dict) -> None:
- if self.ui:
- self.ui.print_json(copy.deepcopy(args["data"]))
- else:
- logger.info(self.jsontotextparser(copy.deepcopy(args["data"])))
-
- if (args.get("type", None) in {"ItemSend"}
- and (not self.ui
- or self.slot_concerns_self(args["receiving"])
- or self.slot_concerns_self(args["item"].player)
- )):
- from_player = self.player_names[int(args["data"][0]["text"])]
- to_player = from_player
- for data_id, data in enumerate(args["data"]):
- if data_id != 0 and "type" in data and data["type"] == "player_id":
- to_player = self.player_names[int(data["text"])]
- break
- item_id = int(args["data"][2]["text"])
- item_name = self.item_names.lookup_in_slot(item_id)
- self.set_message(
- {
- "from_player": from_player,
- "to_player": to_player,
- "item_name": item_name,
- "item_id": item_id
- }
- )
-
-
-def main() -> None:
- init_logging("Diddy Kong Racing Client")
- parser = get_base_parser()
- args = sys.argv[1:]
- if "Diddy Kong Racing Client" in args:
- args.remove("Diddy Kong Racing Client")
- args = parser.parse_args(args)
-
- async def _main():
- multiprocessing.freeze_support()
-
- ctx = DiddyKongRacingContext(args.connect, args.password)
- ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop")
-
- if gui_enabled:
- ctx.run_gui()
- ctx.run_cli()
-
- await ctx.exit_event.wait()
- ctx.server_address = None
-
- await ctx.shutdown()
-
- if ctx.n64_sync_task:
- await ctx.n64_sync_task
-
- import colorama
-
- colorama.init()
-
- asyncio.run(_main())
- colorama.deinit()
-
-
-async def n64_sync_task(ctx: DiddyKongRacingContext) -> None:
- logger.info("Starting n64 connector. Use /n64 for status information.")
- while not ctx.exit_event.is_set():
- error_status = None
- if ctx.n64_streams:
- (reader, writer) = ctx.n64_streams
- if ctx.sendSlot:
- msg = get_slot_payload(ctx).encode()
- else:
- msg = get_payload(ctx).encode()
- writer.write(msg)
- writer.write(b'\n')
-
- try:
- await asyncio.wait_for(writer.drain(), timeout=1.5)
- try:
- data = await asyncio.wait_for(reader.readline(), timeout=10)
- data_decoded = json.loads(data.decode())
- get_slot_data = data_decoded.get("getSlot", 0)
- if get_slot_data:
- ctx.sendSlot = True
- else:
- if ctx.game is not None and "locations" in data_decoded:
- # Not just a keep alive ping, parse
- async_start(parse_payload(data_decoded, ctx))
-
- if not ctx.auth:
- ctx.auth = data_decoded["playerName"]
- if ctx.awaiting_rom:
- await ctx.server_auth(False)
- except asyncio.TimeoutError:
- logger.debug("Read Timed Out, Reconnecting")
- error_status = CONNECTION_TIMING_OUT_STATUS
- writer.close()
- ctx.n64_streams = None
- except ConnectionResetError:
- logger.debug("Read failed due to Connection Lost, Reconnecting")
- error_status = CONNECTION_RESET_STATUS
- writer.close()
- ctx.n64_streams = None
- except Exception as e:
- logger.debug(e)
- except asyncio.TimeoutError:
- logger.debug("Connection Timed Out, Reconnecting")
- error_status = CONNECTION_TIMING_OUT_STATUS
- writer.close()
- ctx.n64_streams = None
- except ConnectionResetError:
- logger.debug("Connection Lost, Reconnecting")
- error_status = CONNECTION_RESET_STATUS
- writer.close()
- ctx.n64_streams = None
-
- if ctx.n64_status == CONNECTION_TENTATIVE_STATUS:
- if not error_status:
- logger.info("Successfully Connected to N64")
- ctx.n64_status = CONNECTION_CONNECTED_STATUS
- else:
- ctx.n64_status = f"Was tentatively connected but error occurred: {error_status}"
- elif error_status:
- ctx.n64_status = error_status
- logger.info("Lost connection to N64 and attempting to reconnect. Use /n64 for status updates")
- else:
- try:
- logger.debug("Attempting to connect to N64")
- ctx.n64_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 21221), timeout=10)
- ctx.n64_status = CONNECTION_TENTATIVE_STATUS
- except asyncio.TimeoutError:
- logger.debug("Connection Timed Out, Trying Again")
- ctx.n64_status = CONNECTION_TIMING_OUT_STATUS
- continue
- except ConnectionRefusedError:
- logger.debug("Connection Refused, Trying Again")
- ctx.n64_status = CONNECTION_REFUSED_STATUS
- continue
-
-
-def get_slot_payload(ctx: DiddyKongRacingContext) -> str:
- payload = json.dumps({
- "slot_player": ctx.slot_data["player_name"],
- "slot_seed": ctx.slot_data["seed"],
- "slot_victory_condition": ctx.slot_data["victory_condition"],
- "slot_shuffle_wizpig_amulet": ctx.slot_data["shuffle_wizpig_amulet"],
- "slot_shuffle_tt_amulet": ctx.slot_data["shuffle_tt_amulet"],
- "slot_open_worlds": ctx.slot_data["open_worlds"],
- "slot_door_requirement_progression": ctx.slot_data["door_requirement_progression"],
- "slot_maximum_door_requirement": ctx.slot_data["maximum_door_requirement"],
- "slot_shuffle_door_requirements": ctx.slot_data["shuffle_door_requirements"],
- "slot_door_unlock_requirements": ctx.slot_data["door_unlock_requirements"],
- "slot_shuffle_race_entrances": ctx.slot_data["shuffle_race_entrances"],
- "slot_entrance_order": ctx.slot_data["entrance_order"],
- "slot_boss_1_regional_balloons": ctx.slot_data["boss_1_regional_balloons"],
- "slot_boss_2_regional_balloons": ctx.slot_data["boss_2_regional_balloons"],
- "slot_wizpig_1_amulet_pieces": ctx.slot_data["wizpig_1_amulet_pieces"],
- "slot_wizpig_2_amulet_pieces": ctx.slot_data["wizpig_2_amulet_pieces"],
- "slot_wizpig_2_balloons": ctx.slot_data["wizpig_2_balloons"],
- "slot_randomize_character_on_map_change": ctx.slot_data["randomize_character_on_map_change"],
- "slot_mirrored_tracks": ctx.slot_data["mirrored_tracks"],
- "slot_music": ctx.slot_data["music"],
- "slot_power_up_balloon_type": ctx.slot_data["power_up_balloon_type"],
- "slot_skip_trophy_races": ctx.slot_data["skip_trophy_races"]
- })
- ctx.sendSlot = False
-
- return payload
-
-
-def get_payload(ctx: DiddyKongRacingContext) -> str:
- if ctx.sync_ready:
- ctx.startup = True
- payload = json.dumps({
- "items": [item.item for item in ctx.items_received],
- "playerNames": [name for (i, name) in ctx.player_names.items() if i != 0],
- "messages": [message for (i, message) in ctx.messages.items() if i != 0],
- })
- else:
- ctx.startup = False
- payload = json.dumps({
- "items": [],
- "playerNames": [name for (i, name) in ctx.player_names.items() if i != 0],
- "messages": [message for (i, message) in ctx.messages.items() if i != 0],
- })
-
- if len(ctx.messages) > 0:
- ctx.messages = {}
-
- return payload
-
-
-async def parse_payload(payload: dict, ctx: DiddyKongRacingContext) -> None:
- # Refuse to do anything if ROM is detected as changed
- if ctx.auth and payload["playerName"] != ctx.auth:
- logger.warning("ROM change detected. Disconnecting and reconnecting...")
- ctx.finished_game = False
- ctx.location_table = {}
- ctx.auth = payload["playerName"]
- await ctx.send_connect()
- return
-
- if payload["gameComplete"] == "true" and not ctx.finished_game:
- await ctx.send_msgs([{
- "cmd": "StatusUpdate",
- "status": 30
- }])
- ctx.finished_game = True
-
- # Locations handling
- locations = payload["locations"]
- payload_current_map = payload["currentMap"]
-
- # The Lua JSON library serializes an empty table into a list instead of a dict. Verify types for safety:
- if isinstance(locations, list):
- locations = {}
- if not isinstance(payload_current_map, int):
- payload_current_map = 0
-
- if "DEMO" not in locations and ctx.sync_ready:
- if ctx.location_table != locations:
- updated_locations = []
- for item_group, dkr_location_table in locations.items():
- if len(dkr_location_table) == 0:
- continue
-
- for locationId, value in dkr_location_table.items():
- if value and locationId not in ctx.location_table:
- updated_locations.append(int(locationId))
- if len(updated_locations) > 0:
- await ctx.send_msgs([{
- "cmd": "LocationChecks",
- "locations": updated_locations
- }])
- ctx.location_table = locations
-
- if ctx.current_map != payload_current_map:
- ctx.current_map = payload_current_map
-
- # For PopTracker
- await ctx.send_msgs([{
- "cmd": "Set",
- "key": f"Diddy_Kong_Racing_{ctx.team}_{ctx.slot}_map",
- "default": hex(0),
- "want_reply": False,
- "operations": [{
- "operation": "replace",
- "value": hex(payload_current_map)
- }]
- }])
-
- # For Universal Tracker deferred entrances
- await ctx.send_msgs([{
- "cmd": "Set",
- "key": f"Diddy_Kong_Racing_{ctx.slot}_" + str(payload_current_map),
- "default": False,
- "want_reply": False,
- "operations": [{
- "operation": "replace",
- "value": True
- }]
- }])
-
- # Send Async Data.
- if "sync_ready" in payload:
- if payload["sync_ready"] == "true":
- ctx.sync_ready = True
- else:
- ctx.sync_ready = False
-
-
-if __name__ == "__main__":
- main()
+from __future__ import annotations
+
+import asyncio
+import copy
+import json
+import multiprocessing
+import sys
+
+# CommonClient import first to trigger ModuleUpdater
+from CommonClient import ClientCommandProcessor, CommonContext, get_base_parser, gui_enabled, logger, server_loop
+from Utils import async_start, init_logging
+from worlds import network_data_package
+from .RomPatcher import apply_patch
+
+SYSTEM_MESSAGE_ID = 0
+
+CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_diddy_kong_racing.lua"
+CONNECTION_REFUSED_STATUS = "Connection refused. Please start your emulator and make sure connector_diddy_kong_racing.lua is running"
+CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart connector_diddy_kong_racing.lua"
+CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
+CONNECTION_CONNECTED_STATUS = "Connected"
+CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
+
+"""
+Payload: lua -> client
+{
+ playerName: string,
+ locations: dict,
+ gameComplete: bool
+}
+
+Payload: client -> lua
+{
+ items: list,
+ playerNames: list,
+ messages: string
+}
+
+"""
+
+logger.info(network_data_package["games"].keys())
+dkr_loc_name_to_id: dict[str, int] = network_data_package["games"]["Diddy Kong Racing"]["location_name_to_id"]
+dkr_itm_name_to_id: dict[str, int] = network_data_package["games"]["Diddy Kong Racing"]["item_name_to_id"]
+
+version_number: str = "v1.1.1"
+apworld_version: str = "DKR" + version_number
+patched_rom_filename: str = "Diddy-Kong-Racing-AP-" + version_number + ".z64"
+vanilla_rom_md5: str = "4f0e07f0eeac7e5d7ce3a75461888d03"
+vanilla_swapped_rom_md5: str = "e00c0e6bfb0ce740e3e1c50ba82bc01a"
+patched_rom_md5: str = "f137ca9527a7dbe93f1c30bf205f0e5a"
+
+
+class DiddyKongRacingCommandProcessor(ClientCommandProcessor):
+ def __init__(self, ctx: CommonContext) -> None:
+ super().__init__(ctx)
+
+ def _cmd_n64(self) -> None:
+ """Check N64 Connection State"""
+ if isinstance(self.ctx, DiddyKongRacingContext):
+ logger.info(f"N64 Status: {self.ctx.n64_status}")
+
+
+class DiddyKongRacingContext(CommonContext):
+ command_processor = DiddyKongRacingCommandProcessor
+ items_handling = 0b111 # full
+
+ def __init__(self, server_address: str | None, password: str | None) -> None:
+ super().__init__(server_address, password)
+ self.game = "Diddy Kong Racing"
+ self.n64_streams: (asyncio.StreamReader, asyncio.StreamWriter) = None # type: ignore
+ self.n64_sync_task = None
+ self.n64_status = CONNECTION_INITIAL_STATUS
+ self.awaiting_rom = False
+ self.location_table = {}
+ self.version_warning = False
+ self.messages = {}
+ self.slot_data = {}
+ self.sendSlot = False
+ self.sync_ready = False
+ self.startup = False
+ self.current_map = 0
+
+ async def server_auth(self, password_requested: bool = False) -> None:
+ if password_requested and not self.password:
+ await super(DiddyKongRacingContext, self).server_auth(password_requested)
+
+ if not self.auth:
+ await self.get_username()
+ await self.send_connect()
+ self.awaiting_rom = True
+
+ return
+
+ return
+
+ def set_message(self, msg: dict) -> None:
+ self.messages.update({len(self.messages) + 1: msg})
+
+ def run_gui(self) -> None:
+ from kvui import GameManager
+
+ class DiddyKongRacingManager(GameManager):
+ logging_pairs = [
+ ("Client", "Archipelago")
+ ]
+ base_title = "Archipelago Diddy Kong Racing Client"
+
+ self.ui = DiddyKongRacingManager(self)
+ self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
+ asyncio.create_task(apply_patch(version_number))
+
+ def on_package(self, cmd: str, args: dict) -> None:
+ if cmd == "Connected":
+ self.slot_data = args.get("slot_data")
+ generated_apworld_version = self.slot_data["apworld_version"]
+
+ if apworld_version != generated_apworld_version:
+ error_message = "Your Diddy Kong Racing apworld version (" + apworld_version + ") does not match the generated world's apworld version (" + generated_apworld_version + ")."
+ logger.error(error_message)
+ raise Exception(error_message)
+
+ logger.info("Please open Diddy Kong Racing and load connector_diddy_kong_racing.lua")
+ self.n64_sync_task = asyncio.create_task(n64_sync_task(self), name="N64 Sync")
+ elif cmd == "ReceivedItems":
+ if not self.startup:
+ for item in args["items"]:
+ player = ""
+ item_name = ""
+ for (i, name) in self.player_names.items():
+ if i == item.player:
+ player = name
+ break
+
+ for (name, i) in dkr_itm_name_to_id.items():
+ if item.item == i:
+ item_name = name
+ break
+ logger.info(player + " sent " + item_name)
+ logger.info("The above items will be sent when Diddy Kong Racing is loaded.")
+ self.startup = True
+
+ def on_print_json(self, args: dict) -> None:
+ if self.ui:
+ self.ui.print_json(copy.deepcopy(args["data"]))
+ else:
+ logger.info(self.jsontotextparser(copy.deepcopy(args["data"])))
+
+ if (args.get("type", None) in {"ItemSend"}
+ and (not self.ui
+ or self.slot_concerns_self(args["receiving"])
+ or self.slot_concerns_self(args["item"].player)
+ )):
+ from_player = self.player_names[int(args["data"][0]["text"])]
+ to_player = from_player
+ for data_id, data in enumerate(args["data"]):
+ if data_id != 0 and "type" in data and data["type"] == "player_id":
+ to_player = self.player_names[int(data["text"])]
+ break
+ item_id = int(args["data"][2]["text"])
+ item_name = self.item_names.lookup_in_slot(item_id)
+ self.set_message(
+ {
+ "from_player": from_player,
+ "to_player": to_player,
+ "item_name": item_name,
+ "item_id": item_id
+ }
+ )
+
+
+def main() -> None:
+ init_logging("Diddy Kong Racing Client")
+ parser = get_base_parser()
+ args = sys.argv[1:]
+ if "Diddy Kong Racing Client" in args:
+ args.remove("Diddy Kong Racing Client")
+ args = parser.parse_args(args)
+
+ async def _main():
+ multiprocessing.freeze_support()
+
+ ctx = DiddyKongRacingContext(args.connect, args.password)
+ ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop")
+
+ if gui_enabled:
+ ctx.run_gui()
+ ctx.run_cli()
+
+ await ctx.exit_event.wait()
+ ctx.server_address = None
+
+ await ctx.shutdown()
+
+ if ctx.n64_sync_task:
+ await ctx.n64_sync_task
+
+ import colorama
+
+ colorama.init()
+
+ asyncio.run(_main())
+ colorama.deinit()
+
+
+async def n64_sync_task(ctx: DiddyKongRacingContext) -> None:
+ logger.info("Starting n64 connector. Use /n64 for status information.")
+ while not ctx.exit_event.is_set():
+ error_status = None
+ if ctx.n64_streams:
+ (reader, writer) = ctx.n64_streams
+ if ctx.sendSlot:
+ msg = get_slot_payload(ctx).encode()
+ else:
+ msg = get_payload(ctx).encode()
+ writer.write(msg)
+ writer.write(b'\n')
+
+ try:
+ await asyncio.wait_for(writer.drain(), timeout=1.5)
+ try:
+ data = await asyncio.wait_for(reader.readline(), timeout=10)
+ data_decoded = json.loads(data.decode())
+ get_slot_data = data_decoded.get("getSlot", 0)
+ if get_slot_data:
+ ctx.sendSlot = True
+ else:
+ if ctx.game is not None and "locations" in data_decoded:
+ # Not just a keep alive ping, parse
+ async_start(parse_payload(data_decoded, ctx))
+
+ if not ctx.auth:
+ ctx.auth = data_decoded["playerName"]
+ if ctx.awaiting_rom:
+ await ctx.server_auth(False)
+ except asyncio.TimeoutError:
+ logger.debug("Read Timed Out, Reconnecting")
+ error_status = CONNECTION_TIMING_OUT_STATUS
+ writer.close()
+ ctx.n64_streams = None
+ except ConnectionResetError:
+ logger.debug("Read failed due to Connection Lost, Reconnecting")
+ error_status = CONNECTION_RESET_STATUS
+ writer.close()
+ ctx.n64_streams = None
+ except Exception as e:
+ logger.debug(e)
+ except asyncio.TimeoutError:
+ logger.debug("Connection Timed Out, Reconnecting")
+ error_status = CONNECTION_TIMING_OUT_STATUS
+ writer.close()
+ ctx.n64_streams = None
+ except ConnectionResetError:
+ logger.debug("Connection Lost, Reconnecting")
+ error_status = CONNECTION_RESET_STATUS
+ writer.close()
+ ctx.n64_streams = None
+
+ if ctx.n64_status == CONNECTION_TENTATIVE_STATUS:
+ if not error_status:
+ logger.info("Successfully Connected to N64")
+ ctx.n64_status = CONNECTION_CONNECTED_STATUS
+ else:
+ ctx.n64_status = f"Was tentatively connected but error occurred: {error_status}"
+ elif error_status:
+ ctx.n64_status = error_status
+ logger.info("Lost connection to N64 and attempting to reconnect. Use /n64 for status updates")
+ else:
+ try:
+ logger.debug("Attempting to connect to N64")
+ ctx.n64_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 21221), timeout=10)
+ ctx.n64_status = CONNECTION_TENTATIVE_STATUS
+ except asyncio.TimeoutError:
+ logger.debug("Connection Timed Out, Trying Again")
+ ctx.n64_status = CONNECTION_TIMING_OUT_STATUS
+ continue
+ except ConnectionRefusedError:
+ logger.debug("Connection Refused, Trying Again")
+ ctx.n64_status = CONNECTION_REFUSED_STATUS
+ continue
+
+
+def get_slot_payload(ctx: DiddyKongRacingContext) -> str:
+ payload = json.dumps({
+ "slot_player": ctx.slot_data["player_name"],
+ "slot_seed": ctx.slot_data["seed"],
+ "slot_victory_condition": ctx.slot_data["victory_condition"],
+ "slot_shuffle_wizpig_amulet": ctx.slot_data["shuffle_wizpig_amulet"],
+ "slot_shuffle_tt_amulet": ctx.slot_data["shuffle_tt_amulet"],
+ "slot_open_worlds": ctx.slot_data["open_worlds"],
+ "slot_door_requirement_progression": ctx.slot_data["door_requirement_progression"],
+ "slot_maximum_door_requirement": ctx.slot_data["maximum_door_requirement"],
+ "slot_shuffle_door_requirements": ctx.slot_data["shuffle_door_requirements"],
+ "slot_door_unlock_requirements": ctx.slot_data["door_unlock_requirements"],
+ "slot_shuffle_race_entrances": ctx.slot_data["shuffle_race_entrances"],
+ "slot_entrance_order": ctx.slot_data["entrance_order"],
+ "slot_boss_1_regional_balloons": ctx.slot_data["boss_1_regional_balloons"],
+ "slot_boss_2_regional_balloons": ctx.slot_data["boss_2_regional_balloons"],
+ "slot_wizpig_1_amulet_pieces": ctx.slot_data["wizpig_1_amulet_pieces"],
+ "slot_wizpig_2_amulet_pieces": ctx.slot_data["wizpig_2_amulet_pieces"],
+ "slot_wizpig_2_balloons": ctx.slot_data["wizpig_2_balloons"],
+ "slot_randomize_character_on_map_change": ctx.slot_data["randomize_character_on_map_change"],
+ "slot_track_versions": ctx.slot_data["track_versions"],
+ "slot_music": ctx.slot_data["music"],
+ "slot_power_up_balloon_type": ctx.slot_data["power_up_balloon_type"],
+ "slot_skip_trophy_races": ctx.slot_data["skip_trophy_races"]
+ })
+ ctx.sendSlot = False
+
+ return payload
+
+
+def get_payload(ctx: DiddyKongRacingContext) -> str:
+ if ctx.sync_ready:
+ ctx.startup = True
+ payload = json.dumps({
+ "items": [item.item for item in ctx.items_received],
+ "playerNames": [name for (i, name) in ctx.player_names.items() if i != 0],
+ "messages": [message for (i, message) in ctx.messages.items() if i != 0],
+ })
+ else:
+ ctx.startup = False
+ payload = json.dumps({
+ "items": [],
+ "playerNames": [name for (i, name) in ctx.player_names.items() if i != 0],
+ "messages": [message for (i, message) in ctx.messages.items() if i != 0],
+ })
+
+ if len(ctx.messages) > 0:
+ ctx.messages = {}
+
+ return payload
+
+
+async def parse_payload(payload: dict, ctx: DiddyKongRacingContext) -> None:
+ # Refuse to do anything if ROM is detected as changed
+ if ctx.auth and payload["playerName"] != ctx.auth:
+ logger.warning("ROM change detected. Disconnecting and reconnecting...")
+ ctx.finished_game = False
+ ctx.location_table = {}
+ ctx.auth = payload["playerName"]
+ await ctx.send_connect()
+ return
+
+ if payload["gameComplete"] == "true" and not ctx.finished_game:
+ await ctx.send_msgs([{
+ "cmd": "StatusUpdate",
+ "status": 30
+ }])
+ ctx.finished_game = True
+
+ # Locations handling
+ locations = payload["locations"]
+ payload_current_map = payload["currentMap"]
+
+ # The Lua JSON library serializes an empty table into a list instead of a dict. Verify types for safety:
+ if isinstance(locations, list):
+ locations = {}
+ if not isinstance(payload_current_map, int):
+ payload_current_map = 0
+
+ if "DEMO" not in locations and ctx.sync_ready:
+ if ctx.location_table != locations:
+ updated_locations = []
+ for item_group, dkr_location_table in locations.items():
+ if len(dkr_location_table) == 0:
+ continue
+
+ for locationId, value in dkr_location_table.items():
+ if value and locationId not in ctx.location_table:
+ updated_locations.append(int(locationId))
+ if len(updated_locations) > 0:
+ await ctx.send_msgs([{
+ "cmd": "LocationChecks",
+ "locations": updated_locations
+ }])
+ ctx.location_table = locations
+
+ if ctx.current_map != payload_current_map:
+ ctx.current_map = payload_current_map
+
+ # For PopTracker
+ await ctx.send_msgs([{
+ "cmd": "Set",
+ "key": f"Diddy_Kong_Racing_{ctx.team}_{ctx.slot}_map",
+ "default": hex(0),
+ "want_reply": False,
+ "operations": [{
+ "operation": "replace",
+ "value": hex(payload_current_map)
+ }]
+ }])
+
+ # For Universal Tracker deferred entrances
+ await ctx.send_msgs([{
+ "cmd": "Set",
+ "key": f"Diddy_Kong_Racing_{ctx.slot}_" + str(payload_current_map),
+ "default": False,
+ "want_reply": False,
+ "operations": [{
+ "operation": "replace",
+ "value": True
+ }]
+ }])
+
+ # Send Async Data.
+ if "sync_ready" in payload:
+ if payload["sync_ready"] == "true":
+ ctx.sync_ready = True
+ else:
+ ctx.sync_ready = False
+
+
+if __name__ == "__main__":
+ main()
diff --git a/worlds/diddy_kong_racing/Diddy_Kong_Racing.patch b/worlds/diddy_kong_racing/Diddy_Kong_Racing.patch
index 12c4d56f8e75..5c94e51b9d6f 100644
Binary files a/worlds/diddy_kong_racing/Diddy_Kong_Racing.patch and b/worlds/diddy_kong_racing/Diddy_Kong_Racing.patch differ
diff --git a/worlds/diddy_kong_racing/DoorUnlocks.py b/worlds/diddy_kong_racing/DoorUnlocks.py
index 145ee1fe2300..8be93de16cc1 100644
--- a/worlds/diddy_kong_racing/DoorUnlocks.py
+++ b/worlds/diddy_kong_racing/DoorUnlocks.py
@@ -1,241 +1,241 @@
-from __future__ import annotations
-
-import math
-import re
-from typing import TYPE_CHECKING
-
-from BaseClasses import Location
-from .Names import ItemName, LocationName
-
-if TYPE_CHECKING:
- from . import DiddyKongRacingWorld
-else:
- DiddyKongRacingWorld = object
-
-
-class DoorUnlockInfo:
- def __init__(self, item: str, location: str, requirement: int):
- self.item = item
- self.location = location
- self.requirement = requirement
-
-
-vanilla_door_unlock_info_list: list[DoorUnlockInfo] = [
- DoorUnlockInfo(ItemName.DINO_DOMAIN_UNLOCK, LocationName.WORLD_1_UNLOCK, 1),
- DoorUnlockInfo(ItemName.SNOWFLAKE_MOUNTAIN_UNLOCK, LocationName.WORLD_2_UNLOCK, 2),
- DoorUnlockInfo(ItemName.SHERBET_ISLAND_UNLOCK, LocationName.WORLD_3_UNLOCK, 10),
- DoorUnlockInfo(ItemName.DRAGON_FOREST_UNLOCK, LocationName.WORLD_4_UNLOCK, 16),
- DoorUnlockInfo(ItemName.ANCIENT_LAKE_DOOR_1_UNLOCK, LocationName.RACE_1_1_UNLOCK, 1),
- DoorUnlockInfo(ItemName.ANCIENT_LAKE_DOOR_2_UNLOCK, LocationName.RACE_1_2_UNLOCK, 6),
- DoorUnlockInfo(ItemName.FOSSIL_CANYON_DOOR_1_UNLOCK, LocationName.RACE_2_1_UNLOCK, 2),
- DoorUnlockInfo(ItemName.FOSSIL_CANYON_DOOR_2_UNLOCK, LocationName.RACE_2_2_UNLOCK, 7),
- DoorUnlockInfo(ItemName.JUNGLE_FALLS_DOOR_1_UNLOCK, LocationName.RACE_3_1_UNLOCK, 3),
- DoorUnlockInfo(ItemName.JUNGLE_FALLS_DOOR_2_UNLOCK, LocationName.RACE_3_2_UNLOCK, 8),
- DoorUnlockInfo(ItemName.HOT_TOP_VOLCANO_DOOR_1_UNLOCK, LocationName.RACE_4_1_UNLOCK, 5),
- DoorUnlockInfo(ItemName.HOT_TOP_VOLCANO_DOOR_2_UNLOCK, LocationName.RACE_4_2_UNLOCK, 10),
- DoorUnlockInfo(ItemName.EVERFROST_PEAK_DOOR_1_UNLOCK, LocationName.RACE_5_1_UNLOCK, 2),
- DoorUnlockInfo(ItemName.EVERFROST_PEAK_DOOR_2_UNLOCK, LocationName.RACE_5_2_UNLOCK, 10),
- DoorUnlockInfo(ItemName.WALRUS_COVE_DOOR_1_UNLOCK, LocationName.RACE_6_1_UNLOCK, 3),
- DoorUnlockInfo(ItemName.WALRUS_COVE_DOOR_2_UNLOCK, LocationName.RACE_6_2_UNLOCK, 11),
- DoorUnlockInfo(ItemName.SNOWBALL_VALLEY_DOOR_1_UNLOCK, LocationName.RACE_7_1_UNLOCK, 6),
- DoorUnlockInfo(ItemName.SNOWBALL_VALLEY_DOOR_2_UNLOCK, LocationName.RACE_7_2_UNLOCK, 14),
- DoorUnlockInfo(ItemName.FROSTY_VILLAGE_DOOR_1_UNLOCK, LocationName.RACE_8_1_UNLOCK, 9),
- DoorUnlockInfo(ItemName.FROSTY_VILLAGE_DOOR_2_UNLOCK, LocationName.RACE_8_2_UNLOCK, 16),
- DoorUnlockInfo(ItemName.WHALE_BAY_DOOR_1_UNLOCK, LocationName.RACE_9_1_UNLOCK, 10),
- DoorUnlockInfo(ItemName.WHALE_BAY_DOOR_2_UNLOCK, LocationName.RACE_9_2_UNLOCK, 17),
- DoorUnlockInfo(ItemName.CRESCENT_ISLAND_DOOR_1_UNLOCK, LocationName.RACE_10_1_UNLOCK, 11),
- DoorUnlockInfo(ItemName.CRESCENT_ISLAND_DOOR_2_UNLOCK, LocationName.RACE_10_2_UNLOCK, 18),
- DoorUnlockInfo(ItemName.PIRATE_LAGOON_DOOR_1_UNLOCK, LocationName.RACE_11_1_UNLOCK, 13),
- DoorUnlockInfo(ItemName.PIRATE_LAGOON_DOOR_2_UNLOCK, LocationName.RACE_11_2_UNLOCK, 20),
- DoorUnlockInfo(ItemName.TREASURE_CAVES_DOOR_1_UNLOCK, LocationName.RACE_12_1_UNLOCK, 16),
- DoorUnlockInfo(ItemName.TREASURE_CAVES_DOOR_2_UNLOCK, LocationName.RACE_12_2_UNLOCK, 22),
- DoorUnlockInfo(ItemName.WINDMILL_PLAINS_DOOR_1_UNLOCK, LocationName.RACE_13_1_UNLOCK, 16),
- DoorUnlockInfo(ItemName.WINDMILL_PLAINS_DOOR_2_UNLOCK, LocationName.RACE_13_2_UNLOCK, 23),
- DoorUnlockInfo(ItemName.GREENWOOD_VILLAGE_DOOR_1_UNLOCK, LocationName.RACE_14_1_UNLOCK, 17),
- DoorUnlockInfo(ItemName.GREENWOOD_VILLAGE_DOOR_2_UNLOCK, LocationName.RACE_14_2_UNLOCK, 24),
- DoorUnlockInfo(ItemName.BOULDER_CANYON_DOOR_1_UNLOCK, LocationName.RACE_15_1_UNLOCK, 20),
- DoorUnlockInfo(ItemName.BOULDER_CANYON_DOOR_2_UNLOCK, LocationName.RACE_15_2_UNLOCK, 30),
- DoorUnlockInfo(ItemName.HAUNTED_WOODS_DOOR_1_UNLOCK, LocationName.RACE_16_1_UNLOCK, 22),
- DoorUnlockInfo(ItemName.HAUNTED_WOODS_DOOR_2_UNLOCK, LocationName.RACE_16_2_UNLOCK, 37),
- DoorUnlockInfo(ItemName.SPACEDUST_ALLEY_DOOR_1_UNLOCK, LocationName.RACE_17_1_UNLOCK, 39),
- DoorUnlockInfo(ItemName.SPACEDUST_ALLEY_DOOR_2_UNLOCK, LocationName.RACE_17_2_UNLOCK, 43),
- DoorUnlockInfo(ItemName.DARKMOON_CAVERNS_DOOR_1_UNLOCK, LocationName.RACE_18_1_UNLOCK, 40),
- DoorUnlockInfo(ItemName.DARKMOON_CAVERNS_DOOR_2_UNLOCK, LocationName.RACE_18_2_UNLOCK, 44),
- DoorUnlockInfo(ItemName.SPACEPORT_ALPHA_DOOR_1_UNLOCK, LocationName.RACE_19_1_UNLOCK, 41),
- DoorUnlockInfo(ItemName.SPACEPORT_ALPHA_DOOR_2_UNLOCK, LocationName.RACE_19_2_UNLOCK, 45),
- DoorUnlockInfo(ItemName.STAR_CITY_DOOR_1_UNLOCK, LocationName.RACE_20_1_UNLOCK, 42),
- DoorUnlockInfo(ItemName.STAR_CITY_DOOR_2_UNLOCK, LocationName.RACE_20_2_UNLOCK, 46)
-]
-
-vanilla_door_unlock_info_sorted_by_requirement: list[DoorUnlockInfo] = sorted(vanilla_door_unlock_info_list,
- key=lambda x: x.requirement)
-cached_door_requirement_progression: list[int] | None = None
-
-DOOR_UNLOCK_ITEM_PATTERN = re.compile("(\\d+) balloon\\(s\\) \\(.* Unlock\\)")
-
-
-def get_door_requirement_progression(world: DiddyKongRacingWorld) -> list[int]:
- global cached_door_requirement_progression
- if cached_door_requirement_progression:
- return cached_door_requirement_progression
-
- if world.options.door_requirement_progression == 0: # Vanilla
- door_requirement_progression = [x.requirement for x in vanilla_door_unlock_info_sorted_by_requirement]
- elif world.options.door_requirement_progression == 1: # Linear
- door_unlock_requirement_interval = ((world.options.maximum_door_requirement - 1)
- / (len(vanilla_door_unlock_info_list) - 1))
- door_requirement_progression = []
- door_unlock_requirement = 1
- for _ in range(len(vanilla_door_unlock_info_list)):
- door_requirement_progression.append(math.floor(door_unlock_requirement))
- door_unlock_requirement += door_unlock_requirement_interval
- else: # Exponential
- door_requirement_progression = []
- ratio = world.options.maximum_door_requirement / 46
- for i in range(len(vanilla_door_unlock_info_list) - 1):
- door_requirement_progression.append(max(1, math.floor(ratio * 3.31 * math.exp(0.0628 * i) - 2)))
-
- door_requirement_progression.append(int(world.options.maximum_door_requirement))
-
- cached_door_requirement_progression = door_requirement_progression
- return door_requirement_progression
-
-
-def shuffle_door_unlock_items(world: DiddyKongRacingWorld) -> None:
- race_1_unlock_to_race_2_unlock = {
- ItemName.ANCIENT_LAKE_DOOR_1_UNLOCK: ItemName.ANCIENT_LAKE_DOOR_2_UNLOCK,
- ItemName.FOSSIL_CANYON_DOOR_1_UNLOCK: ItemName.FOSSIL_CANYON_DOOR_2_UNLOCK,
- ItemName.JUNGLE_FALLS_DOOR_1_UNLOCK: ItemName.JUNGLE_FALLS_DOOR_2_UNLOCK,
- ItemName.HOT_TOP_VOLCANO_DOOR_1_UNLOCK: ItemName.HOT_TOP_VOLCANO_DOOR_2_UNLOCK,
- ItemName.EVERFROST_PEAK_DOOR_1_UNLOCK: ItemName.EVERFROST_PEAK_DOOR_2_UNLOCK,
- ItemName.WALRUS_COVE_DOOR_1_UNLOCK: ItemName.WALRUS_COVE_DOOR_2_UNLOCK,
- ItemName.SNOWBALL_VALLEY_DOOR_1_UNLOCK: ItemName.SNOWBALL_VALLEY_DOOR_2_UNLOCK,
- ItemName.FROSTY_VILLAGE_DOOR_1_UNLOCK: ItemName.FROSTY_VILLAGE_DOOR_2_UNLOCK,
- ItemName.WHALE_BAY_DOOR_1_UNLOCK: ItemName.WHALE_BAY_DOOR_2_UNLOCK,
- ItemName.CRESCENT_ISLAND_DOOR_1_UNLOCK: ItemName.CRESCENT_ISLAND_DOOR_2_UNLOCK,
- ItemName.PIRATE_LAGOON_DOOR_1_UNLOCK: ItemName.PIRATE_LAGOON_DOOR_2_UNLOCK,
- ItemName.TREASURE_CAVES_DOOR_1_UNLOCK: ItemName.TREASURE_CAVES_DOOR_2_UNLOCK,
- ItemName.WINDMILL_PLAINS_DOOR_1_UNLOCK: ItemName.WINDMILL_PLAINS_DOOR_2_UNLOCK,
- ItemName.GREENWOOD_VILLAGE_DOOR_1_UNLOCK: ItemName.GREENWOOD_VILLAGE_DOOR_2_UNLOCK,
- ItemName.BOULDER_CANYON_DOOR_1_UNLOCK: ItemName.BOULDER_CANYON_DOOR_2_UNLOCK,
- ItemName.HAUNTED_WOODS_DOOR_1_UNLOCK: ItemName.HAUNTED_WOODS_DOOR_2_UNLOCK,
- ItemName.SPACEDUST_ALLEY_DOOR_1_UNLOCK: ItemName.SPACEDUST_ALLEY_DOOR_2_UNLOCK,
- ItemName.DARKMOON_CAVERNS_DOOR_1_UNLOCK: ItemName.DARKMOON_CAVERNS_DOOR_2_UNLOCK,
- ItemName.SPACEPORT_ALPHA_DOOR_1_UNLOCK: ItemName.SPACEPORT_ALPHA_DOOR_2_UNLOCK,
- ItemName.STAR_CITY_DOOR_1_UNLOCK: ItemName.STAR_CITY_DOOR_2_UNLOCK
- }
-
- dino_domain_race_1_unlocks = (
- ItemName.ANCIENT_LAKE_DOOR_1_UNLOCK,
- ItemName.FOSSIL_CANYON_DOOR_1_UNLOCK,
- ItemName.JUNGLE_FALLS_DOOR_1_UNLOCK,
- ItemName.HOT_TOP_VOLCANO_DOOR_1_UNLOCK
- )
- snowflake_mountain_race_1_unlocks = (
- ItemName.EVERFROST_PEAK_DOOR_1_UNLOCK,
- ItemName.WALRUS_COVE_DOOR_1_UNLOCK,
- ItemName.SNOWBALL_VALLEY_DOOR_1_UNLOCK,
- ItemName.FROSTY_VILLAGE_DOOR_1_UNLOCK
- )
- sherbet_island_race_1_unlocks = (
- ItemName.WHALE_BAY_DOOR_1_UNLOCK,
- ItemName.CRESCENT_ISLAND_DOOR_1_UNLOCK,
- ItemName.PIRATE_LAGOON_DOOR_1_UNLOCK,
- ItemName.TREASURE_CAVES_DOOR_1_UNLOCK
- )
- dragon_forest_race_1_unlocks = (
- ItemName.WINDMILL_PLAINS_DOOR_1_UNLOCK,
- ItemName.GREENWOOD_VILLAGE_DOOR_1_UNLOCK,
- ItemName.BOULDER_CANYON_DOOR_1_UNLOCK,
- ItemName.HAUNTED_WOODS_DOOR_1_UNLOCK
- )
- future_fun_land_race_1_unlocks = (
- ItemName.SPACEDUST_ALLEY_DOOR_1_UNLOCK,
- ItemName.DARKMOON_CAVERNS_DOOR_1_UNLOCK,
- ItemName.SPACEPORT_ALPHA_DOOR_1_UNLOCK,
- ItemName.STAR_CITY_DOOR_1_UNLOCK
- )
-
- if world.options.open_worlds:
- available_doors = [
- *dino_domain_race_1_unlocks,
- *snowflake_mountain_race_1_unlocks,
- *sherbet_island_race_1_unlocks,
- *dragon_forest_race_1_unlocks,
- *future_fun_land_race_1_unlocks
- ]
- else:
- available_doors = [
- ItemName.DINO_DOMAIN_UNLOCK,
- ItemName.SNOWFLAKE_MOUNTAIN_UNLOCK,
- ItemName.SHERBET_ISLAND_UNLOCK,
- ItemName.DRAGON_FOREST_UNLOCK
- ]
-
- door_unlock_item_to_index = {door_unlock_info.item: index for index, door_unlock_info in
- enumerate(vanilla_door_unlock_info_list)}
-
- race_2_unlock_count = 0
-
- for door_unlock_info, requirement in zip(vanilla_door_unlock_info_sorted_by_requirement,
- get_door_requirement_progression(world)):
- if not (world.options.open_worlds and door_unlock_info.location in LocationName.WORLD_UNLOCK_LOCATIONS):
- world.random.shuffle(available_doors)
- door_unlock_item = available_doors.pop()
- door_unlock_item_index = door_unlock_item_to_index[door_unlock_item]
- world.door_unlock_requirements[door_unlock_item_index] = requirement
-
- if door_unlock_item == ItemName.DINO_DOMAIN_UNLOCK:
- available_doors.extend(dino_domain_race_1_unlocks)
- elif door_unlock_item == ItemName.SNOWFLAKE_MOUNTAIN_UNLOCK:
- available_doors.extend(snowflake_mountain_race_1_unlocks)
- elif door_unlock_item == ItemName.SHERBET_ISLAND_UNLOCK:
- available_doors.extend(sherbet_island_race_1_unlocks)
- elif door_unlock_item == ItemName.DRAGON_FOREST_UNLOCK:
- available_doors.extend(dragon_forest_race_1_unlocks)
- elif door_unlock_item in race_1_unlock_to_race_2_unlock:
- available_doors.append(race_1_unlock_to_race_2_unlock[door_unlock_item])
- elif not world.options.open_worlds:
- race_2_unlock_count += 1
- if race_2_unlock_count == 16:
- available_doors.extend(future_fun_land_race_1_unlocks)
-
-
-def place_vanilla_door_unlock_items(world: DiddyKongRacingWorld) -> None:
- for door_num, door_unlock_info in enumerate(vanilla_door_unlock_info_list):
- if not (world.options.open_worlds and door_unlock_info.location in LocationName.WORLD_UNLOCK_LOCATIONS):
- world.door_unlock_requirements[door_num] = door_unlock_info.requirement
-
-
-def place_door_unlock_items(world: DiddyKongRacingWorld) -> None:
- filled_door_unlock_locations = set()
- if world.options.open_worlds:
- filled_door_unlock_locations.update(LocationName.WORLD_UNLOCK_LOCATIONS)
-
- origin_region = world.get_region(world.origin_region_name)
-
- for item_door_unlock_info, item_door_unlock_requirement in zip(vanilla_door_unlock_info_list,
- world.door_unlock_requirements):
- if not (world.options.open_worlds and item_door_unlock_info.location in LocationName.WORLD_UNLOCK_LOCATIONS):
- for location_door_unlock_info, location_door_unlock_requirement in \
- zip(vanilla_door_unlock_info_sorted_by_requirement, get_door_requirement_progression(world)):
- base_location_name = location_door_unlock_info.location
- if item_door_unlock_requirement == location_door_unlock_requirement and base_location_name not in filled_door_unlock_locations:
- location_name = build_door_unlock_location_name(location_door_unlock_info.location,
- item_door_unlock_requirement)
- origin_region.add_locations({location_name: None})
- world.place_locked_item(location_name, world.create_event_item(item_door_unlock_info.item))
- filled_door_unlock_locations.add(base_location_name)
- break
-
-
-def build_door_unlock_location_name(door_unlock_location_base_name: str, door_unlock_requirement: int) -> str:
- return str(door_unlock_requirement) + " balloon(s) (" + door_unlock_location_base_name + ")"
-
-
-def is_door_unlock_location(location: Location):
- return DOOR_UNLOCK_ITEM_PATTERN.match(location.name)
-
-
-def get_door_unlock_requirement(location: Location):
- return int(DOOR_UNLOCK_ITEM_PATTERN.match(location.name).group(1))
+from __future__ import annotations
+
+import math
+import re
+from typing import TYPE_CHECKING
+
+from BaseClasses import Location
+from .Names import ItemName, LocationName
+
+if TYPE_CHECKING:
+ from . import DiddyKongRacingWorld
+else:
+ DiddyKongRacingWorld = object
+
+
+class DoorUnlockInfo:
+ def __init__(self, item: str, location: str, requirement: int):
+ self.item = item
+ self.location = location
+ self.requirement = requirement
+
+
+vanilla_door_unlock_info_list: list[DoorUnlockInfo] = [
+ DoorUnlockInfo(ItemName.DINO_DOMAIN_UNLOCK, LocationName.WORLD_1_UNLOCK, 1),
+ DoorUnlockInfo(ItemName.SNOWFLAKE_MOUNTAIN_UNLOCK, LocationName.WORLD_2_UNLOCK, 2),
+ DoorUnlockInfo(ItemName.SHERBET_ISLAND_UNLOCK, LocationName.WORLD_3_UNLOCK, 10),
+ DoorUnlockInfo(ItemName.DRAGON_FOREST_UNLOCK, LocationName.WORLD_4_UNLOCK, 16),
+ DoorUnlockInfo(ItemName.ANCIENT_LAKE_DOOR_1_UNLOCK, LocationName.RACE_1_1_UNLOCK, 1),
+ DoorUnlockInfo(ItemName.ANCIENT_LAKE_DOOR_2_UNLOCK, LocationName.RACE_1_2_UNLOCK, 6),
+ DoorUnlockInfo(ItemName.FOSSIL_CANYON_DOOR_1_UNLOCK, LocationName.RACE_2_1_UNLOCK, 2),
+ DoorUnlockInfo(ItemName.FOSSIL_CANYON_DOOR_2_UNLOCK, LocationName.RACE_2_2_UNLOCK, 7),
+ DoorUnlockInfo(ItemName.JUNGLE_FALLS_DOOR_1_UNLOCK, LocationName.RACE_3_1_UNLOCK, 3),
+ DoorUnlockInfo(ItemName.JUNGLE_FALLS_DOOR_2_UNLOCK, LocationName.RACE_3_2_UNLOCK, 8),
+ DoorUnlockInfo(ItemName.HOT_TOP_VOLCANO_DOOR_1_UNLOCK, LocationName.RACE_4_1_UNLOCK, 5),
+ DoorUnlockInfo(ItemName.HOT_TOP_VOLCANO_DOOR_2_UNLOCK, LocationName.RACE_4_2_UNLOCK, 10),
+ DoorUnlockInfo(ItemName.EVERFROST_PEAK_DOOR_1_UNLOCK, LocationName.RACE_5_1_UNLOCK, 2),
+ DoorUnlockInfo(ItemName.EVERFROST_PEAK_DOOR_2_UNLOCK, LocationName.RACE_5_2_UNLOCK, 10),
+ DoorUnlockInfo(ItemName.WALRUS_COVE_DOOR_1_UNLOCK, LocationName.RACE_6_1_UNLOCK, 3),
+ DoorUnlockInfo(ItemName.WALRUS_COVE_DOOR_2_UNLOCK, LocationName.RACE_6_2_UNLOCK, 11),
+ DoorUnlockInfo(ItemName.SNOWBALL_VALLEY_DOOR_1_UNLOCK, LocationName.RACE_7_1_UNLOCK, 6),
+ DoorUnlockInfo(ItemName.SNOWBALL_VALLEY_DOOR_2_UNLOCK, LocationName.RACE_7_2_UNLOCK, 14),
+ DoorUnlockInfo(ItemName.FROSTY_VILLAGE_DOOR_1_UNLOCK, LocationName.RACE_8_1_UNLOCK, 9),
+ DoorUnlockInfo(ItemName.FROSTY_VILLAGE_DOOR_2_UNLOCK, LocationName.RACE_8_2_UNLOCK, 16),
+ DoorUnlockInfo(ItemName.WHALE_BAY_DOOR_1_UNLOCK, LocationName.RACE_9_1_UNLOCK, 10),
+ DoorUnlockInfo(ItemName.WHALE_BAY_DOOR_2_UNLOCK, LocationName.RACE_9_2_UNLOCK, 17),
+ DoorUnlockInfo(ItemName.CRESCENT_ISLAND_DOOR_1_UNLOCK, LocationName.RACE_10_1_UNLOCK, 11),
+ DoorUnlockInfo(ItemName.CRESCENT_ISLAND_DOOR_2_UNLOCK, LocationName.RACE_10_2_UNLOCK, 18),
+ DoorUnlockInfo(ItemName.PIRATE_LAGOON_DOOR_1_UNLOCK, LocationName.RACE_11_1_UNLOCK, 13),
+ DoorUnlockInfo(ItemName.PIRATE_LAGOON_DOOR_2_UNLOCK, LocationName.RACE_11_2_UNLOCK, 20),
+ DoorUnlockInfo(ItemName.TREASURE_CAVES_DOOR_1_UNLOCK, LocationName.RACE_12_1_UNLOCK, 16),
+ DoorUnlockInfo(ItemName.TREASURE_CAVES_DOOR_2_UNLOCK, LocationName.RACE_12_2_UNLOCK, 22),
+ DoorUnlockInfo(ItemName.WINDMILL_PLAINS_DOOR_1_UNLOCK, LocationName.RACE_13_1_UNLOCK, 16),
+ DoorUnlockInfo(ItemName.WINDMILL_PLAINS_DOOR_2_UNLOCK, LocationName.RACE_13_2_UNLOCK, 23),
+ DoorUnlockInfo(ItemName.GREENWOOD_VILLAGE_DOOR_1_UNLOCK, LocationName.RACE_14_1_UNLOCK, 17),
+ DoorUnlockInfo(ItemName.GREENWOOD_VILLAGE_DOOR_2_UNLOCK, LocationName.RACE_14_2_UNLOCK, 24),
+ DoorUnlockInfo(ItemName.BOULDER_CANYON_DOOR_1_UNLOCK, LocationName.RACE_15_1_UNLOCK, 20),
+ DoorUnlockInfo(ItemName.BOULDER_CANYON_DOOR_2_UNLOCK, LocationName.RACE_15_2_UNLOCK, 30),
+ DoorUnlockInfo(ItemName.HAUNTED_WOODS_DOOR_1_UNLOCK, LocationName.RACE_16_1_UNLOCK, 22),
+ DoorUnlockInfo(ItemName.HAUNTED_WOODS_DOOR_2_UNLOCK, LocationName.RACE_16_2_UNLOCK, 37),
+ DoorUnlockInfo(ItemName.SPACEDUST_ALLEY_DOOR_1_UNLOCK, LocationName.RACE_17_1_UNLOCK, 39),
+ DoorUnlockInfo(ItemName.SPACEDUST_ALLEY_DOOR_2_UNLOCK, LocationName.RACE_17_2_UNLOCK, 43),
+ DoorUnlockInfo(ItemName.DARKMOON_CAVERNS_DOOR_1_UNLOCK, LocationName.RACE_18_1_UNLOCK, 40),
+ DoorUnlockInfo(ItemName.DARKMOON_CAVERNS_DOOR_2_UNLOCK, LocationName.RACE_18_2_UNLOCK, 44),
+ DoorUnlockInfo(ItemName.SPACEPORT_ALPHA_DOOR_1_UNLOCK, LocationName.RACE_19_1_UNLOCK, 41),
+ DoorUnlockInfo(ItemName.SPACEPORT_ALPHA_DOOR_2_UNLOCK, LocationName.RACE_19_2_UNLOCK, 45),
+ DoorUnlockInfo(ItemName.STAR_CITY_DOOR_1_UNLOCK, LocationName.RACE_20_1_UNLOCK, 42),
+ DoorUnlockInfo(ItemName.STAR_CITY_DOOR_2_UNLOCK, LocationName.RACE_20_2_UNLOCK, 46)
+]
+
+vanilla_door_unlock_info_sorted_by_requirement: list[DoorUnlockInfo] = sorted(vanilla_door_unlock_info_list,
+ key=lambda x: x.requirement)
+cached_door_requirement_progression: list[int] | None = None
+
+DOOR_UNLOCK_ITEM_PATTERN = re.compile("(\\d+) balloon\\(s\\) \\(.* Unlock\\)")
+
+
+def get_door_requirement_progression(world: DiddyKongRacingWorld) -> list[int]:
+ global cached_door_requirement_progression
+ if cached_door_requirement_progression:
+ return cached_door_requirement_progression
+
+ if world.options.door_requirement_progression == 0: # Vanilla
+ door_requirement_progression = [x.requirement for x in vanilla_door_unlock_info_sorted_by_requirement]
+ elif world.options.door_requirement_progression == 1: # Linear
+ door_unlock_requirement_interval = ((world.options.maximum_door_requirement - 1)
+ / (len(vanilla_door_unlock_info_list) - 1))
+ door_requirement_progression = []
+ door_unlock_requirement = 1
+ for _ in range(len(vanilla_door_unlock_info_list)):
+ door_requirement_progression.append(math.floor(door_unlock_requirement))
+ door_unlock_requirement += door_unlock_requirement_interval
+ else: # Exponential
+ door_requirement_progression = []
+ ratio = world.options.maximum_door_requirement / 46
+ for i in range(len(vanilla_door_unlock_info_list) - 1):
+ door_requirement_progression.append(max(1, math.floor(ratio * 3.31 * math.exp(0.0628 * i) - 2)))
+
+ door_requirement_progression.append(int(world.options.maximum_door_requirement))
+
+ cached_door_requirement_progression = door_requirement_progression
+ return door_requirement_progression
+
+
+def shuffle_door_unlock_items(world: DiddyKongRacingWorld) -> None:
+ race_1_unlock_to_race_2_unlock = {
+ ItemName.ANCIENT_LAKE_DOOR_1_UNLOCK: ItemName.ANCIENT_LAKE_DOOR_2_UNLOCK,
+ ItemName.FOSSIL_CANYON_DOOR_1_UNLOCK: ItemName.FOSSIL_CANYON_DOOR_2_UNLOCK,
+ ItemName.JUNGLE_FALLS_DOOR_1_UNLOCK: ItemName.JUNGLE_FALLS_DOOR_2_UNLOCK,
+ ItemName.HOT_TOP_VOLCANO_DOOR_1_UNLOCK: ItemName.HOT_TOP_VOLCANO_DOOR_2_UNLOCK,
+ ItemName.EVERFROST_PEAK_DOOR_1_UNLOCK: ItemName.EVERFROST_PEAK_DOOR_2_UNLOCK,
+ ItemName.WALRUS_COVE_DOOR_1_UNLOCK: ItemName.WALRUS_COVE_DOOR_2_UNLOCK,
+ ItemName.SNOWBALL_VALLEY_DOOR_1_UNLOCK: ItemName.SNOWBALL_VALLEY_DOOR_2_UNLOCK,
+ ItemName.FROSTY_VILLAGE_DOOR_1_UNLOCK: ItemName.FROSTY_VILLAGE_DOOR_2_UNLOCK,
+ ItemName.WHALE_BAY_DOOR_1_UNLOCK: ItemName.WHALE_BAY_DOOR_2_UNLOCK,
+ ItemName.CRESCENT_ISLAND_DOOR_1_UNLOCK: ItemName.CRESCENT_ISLAND_DOOR_2_UNLOCK,
+ ItemName.PIRATE_LAGOON_DOOR_1_UNLOCK: ItemName.PIRATE_LAGOON_DOOR_2_UNLOCK,
+ ItemName.TREASURE_CAVES_DOOR_1_UNLOCK: ItemName.TREASURE_CAVES_DOOR_2_UNLOCK,
+ ItemName.WINDMILL_PLAINS_DOOR_1_UNLOCK: ItemName.WINDMILL_PLAINS_DOOR_2_UNLOCK,
+ ItemName.GREENWOOD_VILLAGE_DOOR_1_UNLOCK: ItemName.GREENWOOD_VILLAGE_DOOR_2_UNLOCK,
+ ItemName.BOULDER_CANYON_DOOR_1_UNLOCK: ItemName.BOULDER_CANYON_DOOR_2_UNLOCK,
+ ItemName.HAUNTED_WOODS_DOOR_1_UNLOCK: ItemName.HAUNTED_WOODS_DOOR_2_UNLOCK,
+ ItemName.SPACEDUST_ALLEY_DOOR_1_UNLOCK: ItemName.SPACEDUST_ALLEY_DOOR_2_UNLOCK,
+ ItemName.DARKMOON_CAVERNS_DOOR_1_UNLOCK: ItemName.DARKMOON_CAVERNS_DOOR_2_UNLOCK,
+ ItemName.SPACEPORT_ALPHA_DOOR_1_UNLOCK: ItemName.SPACEPORT_ALPHA_DOOR_2_UNLOCK,
+ ItemName.STAR_CITY_DOOR_1_UNLOCK: ItemName.STAR_CITY_DOOR_2_UNLOCK
+ }
+
+ dino_domain_race_1_unlocks = (
+ ItemName.ANCIENT_LAKE_DOOR_1_UNLOCK,
+ ItemName.FOSSIL_CANYON_DOOR_1_UNLOCK,
+ ItemName.JUNGLE_FALLS_DOOR_1_UNLOCK,
+ ItemName.HOT_TOP_VOLCANO_DOOR_1_UNLOCK
+ )
+ snowflake_mountain_race_1_unlocks = (
+ ItemName.EVERFROST_PEAK_DOOR_1_UNLOCK,
+ ItemName.WALRUS_COVE_DOOR_1_UNLOCK,
+ ItemName.SNOWBALL_VALLEY_DOOR_1_UNLOCK,
+ ItemName.FROSTY_VILLAGE_DOOR_1_UNLOCK
+ )
+ sherbet_island_race_1_unlocks = (
+ ItemName.WHALE_BAY_DOOR_1_UNLOCK,
+ ItemName.CRESCENT_ISLAND_DOOR_1_UNLOCK,
+ ItemName.PIRATE_LAGOON_DOOR_1_UNLOCK,
+ ItemName.TREASURE_CAVES_DOOR_1_UNLOCK
+ )
+ dragon_forest_race_1_unlocks = (
+ ItemName.WINDMILL_PLAINS_DOOR_1_UNLOCK,
+ ItemName.GREENWOOD_VILLAGE_DOOR_1_UNLOCK,
+ ItemName.BOULDER_CANYON_DOOR_1_UNLOCK,
+ ItemName.HAUNTED_WOODS_DOOR_1_UNLOCK
+ )
+ future_fun_land_race_1_unlocks = (
+ ItemName.SPACEDUST_ALLEY_DOOR_1_UNLOCK,
+ ItemName.DARKMOON_CAVERNS_DOOR_1_UNLOCK,
+ ItemName.SPACEPORT_ALPHA_DOOR_1_UNLOCK,
+ ItemName.STAR_CITY_DOOR_1_UNLOCK
+ )
+
+ if world.options.open_worlds:
+ available_doors = [
+ *dino_domain_race_1_unlocks,
+ *snowflake_mountain_race_1_unlocks,
+ *sherbet_island_race_1_unlocks,
+ *dragon_forest_race_1_unlocks,
+ *future_fun_land_race_1_unlocks
+ ]
+ else:
+ available_doors = [
+ ItemName.DINO_DOMAIN_UNLOCK,
+ ItemName.SNOWFLAKE_MOUNTAIN_UNLOCK,
+ ItemName.SHERBET_ISLAND_UNLOCK,
+ ItemName.DRAGON_FOREST_UNLOCK
+ ]
+
+ door_unlock_item_to_index = {door_unlock_info.item: index for index, door_unlock_info in
+ enumerate(vanilla_door_unlock_info_list)}
+
+ race_2_unlock_count = 0
+
+ for door_unlock_info, requirement in zip(vanilla_door_unlock_info_sorted_by_requirement,
+ get_door_requirement_progression(world)):
+ if not (world.options.open_worlds and door_unlock_info.location in LocationName.WORLD_UNLOCK_LOCATIONS):
+ world.random.shuffle(available_doors)
+ door_unlock_item = available_doors.pop()
+ door_unlock_item_index = door_unlock_item_to_index[door_unlock_item]
+ world.door_unlock_requirements[door_unlock_item_index] = requirement
+
+ if door_unlock_item == ItemName.DINO_DOMAIN_UNLOCK:
+ available_doors.extend(dino_domain_race_1_unlocks)
+ elif door_unlock_item == ItemName.SNOWFLAKE_MOUNTAIN_UNLOCK:
+ available_doors.extend(snowflake_mountain_race_1_unlocks)
+ elif door_unlock_item == ItemName.SHERBET_ISLAND_UNLOCK:
+ available_doors.extend(sherbet_island_race_1_unlocks)
+ elif door_unlock_item == ItemName.DRAGON_FOREST_UNLOCK:
+ available_doors.extend(dragon_forest_race_1_unlocks)
+ elif door_unlock_item in race_1_unlock_to_race_2_unlock:
+ available_doors.append(race_1_unlock_to_race_2_unlock[door_unlock_item])
+ elif not world.options.open_worlds:
+ race_2_unlock_count += 1
+ if race_2_unlock_count == 16:
+ available_doors.extend(future_fun_land_race_1_unlocks)
+
+
+def place_vanilla_door_unlock_items(world: DiddyKongRacingWorld) -> None:
+ for door_num, door_unlock_info in enumerate(vanilla_door_unlock_info_list):
+ if not (world.options.open_worlds and door_unlock_info.location in LocationName.WORLD_UNLOCK_LOCATIONS):
+ world.door_unlock_requirements[door_num] = door_unlock_info.requirement
+
+
+def place_door_unlock_items(world: DiddyKongRacingWorld) -> None:
+ filled_door_unlock_locations = set()
+ if world.options.open_worlds:
+ filled_door_unlock_locations.update(LocationName.WORLD_UNLOCK_LOCATIONS)
+
+ origin_region = world.get_region(world.origin_region_name)
+
+ for item_door_unlock_info, item_door_unlock_requirement in zip(vanilla_door_unlock_info_list,
+ world.door_unlock_requirements):
+ if not (world.options.open_worlds and item_door_unlock_info.location in LocationName.WORLD_UNLOCK_LOCATIONS):
+ for location_door_unlock_info, location_door_unlock_requirement in \
+ zip(vanilla_door_unlock_info_sorted_by_requirement, get_door_requirement_progression(world)):
+ base_location_name = location_door_unlock_info.location
+ if item_door_unlock_requirement == location_door_unlock_requirement and base_location_name not in filled_door_unlock_locations:
+ location_name = build_door_unlock_location_name(location_door_unlock_info.location,
+ item_door_unlock_requirement)
+ origin_region.add_locations({location_name: None})
+ world.place_locked_item(location_name, world.create_event_item(item_door_unlock_info.item))
+ filled_door_unlock_locations.add(base_location_name)
+ break
+
+
+def build_door_unlock_location_name(door_unlock_location_base_name: str, door_unlock_requirement: int) -> str:
+ return str(door_unlock_requirement) + " balloon(s) (" + door_unlock_location_base_name + ")"
+
+
+def is_door_unlock_location(location: Location):
+ return DOOR_UNLOCK_ITEM_PATTERN.match(location.name)
+
+
+def get_door_unlock_requirement(location: Location):
+ return int(DOOR_UNLOCK_ITEM_PATTERN.match(location.name).group(1))
diff --git a/worlds/diddy_kong_racing/Items.py b/worlds/diddy_kong_racing/Items.py
index 5e2d4b5cb90d..b69a138204a3 100644
--- a/worlds/diddy_kong_racing/Items.py
+++ b/worlds/diddy_kong_racing/Items.py
@@ -1,56 +1,56 @@
-from typing import NamedTuple
-
-from BaseClasses import Item
-from .Names import ItemName
-
-
-class DiddyKongRacingItem(Item):
- game: str = "Diddy Kong Racing"
-
-
-class ItemData(NamedTuple):
- dkr_id: int
- count: int
-
-
-BALLOON_TABLE: dict[str, ItemData] = {
- ItemName.TIMBERS_ISLAND_BALLOON: ItemData(1616000, 7),
- ItemName.DINO_DOMAIN_BALLOON: ItemData(1616001, 8),
- ItemName.SNOWFLAKE_MOUNTAIN_BALLOON: ItemData(1616002, 8),
- ItemName.SHERBET_ISLAND_BALLOON: ItemData(1616003, 8),
- ItemName.DRAGON_FOREST_BALLOON: ItemData(1616004, 8),
- ItemName.FUTURE_FUN_LAND_BALLOON: ItemData(1616005, 8)
-}
-
-KEY_TABLE: dict[str, ItemData] = {
- ItemName.FIRE_MOUNTAIN_KEY: ItemData(1616006, 1),
- ItemName.ICICLE_PYRAMID_KEY: ItemData(1616007, 1),
- ItemName.DARKWATER_BEACH_KEY: ItemData(1616008, 1),
- ItemName.SMOKEY_CASTLE_KEY: ItemData(1616009, 1)
-}
-
-AMULET_TABLE: dict[str, ItemData] = {
- ItemName.WIZPIG_AMULET_PIECE: ItemData(1616010, 4),
- ItemName.TT_AMULET_PIECE: ItemData(1616011, 4)
-}
-
-ALL_ITEM_TABLE: dict[str, ItemData] = {
- **BALLOON_TABLE,
- **KEY_TABLE,
- **AMULET_TABLE
-}
-
-ITEM_NAME_GROUPS: dict[str, set[str]] = {
- "Balloons": {ItemName.TIMBERS_ISLAND_BALLOON, ItemName.DINO_DOMAIN_BALLOON, ItemName.SNOWFLAKE_MOUNTAIN_BALLOON,
- ItemName.SHERBET_ISLAND_BALLOON, ItemName.FUTURE_FUN_LAND_BALLOON},
- "Keys": {ItemName.FIRE_MOUNTAIN_KEY, ItemName.ICICLE_PYRAMID_KEY, ItemName.DARKWATER_BEACH_KEY,
- ItemName.SMOKEY_CASTLE_KEY},
- "Amulets": {ItemName.WIZPIG_AMULET_PIECE, ItemName.TT_AMULET_PIECE},
- "TI Balloon": {ItemName.TIMBERS_ISLAND_BALLOON},
- "DD Balloon": {ItemName.DINO_DOMAIN_BALLOON},
- "SM Balloon": {ItemName.SNOWFLAKE_MOUNTAIN_BALLOON},
- "SI Balloon": {ItemName.SHERBET_ISLAND_BALLOON},
- "FFL Balloon": {ItemName.FUTURE_FUN_LAND_BALLOON},
- "Wizpig Amulet": {ItemName.WIZPIG_AMULET_PIECE},
- "TT Amulet": {ItemName.TT_AMULET_PIECE}
-}
+from typing import NamedTuple
+
+from BaseClasses import Item
+from .Names import ItemName
+
+
+class DiddyKongRacingItem(Item):
+ game: str = "Diddy Kong Racing"
+
+
+class ItemData(NamedTuple):
+ dkr_id: int
+ count: int
+
+
+BALLOON_TABLE: dict[str, ItemData] = {
+ ItemName.TIMBERS_ISLAND_BALLOON: ItemData(1616000, 7),
+ ItemName.DINO_DOMAIN_BALLOON: ItemData(1616001, 8),
+ ItemName.SNOWFLAKE_MOUNTAIN_BALLOON: ItemData(1616002, 8),
+ ItemName.SHERBET_ISLAND_BALLOON: ItemData(1616003, 8),
+ ItemName.DRAGON_FOREST_BALLOON: ItemData(1616004, 8),
+ ItemName.FUTURE_FUN_LAND_BALLOON: ItemData(1616005, 8)
+}
+
+KEY_TABLE: dict[str, ItemData] = {
+ ItemName.FIRE_MOUNTAIN_KEY: ItemData(1616006, 1),
+ ItemName.ICICLE_PYRAMID_KEY: ItemData(1616007, 1),
+ ItemName.DARKWATER_BEACH_KEY: ItemData(1616008, 1),
+ ItemName.SMOKEY_CASTLE_KEY: ItemData(1616009, 1)
+}
+
+AMULET_TABLE: dict[str, ItemData] = {
+ ItemName.WIZPIG_AMULET_PIECE: ItemData(1616010, 4),
+ ItemName.TT_AMULET_PIECE: ItemData(1616011, 4)
+}
+
+ALL_ITEM_TABLE: dict[str, ItemData] = {
+ **BALLOON_TABLE,
+ **KEY_TABLE,
+ **AMULET_TABLE
+}
+
+ITEM_NAME_GROUPS: dict[str, set[str]] = {
+ "Balloons": {ItemName.TIMBERS_ISLAND_BALLOON, ItemName.DINO_DOMAIN_BALLOON, ItemName.SNOWFLAKE_MOUNTAIN_BALLOON,
+ ItemName.SHERBET_ISLAND_BALLOON, ItemName.FUTURE_FUN_LAND_BALLOON},
+ "Keys": {ItemName.FIRE_MOUNTAIN_KEY, ItemName.ICICLE_PYRAMID_KEY, ItemName.DARKWATER_BEACH_KEY,
+ ItemName.SMOKEY_CASTLE_KEY},
+ "Amulets": {ItemName.WIZPIG_AMULET_PIECE, ItemName.TT_AMULET_PIECE},
+ "TI Balloon": {ItemName.TIMBERS_ISLAND_BALLOON},
+ "DD Balloon": {ItemName.DINO_DOMAIN_BALLOON},
+ "SM Balloon": {ItemName.SNOWFLAKE_MOUNTAIN_BALLOON},
+ "SI Balloon": {ItemName.SHERBET_ISLAND_BALLOON},
+ "FFL Balloon": {ItemName.FUTURE_FUN_LAND_BALLOON},
+ "Wizpig Amulet": {ItemName.WIZPIG_AMULET_PIECE},
+ "TT Amulet": {ItemName.TT_AMULET_PIECE}
+}
diff --git a/worlds/diddy_kong_racing/Locations.py b/worlds/diddy_kong_racing/Locations.py
index e91f31522768..be8b3d279b9f 100644
--- a/worlds/diddy_kong_racing/Locations.py
+++ b/worlds/diddy_kong_racing/Locations.py
@@ -1,101 +1,101 @@
-from __future__ import annotations
-
-from typing import NamedTuple
-
-from BaseClasses import Location
-from .Names import LocationName
-
-
-class DiddyKongRacingLocation(Location):
- game: str = "Diddy Kong Racing"
-
-
-class LocationData(NamedTuple):
- dkr_id: int | None = 0
-
-
-TIMBERS_ISLAND_LOCATION_TABLE: dict[str, LocationData] = {
- LocationName.BRIDGE_BALLOON: LocationData(1616100),
- LocationName.WATERFALL_BALLOON: LocationData(1616101),
- LocationName.RIVER_BALLOON: LocationData(1616102),
- LocationName.OCEAN_BALLOON: LocationData(1616103),
- LocationName.TAJ_CAR_RACE: LocationData(1616104),
- LocationName.TAJ_HOVERCRAFT_RACE: LocationData(1616105),
- LocationName.TAJ_PLANE_RACE: LocationData(1616106)
-}
-
-DINO_DOMAIN_LOCATION_TABLE: dict[str, LocationData] = {
- LocationName.ANCIENT_LAKE_1: LocationData(1616200),
- LocationName.ANCIENT_LAKE_2: LocationData(1616201),
- LocationName.FOSSIL_CANYON_1: LocationData(1616202),
- LocationName.FOSSIL_CANYON_2: LocationData(1616203),
- LocationName.JUNGLE_FALLS_1: LocationData(1616204),
- LocationName.JUNGLE_FALLS_2: LocationData(1616205),
- LocationName.HOT_TOP_VOLCANO_1: LocationData(1616206),
- LocationName.HOT_TOP_VOLCANO_2: LocationData(1616207),
- LocationName.FIRE_MOUNTAIN_KEY: LocationData(1616208),
- LocationName.FIRE_MOUNTAIN: LocationData(1616209),
- LocationName.TRICKY_2: LocationData(1616210)
-}
-
-SNOWFLAKE_MOUNTAIN_LOCATION_TABLE: dict[str, LocationData] = {
- LocationName.EVERFROST_PEAK_1: LocationData(1616300),
- LocationName.EVERFROST_PEAK_2: LocationData(1616301),
- LocationName.WALRUS_COVE_1: LocationData(1616302),
- LocationName.WALRUS_COVE_2: LocationData(1616303),
- LocationName.SNOWBALL_VALLEY_1: LocationData(1616304),
- LocationName.SNOWBALL_VALLEY_2: LocationData(1616305),
- LocationName.FROSTY_VILLAGE_1: LocationData(1616306),
- LocationName.FROSTY_VILLAGE_2: LocationData(1616307),
- LocationName.ICICLE_PYRAMID_KEY: LocationData(1616308),
- LocationName.ICICLE_PYRAMID: LocationData(1616309),
- LocationName.BLUEY_2: LocationData(1616310)
-}
-
-SHERBET_ISLAND_LOCATION_TABLE: dict[str, LocationData] = {
- LocationName.WHALE_BAY_1: LocationData(1616400),
- LocationName.WHALE_BAY_2: LocationData(1616401),
- LocationName.CRESCENT_ISLAND_1: LocationData(1616402),
- LocationName.CRESCENT_ISLAND_2: LocationData(1616403),
- LocationName.PIRATE_LAGOON_1: LocationData(1616404),
- LocationName.PIRATE_LAGOON_2: LocationData(1616405),
- LocationName.TREASURE_CAVES_1: LocationData(1616406),
- LocationName.TREASURE_CAVES_2: LocationData(1616407),
- LocationName.DARKWATER_BEACH_KEY: LocationData(1616408),
- LocationName.DARKWATER_BEACH: LocationData(1616409),
- LocationName.BUBBLER_2: LocationData(1616410)
-}
-
-DRAGON_FOREST_LOCATION_TABLE: dict[str, LocationData] = {
- LocationName.WINDMILL_PLAINS_1: LocationData(1616500),
- LocationName.WINDMILL_PLAINS_2: LocationData(1616501),
- LocationName.GREENWOOD_VILLAGE_1: LocationData(1616502),
- LocationName.GREENWOOD_VILLAGE_2: LocationData(1616503),
- LocationName.BOULDER_CANYON_1: LocationData(1616504),
- LocationName.BOULDER_CANYON_2: LocationData(1616505),
- LocationName.HAUNTED_WOODS_1: LocationData(1616506),
- LocationName.HAUNTED_WOODS_2: LocationData(1616507),
- LocationName.SMOKEY_CASTLE_KEY: LocationData(1616508),
- LocationName.SMOKEY_CASTLE: LocationData(1616509),
- LocationName.SMOKEY_2: LocationData(1616510)
-}
-
-FUTURE_FUN_LAND_LOCATION_TABLE: dict[str, LocationData] = {
- LocationName.SPACEDUST_ALLEY_1: LocationData(1616600),
- LocationName.SPACEDUST_ALLEY_2: LocationData(1616601),
- LocationName.DARKMOON_CAVERNS_1: LocationData(1616602),
- LocationName.DARKMOON_CAVERNS_2: LocationData(1616603),
- LocationName.SPACEPORT_ALPHA_1: LocationData(1616604),
- LocationName.SPACEPORT_ALPHA_2: LocationData(1616605),
- LocationName.STAR_CITY_1: LocationData(1616606),
- LocationName.STAR_CITY_2: LocationData(1616607)
-}
-
-ALL_LOCATION_TABLE: dict[str, LocationData] = {
- **TIMBERS_ISLAND_LOCATION_TABLE,
- **DINO_DOMAIN_LOCATION_TABLE,
- **SNOWFLAKE_MOUNTAIN_LOCATION_TABLE,
- **SHERBET_ISLAND_LOCATION_TABLE,
- **DRAGON_FOREST_LOCATION_TABLE,
- **FUTURE_FUN_LAND_LOCATION_TABLE
-}
+from __future__ import annotations
+
+from typing import NamedTuple
+
+from BaseClasses import Location
+from .Names import LocationName
+
+
+class DiddyKongRacingLocation(Location):
+ game: str = "Diddy Kong Racing"
+
+
+class LocationData(NamedTuple):
+ dkr_id: int | None = 0
+
+
+TIMBERS_ISLAND_LOCATION_TABLE: dict[str, LocationData] = {
+ LocationName.BRIDGE_BALLOON: LocationData(1616100),
+ LocationName.WATERFALL_BALLOON: LocationData(1616101),
+ LocationName.RIVER_BALLOON: LocationData(1616102),
+ LocationName.OCEAN_BALLOON: LocationData(1616103),
+ LocationName.TAJ_CAR_RACE: LocationData(1616104),
+ LocationName.TAJ_HOVERCRAFT_RACE: LocationData(1616105),
+ LocationName.TAJ_PLANE_RACE: LocationData(1616106)
+}
+
+DINO_DOMAIN_LOCATION_TABLE: dict[str, LocationData] = {
+ LocationName.ANCIENT_LAKE_1: LocationData(1616200),
+ LocationName.ANCIENT_LAKE_2: LocationData(1616201),
+ LocationName.FOSSIL_CANYON_1: LocationData(1616202),
+ LocationName.FOSSIL_CANYON_2: LocationData(1616203),
+ LocationName.JUNGLE_FALLS_1: LocationData(1616204),
+ LocationName.JUNGLE_FALLS_2: LocationData(1616205),
+ LocationName.HOT_TOP_VOLCANO_1: LocationData(1616206),
+ LocationName.HOT_TOP_VOLCANO_2: LocationData(1616207),
+ LocationName.FIRE_MOUNTAIN_KEY: LocationData(1616208),
+ LocationName.FIRE_MOUNTAIN: LocationData(1616209),
+ LocationName.TRICKY_2: LocationData(1616210)
+}
+
+SNOWFLAKE_MOUNTAIN_LOCATION_TABLE: dict[str, LocationData] = {
+ LocationName.EVERFROST_PEAK_1: LocationData(1616300),
+ LocationName.EVERFROST_PEAK_2: LocationData(1616301),
+ LocationName.WALRUS_COVE_1: LocationData(1616302),
+ LocationName.WALRUS_COVE_2: LocationData(1616303),
+ LocationName.SNOWBALL_VALLEY_1: LocationData(1616304),
+ LocationName.SNOWBALL_VALLEY_2: LocationData(1616305),
+ LocationName.FROSTY_VILLAGE_1: LocationData(1616306),
+ LocationName.FROSTY_VILLAGE_2: LocationData(1616307),
+ LocationName.ICICLE_PYRAMID_KEY: LocationData(1616308),
+ LocationName.ICICLE_PYRAMID: LocationData(1616309),
+ LocationName.BLUEY_2: LocationData(1616310)
+}
+
+SHERBET_ISLAND_LOCATION_TABLE: dict[str, LocationData] = {
+ LocationName.WHALE_BAY_1: LocationData(1616400),
+ LocationName.WHALE_BAY_2: LocationData(1616401),
+ LocationName.CRESCENT_ISLAND_1: LocationData(1616402),
+ LocationName.CRESCENT_ISLAND_2: LocationData(1616403),
+ LocationName.PIRATE_LAGOON_1: LocationData(1616404),
+ LocationName.PIRATE_LAGOON_2: LocationData(1616405),
+ LocationName.TREASURE_CAVES_1: LocationData(1616406),
+ LocationName.TREASURE_CAVES_2: LocationData(1616407),
+ LocationName.DARKWATER_BEACH_KEY: LocationData(1616408),
+ LocationName.DARKWATER_BEACH: LocationData(1616409),
+ LocationName.BUBBLER_2: LocationData(1616410)
+}
+
+DRAGON_FOREST_LOCATION_TABLE: dict[str, LocationData] = {
+ LocationName.WINDMILL_PLAINS_1: LocationData(1616500),
+ LocationName.WINDMILL_PLAINS_2: LocationData(1616501),
+ LocationName.GREENWOOD_VILLAGE_1: LocationData(1616502),
+ LocationName.GREENWOOD_VILLAGE_2: LocationData(1616503),
+ LocationName.BOULDER_CANYON_1: LocationData(1616504),
+ LocationName.BOULDER_CANYON_2: LocationData(1616505),
+ LocationName.HAUNTED_WOODS_1: LocationData(1616506),
+ LocationName.HAUNTED_WOODS_2: LocationData(1616507),
+ LocationName.SMOKEY_CASTLE_KEY: LocationData(1616508),
+ LocationName.SMOKEY_CASTLE: LocationData(1616509),
+ LocationName.SMOKEY_2: LocationData(1616510)
+}
+
+FUTURE_FUN_LAND_LOCATION_TABLE: dict[str, LocationData] = {
+ LocationName.SPACEDUST_ALLEY_1: LocationData(1616600),
+ LocationName.SPACEDUST_ALLEY_2: LocationData(1616601),
+ LocationName.DARKMOON_CAVERNS_1: LocationData(1616602),
+ LocationName.DARKMOON_CAVERNS_2: LocationData(1616603),
+ LocationName.SPACEPORT_ALPHA_1: LocationData(1616604),
+ LocationName.SPACEPORT_ALPHA_2: LocationData(1616605),
+ LocationName.STAR_CITY_1: LocationData(1616606),
+ LocationName.STAR_CITY_2: LocationData(1616607)
+}
+
+ALL_LOCATION_TABLE: dict[str, LocationData] = {
+ **TIMBERS_ISLAND_LOCATION_TABLE,
+ **DINO_DOMAIN_LOCATION_TABLE,
+ **SNOWFLAKE_MOUNTAIN_LOCATION_TABLE,
+ **SHERBET_ISLAND_LOCATION_TABLE,
+ **DRAGON_FOREST_LOCATION_TABLE,
+ **FUTURE_FUN_LAND_LOCATION_TABLE
+}
diff --git a/worlds/diddy_kong_racing/Names/ItemName.py b/worlds/diddy_kong_racing/Names/ItemName.py
index 43e8bb11176c..d5fe6a2641fe 100644
--- a/worlds/diddy_kong_racing/Names/ItemName.py
+++ b/worlds/diddy_kong_racing/Names/ItemName.py
@@ -1,114 +1,114 @@
-TIMBERS_ISLAND_BALLOON = "Timber's Island Balloon"
-DINO_DOMAIN_BALLOON = "Dino Domain Balloon"
-SNOWFLAKE_MOUNTAIN_BALLOON = "Snowflake Mountain Balloon"
-SHERBET_ISLAND_BALLOON = "Sherbet Island Balloon"
-DRAGON_FOREST_BALLOON = "Dragon Forest Balloon"
-FUTURE_FUN_LAND_BALLOON = "Future Fun Land Balloon"
-
-FIRE_MOUNTAIN_KEY = "Fire Mountain Key"
-ICICLE_PYRAMID_KEY = "Icicle Pyramid Key"
-DARKWATER_BEACH_KEY = "Darkwater Beach Key"
-SMOKEY_CASTLE_KEY = "Smokey Castle Key"
-
-WIZPIG_AMULET_PIECE = "Wizpig Amulet Piece"
-TT_AMULET_PIECE = "T.T. Amulet Piece"
-
-DINO_DOMAIN_UNLOCK = "Dino Domain Unlock"
-SNOWFLAKE_MOUNTAIN_UNLOCK = "Snowflake Mountain Unlock"
-SHERBET_ISLAND_UNLOCK = "Sherbet Island Unlock"
-DRAGON_FOREST_UNLOCK = "Dragon Forest Unlock"
-
-ANCIENT_LAKE_DOOR_1_UNLOCK = "Ancient Lake Door 1 Unlock"
-ANCIENT_LAKE_DOOR_2_UNLOCK = "Ancient Lake Door 2 Unlock"
-FOSSIL_CANYON_DOOR_1_UNLOCK = "Fossil Canyon Door 1 Unlock"
-FOSSIL_CANYON_DOOR_2_UNLOCK = "Fossil Canyon Door 2 Unlock"
-JUNGLE_FALLS_DOOR_1_UNLOCK = "Jungle Falls Door 1 Unlock"
-JUNGLE_FALLS_DOOR_2_UNLOCK = "Jungle Falls Door 2 Unlock"
-HOT_TOP_VOLCANO_DOOR_1_UNLOCK = "Hot Top Volcano Door 1 Unlock"
-HOT_TOP_VOLCANO_DOOR_2_UNLOCK = "Hot Top Volcano Door 2 Unlock"
-EVERFROST_PEAK_DOOR_1_UNLOCK = "Everfrost Peak Door 1 Unlock"
-EVERFROST_PEAK_DOOR_2_UNLOCK = "Everfrost Peak Door 2 Unlock"
-WALRUS_COVE_DOOR_1_UNLOCK = "Walrus Cove Door 1 Unlock"
-WALRUS_COVE_DOOR_2_UNLOCK = "Walrus Cove Door 2 Unlock"
-SNOWBALL_VALLEY_DOOR_1_UNLOCK = "Snowball Valley Door 1 Unlock"
-SNOWBALL_VALLEY_DOOR_2_UNLOCK = "Snowball Valley Door 2 Unlock"
-FROSTY_VILLAGE_DOOR_1_UNLOCK = "Frosty Village Door 1 Unlock"
-FROSTY_VILLAGE_DOOR_2_UNLOCK = "Frosty Village Door 2 Unlock"
-WHALE_BAY_DOOR_1_UNLOCK = "Whale Bay Door 1 Unlock"
-WHALE_BAY_DOOR_2_UNLOCK = "Whale Bay Door 2 Unlock"
-CRESCENT_ISLAND_DOOR_1_UNLOCK = "Crescent Island Door 1 Unlock"
-CRESCENT_ISLAND_DOOR_2_UNLOCK = "Crescent Island Door 2 Unlock"
-PIRATE_LAGOON_DOOR_1_UNLOCK = "Pirate Lagoon Door 1 Unlock"
-PIRATE_LAGOON_DOOR_2_UNLOCK = "Pirate Lagoon Door 2 Unlock"
-TREASURE_CAVES_DOOR_1_UNLOCK = "Treasure Caves Door 1 Unlock"
-TREASURE_CAVES_DOOR_2_UNLOCK = "Treasure Caves Door 2 Unlock"
-WINDMILL_PLAINS_DOOR_1_UNLOCK = "Windmill Plains Door 1 Unlock"
-WINDMILL_PLAINS_DOOR_2_UNLOCK = "Windmill Plains Door 2 Unlock"
-GREENWOOD_VILLAGE_DOOR_1_UNLOCK = "Greenwood Village Door 1 Unlock"
-GREENWOOD_VILLAGE_DOOR_2_UNLOCK = "Greenwood Village Door 2 Unlock"
-BOULDER_CANYON_DOOR_1_UNLOCK = "Boulder Canyon Door 1 Unlock"
-BOULDER_CANYON_DOOR_2_UNLOCK = "Boulder Canyon Door 2 Unlock"
-HAUNTED_WOODS_DOOR_1_UNLOCK = "Haunted Woods Door 1 Unlock"
-HAUNTED_WOODS_DOOR_2_UNLOCK = "Haunted Woods Door 2 Unlock"
-SPACEDUST_ALLEY_DOOR_1_UNLOCK = "Spacedust Alley Door 1 Unlock"
-SPACEDUST_ALLEY_DOOR_2_UNLOCK = "Spacedust Alley Door 2 Unlock"
-DARKMOON_CAVERNS_DOOR_1_UNLOCK = "Darkmoon Caverns Door 1 Unlock"
-DARKMOON_CAVERNS_DOOR_2_UNLOCK = "Darkmoon Caverns Door 2 Unlock"
-SPACEPORT_ALPHA_DOOR_1_UNLOCK = "Spaceport Alpha Door 1 Unlock"
-SPACEPORT_ALPHA_DOOR_2_UNLOCK = "Spaceport Alpha Door 2 Unlock"
-STAR_CITY_DOOR_1_UNLOCK = "Star City Door 1 Unlock"
-STAR_CITY_DOOR_2_UNLOCK = "Star City Door 2 Unlock"
-
-VICTORY = "Victory"
-
-ALL_BALLOON_ITEMS = [
- TIMBERS_ISLAND_BALLOON,
- DINO_DOMAIN_BALLOON,
- SNOWFLAKE_MOUNTAIN_BALLOON,
- SHERBET_ISLAND_BALLOON,
- DRAGON_FOREST_BALLOON,
- FUTURE_FUN_LAND_BALLOON
-]
-
-DOOR_UNLOCK_ITEMS = [
- ANCIENT_LAKE_DOOR_1_UNLOCK,
- ANCIENT_LAKE_DOOR_2_UNLOCK,
- FOSSIL_CANYON_DOOR_1_UNLOCK,
- FOSSIL_CANYON_DOOR_2_UNLOCK,
- JUNGLE_FALLS_DOOR_1_UNLOCK,
- JUNGLE_FALLS_DOOR_2_UNLOCK,
- HOT_TOP_VOLCANO_DOOR_1_UNLOCK,
- HOT_TOP_VOLCANO_DOOR_2_UNLOCK,
- EVERFROST_PEAK_DOOR_1_UNLOCK,
- EVERFROST_PEAK_DOOR_2_UNLOCK,
- WALRUS_COVE_DOOR_1_UNLOCK,
- WALRUS_COVE_DOOR_2_UNLOCK,
- SNOWBALL_VALLEY_DOOR_1_UNLOCK,
- SNOWBALL_VALLEY_DOOR_2_UNLOCK,
- FROSTY_VILLAGE_DOOR_1_UNLOCK,
- FROSTY_VILLAGE_DOOR_2_UNLOCK,
- WHALE_BAY_DOOR_1_UNLOCK,
- WHALE_BAY_DOOR_2_UNLOCK,
- CRESCENT_ISLAND_DOOR_1_UNLOCK,
- CRESCENT_ISLAND_DOOR_2_UNLOCK,
- PIRATE_LAGOON_DOOR_1_UNLOCK,
- PIRATE_LAGOON_DOOR_2_UNLOCK,
- TREASURE_CAVES_DOOR_1_UNLOCK,
- TREASURE_CAVES_DOOR_2_UNLOCK,
- WINDMILL_PLAINS_DOOR_1_UNLOCK,
- WINDMILL_PLAINS_DOOR_2_UNLOCK,
- GREENWOOD_VILLAGE_DOOR_1_UNLOCK,
- GREENWOOD_VILLAGE_DOOR_2_UNLOCK,
- BOULDER_CANYON_DOOR_1_UNLOCK,
- BOULDER_CANYON_DOOR_2_UNLOCK,
- HAUNTED_WOODS_DOOR_1_UNLOCK,
- HAUNTED_WOODS_DOOR_2_UNLOCK,
- SPACEDUST_ALLEY_DOOR_1_UNLOCK,
- SPACEDUST_ALLEY_DOOR_2_UNLOCK,
- DARKMOON_CAVERNS_DOOR_1_UNLOCK,
- DARKMOON_CAVERNS_DOOR_2_UNLOCK,
- SPACEPORT_ALPHA_DOOR_1_UNLOCK,
- SPACEPORT_ALPHA_DOOR_2_UNLOCK,
- STAR_CITY_DOOR_1_UNLOCK,
- STAR_CITY_DOOR_2_UNLOCK
-]
+TIMBERS_ISLAND_BALLOON = "Timber's Island Balloon"
+DINO_DOMAIN_BALLOON = "Dino Domain Balloon"
+SNOWFLAKE_MOUNTAIN_BALLOON = "Snowflake Mountain Balloon"
+SHERBET_ISLAND_BALLOON = "Sherbet Island Balloon"
+DRAGON_FOREST_BALLOON = "Dragon Forest Balloon"
+FUTURE_FUN_LAND_BALLOON = "Future Fun Land Balloon"
+
+FIRE_MOUNTAIN_KEY = "Fire Mountain Key"
+ICICLE_PYRAMID_KEY = "Icicle Pyramid Key"
+DARKWATER_BEACH_KEY = "Darkwater Beach Key"
+SMOKEY_CASTLE_KEY = "Smokey Castle Key"
+
+WIZPIG_AMULET_PIECE = "Wizpig Amulet Piece"
+TT_AMULET_PIECE = "T.T. Amulet Piece"
+
+DINO_DOMAIN_UNLOCK = "Dino Domain Unlock"
+SNOWFLAKE_MOUNTAIN_UNLOCK = "Snowflake Mountain Unlock"
+SHERBET_ISLAND_UNLOCK = "Sherbet Island Unlock"
+DRAGON_FOREST_UNLOCK = "Dragon Forest Unlock"
+
+ANCIENT_LAKE_DOOR_1_UNLOCK = "Ancient Lake Door 1 Unlock"
+ANCIENT_LAKE_DOOR_2_UNLOCK = "Ancient Lake Door 2 Unlock"
+FOSSIL_CANYON_DOOR_1_UNLOCK = "Fossil Canyon Door 1 Unlock"
+FOSSIL_CANYON_DOOR_2_UNLOCK = "Fossil Canyon Door 2 Unlock"
+JUNGLE_FALLS_DOOR_1_UNLOCK = "Jungle Falls Door 1 Unlock"
+JUNGLE_FALLS_DOOR_2_UNLOCK = "Jungle Falls Door 2 Unlock"
+HOT_TOP_VOLCANO_DOOR_1_UNLOCK = "Hot Top Volcano Door 1 Unlock"
+HOT_TOP_VOLCANO_DOOR_2_UNLOCK = "Hot Top Volcano Door 2 Unlock"
+EVERFROST_PEAK_DOOR_1_UNLOCK = "Everfrost Peak Door 1 Unlock"
+EVERFROST_PEAK_DOOR_2_UNLOCK = "Everfrost Peak Door 2 Unlock"
+WALRUS_COVE_DOOR_1_UNLOCK = "Walrus Cove Door 1 Unlock"
+WALRUS_COVE_DOOR_2_UNLOCK = "Walrus Cove Door 2 Unlock"
+SNOWBALL_VALLEY_DOOR_1_UNLOCK = "Snowball Valley Door 1 Unlock"
+SNOWBALL_VALLEY_DOOR_2_UNLOCK = "Snowball Valley Door 2 Unlock"
+FROSTY_VILLAGE_DOOR_1_UNLOCK = "Frosty Village Door 1 Unlock"
+FROSTY_VILLAGE_DOOR_2_UNLOCK = "Frosty Village Door 2 Unlock"
+WHALE_BAY_DOOR_1_UNLOCK = "Whale Bay Door 1 Unlock"
+WHALE_BAY_DOOR_2_UNLOCK = "Whale Bay Door 2 Unlock"
+CRESCENT_ISLAND_DOOR_1_UNLOCK = "Crescent Island Door 1 Unlock"
+CRESCENT_ISLAND_DOOR_2_UNLOCK = "Crescent Island Door 2 Unlock"
+PIRATE_LAGOON_DOOR_1_UNLOCK = "Pirate Lagoon Door 1 Unlock"
+PIRATE_LAGOON_DOOR_2_UNLOCK = "Pirate Lagoon Door 2 Unlock"
+TREASURE_CAVES_DOOR_1_UNLOCK = "Treasure Caves Door 1 Unlock"
+TREASURE_CAVES_DOOR_2_UNLOCK = "Treasure Caves Door 2 Unlock"
+WINDMILL_PLAINS_DOOR_1_UNLOCK = "Windmill Plains Door 1 Unlock"
+WINDMILL_PLAINS_DOOR_2_UNLOCK = "Windmill Plains Door 2 Unlock"
+GREENWOOD_VILLAGE_DOOR_1_UNLOCK = "Greenwood Village Door 1 Unlock"
+GREENWOOD_VILLAGE_DOOR_2_UNLOCK = "Greenwood Village Door 2 Unlock"
+BOULDER_CANYON_DOOR_1_UNLOCK = "Boulder Canyon Door 1 Unlock"
+BOULDER_CANYON_DOOR_2_UNLOCK = "Boulder Canyon Door 2 Unlock"
+HAUNTED_WOODS_DOOR_1_UNLOCK = "Haunted Woods Door 1 Unlock"
+HAUNTED_WOODS_DOOR_2_UNLOCK = "Haunted Woods Door 2 Unlock"
+SPACEDUST_ALLEY_DOOR_1_UNLOCK = "Spacedust Alley Door 1 Unlock"
+SPACEDUST_ALLEY_DOOR_2_UNLOCK = "Spacedust Alley Door 2 Unlock"
+DARKMOON_CAVERNS_DOOR_1_UNLOCK = "Darkmoon Caverns Door 1 Unlock"
+DARKMOON_CAVERNS_DOOR_2_UNLOCK = "Darkmoon Caverns Door 2 Unlock"
+SPACEPORT_ALPHA_DOOR_1_UNLOCK = "Spaceport Alpha Door 1 Unlock"
+SPACEPORT_ALPHA_DOOR_2_UNLOCK = "Spaceport Alpha Door 2 Unlock"
+STAR_CITY_DOOR_1_UNLOCK = "Star City Door 1 Unlock"
+STAR_CITY_DOOR_2_UNLOCK = "Star City Door 2 Unlock"
+
+VICTORY = "Victory"
+
+ALL_BALLOON_ITEMS = [
+ TIMBERS_ISLAND_BALLOON,
+ DINO_DOMAIN_BALLOON,
+ SNOWFLAKE_MOUNTAIN_BALLOON,
+ SHERBET_ISLAND_BALLOON,
+ DRAGON_FOREST_BALLOON,
+ FUTURE_FUN_LAND_BALLOON
+]
+
+DOOR_UNLOCK_ITEMS = [
+ ANCIENT_LAKE_DOOR_1_UNLOCK,
+ ANCIENT_LAKE_DOOR_2_UNLOCK,
+ FOSSIL_CANYON_DOOR_1_UNLOCK,
+ FOSSIL_CANYON_DOOR_2_UNLOCK,
+ JUNGLE_FALLS_DOOR_1_UNLOCK,
+ JUNGLE_FALLS_DOOR_2_UNLOCK,
+ HOT_TOP_VOLCANO_DOOR_1_UNLOCK,
+ HOT_TOP_VOLCANO_DOOR_2_UNLOCK,
+ EVERFROST_PEAK_DOOR_1_UNLOCK,
+ EVERFROST_PEAK_DOOR_2_UNLOCK,
+ WALRUS_COVE_DOOR_1_UNLOCK,
+ WALRUS_COVE_DOOR_2_UNLOCK,
+ SNOWBALL_VALLEY_DOOR_1_UNLOCK,
+ SNOWBALL_VALLEY_DOOR_2_UNLOCK,
+ FROSTY_VILLAGE_DOOR_1_UNLOCK,
+ FROSTY_VILLAGE_DOOR_2_UNLOCK,
+ WHALE_BAY_DOOR_1_UNLOCK,
+ WHALE_BAY_DOOR_2_UNLOCK,
+ CRESCENT_ISLAND_DOOR_1_UNLOCK,
+ CRESCENT_ISLAND_DOOR_2_UNLOCK,
+ PIRATE_LAGOON_DOOR_1_UNLOCK,
+ PIRATE_LAGOON_DOOR_2_UNLOCK,
+ TREASURE_CAVES_DOOR_1_UNLOCK,
+ TREASURE_CAVES_DOOR_2_UNLOCK,
+ WINDMILL_PLAINS_DOOR_1_UNLOCK,
+ WINDMILL_PLAINS_DOOR_2_UNLOCK,
+ GREENWOOD_VILLAGE_DOOR_1_UNLOCK,
+ GREENWOOD_VILLAGE_DOOR_2_UNLOCK,
+ BOULDER_CANYON_DOOR_1_UNLOCK,
+ BOULDER_CANYON_DOOR_2_UNLOCK,
+ HAUNTED_WOODS_DOOR_1_UNLOCK,
+ HAUNTED_WOODS_DOOR_2_UNLOCK,
+ SPACEDUST_ALLEY_DOOR_1_UNLOCK,
+ SPACEDUST_ALLEY_DOOR_2_UNLOCK,
+ DARKMOON_CAVERNS_DOOR_1_UNLOCK,
+ DARKMOON_CAVERNS_DOOR_2_UNLOCK,
+ SPACEPORT_ALPHA_DOOR_1_UNLOCK,
+ SPACEPORT_ALPHA_DOOR_2_UNLOCK,
+ STAR_CITY_DOOR_1_UNLOCK,
+ STAR_CITY_DOOR_2_UNLOCK
+]
diff --git a/worlds/diddy_kong_racing/Names/LocationName.py b/worlds/diddy_kong_racing/Names/LocationName.py
index dca995f18968..a01b217c6002 100644
--- a/worlds/diddy_kong_racing/Names/LocationName.py
+++ b/worlds/diddy_kong_racing/Names/LocationName.py
@@ -1,156 +1,156 @@
-BRIDGE_BALLOON = "TI: Bridge Balloon"
-WATERFALL_BALLOON = "TI: Waterfall Balloon"
-OCEAN_BALLOON = "TI: Ocean Balloon"
-RIVER_BALLOON = "TI: River Balloon"
-TAJ_CAR_RACE = "TI: Taj Car Race"
-TAJ_HOVERCRAFT_RACE = "TI: Taj Hovercraft Race"
-TAJ_PLANE_RACE = "TI: Taj Plane Race"
-ANCIENT_LAKE_1 = "DD: Ancient Lake 1"
-ANCIENT_LAKE_2 = "DD: Ancient Lake 2"
-FOSSIL_CANYON_1 = "DD: Fossil Canyon 1"
-FOSSIL_CANYON_2 = "DD: Fossil Canyon 2"
-JUNGLE_FALLS_1 = "DD: Jungle Falls 1"
-JUNGLE_FALLS_2 = "DD: Jungle Falls 2"
-HOT_TOP_VOLCANO_1 = "DD: Hot Top Volcano 1"
-HOT_TOP_VOLCANO_2 = "DD: Hot Top Volcano 2"
-FIRE_MOUNTAIN_KEY = "DD: Fire Mountain Key"
-FIRE_MOUNTAIN = "DD: Fire Mountain"
-TRICKY_2 = "DD: Tricky 2"
-EVERFROST_PEAK_1 = "SM: Everfrost Peak 1"
-EVERFROST_PEAK_2 = "SM: Everfrost Peak 2"
-WALRUS_COVE_1 = "SM: Walrus Cove 1"
-WALRUS_COVE_2 = "SM: Walrus Cove 2"
-SNOWBALL_VALLEY_1 = "SM: Snowball Valley 1"
-SNOWBALL_VALLEY_2 = "SM: Snowball Valley 2"
-FROSTY_VILLAGE_1 = "SM: Frosty Village 1"
-FROSTY_VILLAGE_2 = "SM: Frosty Village 2"
-ICICLE_PYRAMID_KEY = "SM: Icicle Pyramid Key"
-ICICLE_PYRAMID = "SM: Icicle Pyramid"
-BLUEY_2 = "SM: Bluey 2"
-WHALE_BAY_1 = "SI: Whale Bay 1"
-WHALE_BAY_2 = "SI: Whale Bay 2"
-CRESCENT_ISLAND_1 = "SI: Crescent Island 1"
-CRESCENT_ISLAND_2 = "SI: Crescent Island 2"
-PIRATE_LAGOON_1 = "SI: Pirate Lagoon 1"
-PIRATE_LAGOON_2 = "SI: Pirate Lagoon 2"
-TREASURE_CAVES_1 = "SI: Treasure Caves 1"
-TREASURE_CAVES_2 = "SI: Treasure Caves 2"
-DARKWATER_BEACH_KEY = "SI: Darkwater Beach Key"
-DARKWATER_BEACH = "SI: Darkwater Beach"
-BUBBLER_2 = "SI: Bubbler 2"
-WINDMILL_PLAINS_1 = "DF: Windmill Plains 1"
-WINDMILL_PLAINS_2 = "DF: Windmill Plains 2"
-GREENWOOD_VILLAGE_1 = "DF: Greenwood Village 1"
-GREENWOOD_VILLAGE_2 = "DF: Greenwood Village 2"
-BOULDER_CANYON_1 = "DF: Boulder Canyon 1"
-BOULDER_CANYON_2 = "DF: Boulder Canyon 2"
-HAUNTED_WOODS_1 = "DF: Haunted Woods 1"
-HAUNTED_WOODS_2 = "DF: Haunted Woods 2"
-SMOKEY_CASTLE_KEY = "DF: Smokey Castle Key"
-SMOKEY_CASTLE = "DF: Smokey Castle"
-SMOKEY_2 = "DF: Smokey 2"
-WIZPIG_1 = "Wizpig 1"
-SPACEDUST_ALLEY_1 = "FFL: Spacedust Alley 1"
-SPACEDUST_ALLEY_2 = "FFL: Spacedust Alley 2"
-DARKMOON_CAVERNS_1 = "FFL: Darkmoon Caverns 1"
-DARKMOON_CAVERNS_2 = "FFL: Darkmoon Caverns 2"
-SPACEPORT_ALPHA_1 = "FFL: Spaceport Alpha 1"
-SPACEPORT_ALPHA_2 = "FFL: Spaceport Alpha 2"
-STAR_CITY_1 = "FFL: Star City 1"
-STAR_CITY_2 = "FFL: Star City 2"
-WIZPIG_2 = "Wizpig 2"
-
-WORLD_1_UNLOCK = "World 1 Unlock"
-WORLD_2_UNLOCK = "World 2 Unlock"
-WORLD_3_UNLOCK = "World 3 Unlock"
-WORLD_4_UNLOCK = "World 4 Unlock"
-RACE_1_1_UNLOCK = "Race 1-1 Unlock"
-RACE_1_2_UNLOCK = "Race 1-2 Unlock"
-RACE_2_1_UNLOCK = "Race 2-1 Unlock"
-RACE_2_2_UNLOCK = "Race 2-2 Unlock"
-RACE_3_1_UNLOCK = "Race 3-1 Unlock"
-RACE_3_2_UNLOCK = "Race 3-2 Unlock"
-RACE_4_1_UNLOCK = "Race 4-1 Unlock"
-RACE_4_2_UNLOCK = "Race 4-2 Unlock"
-RACE_5_1_UNLOCK = "Race 5-1 Unlock"
-RACE_5_2_UNLOCK = "Race 5-2 Unlock"
-RACE_6_1_UNLOCK = "Race 6-1 Unlock"
-RACE_6_2_UNLOCK = "Race 6-2 Unlock"
-RACE_7_1_UNLOCK = "Race 7-1 Unlock"
-RACE_7_2_UNLOCK = "Race 7-2 Unlock"
-RACE_8_1_UNLOCK = "Race 8-1 Unlock"
-RACE_8_2_UNLOCK = "Race 8-2 Unlock"
-RACE_9_1_UNLOCK = "Race 9-1 Unlock"
-RACE_9_2_UNLOCK = "Race 9-2 Unlock"
-RACE_10_1_UNLOCK = "Race 10-1 Unlock"
-RACE_10_2_UNLOCK = "Race 10-2 Unlock"
-RACE_11_1_UNLOCK = "Race 11-1 Unlock"
-RACE_11_2_UNLOCK = "Race 11-2 Unlock"
-RACE_12_1_UNLOCK = "Race 12-1 Unlock"
-RACE_12_2_UNLOCK = "Race 12-2 Unlock"
-RACE_13_1_UNLOCK = "Race 13-1 Unlock"
-RACE_13_2_UNLOCK = "Race 13-2 Unlock"
-RACE_14_1_UNLOCK = "Race 14-1 Unlock"
-RACE_14_2_UNLOCK = "Race 14-2 Unlock"
-RACE_15_1_UNLOCK = "Race 15-1 Unlock"
-RACE_15_2_UNLOCK = "Race 15-2 Unlock"
-RACE_16_1_UNLOCK = "Race 16-1 Unlock"
-RACE_16_2_UNLOCK = "Race 16-2 Unlock"
-RACE_17_1_UNLOCK = "Race 17-1 Unlock"
-RACE_17_2_UNLOCK = "Race 17-2 Unlock"
-RACE_18_1_UNLOCK = "Race 18-1 Unlock"
-RACE_18_2_UNLOCK = "Race 18-2 Unlock"
-RACE_19_1_UNLOCK = "Race 19-1 Unlock"
-RACE_19_2_UNLOCK = "Race 19-2 Unlock"
-RACE_20_1_UNLOCK = "Race 20-1 Unlock"
-RACE_20_2_UNLOCK = "Race 20-2 Unlock"
-
-WORLD_UNLOCK_LOCATIONS = [
- WORLD_1_UNLOCK,
- WORLD_2_UNLOCK,
- WORLD_3_UNLOCK,
- WORLD_4_UNLOCK
-]
-
-DOOR_UNLOCK_LOCATIONS = [
- RACE_1_1_UNLOCK,
- RACE_1_2_UNLOCK,
- RACE_2_1_UNLOCK,
- RACE_2_2_UNLOCK,
- RACE_3_1_UNLOCK,
- RACE_3_2_UNLOCK,
- RACE_4_1_UNLOCK,
- RACE_4_2_UNLOCK,
- RACE_5_1_UNLOCK,
- RACE_5_2_UNLOCK,
- RACE_6_1_UNLOCK,
- RACE_6_2_UNLOCK,
- RACE_7_1_UNLOCK,
- RACE_7_2_UNLOCK,
- RACE_8_1_UNLOCK,
- RACE_8_2_UNLOCK,
- RACE_9_1_UNLOCK,
- RACE_9_2_UNLOCK,
- RACE_10_1_UNLOCK,
- RACE_10_2_UNLOCK,
- RACE_11_1_UNLOCK,
- RACE_11_2_UNLOCK,
- RACE_12_1_UNLOCK,
- RACE_12_2_UNLOCK,
- RACE_13_1_UNLOCK,
- RACE_13_2_UNLOCK,
- RACE_14_1_UNLOCK,
- RACE_14_2_UNLOCK,
- RACE_15_1_UNLOCK,
- RACE_15_2_UNLOCK,
- RACE_16_1_UNLOCK,
- RACE_16_2_UNLOCK,
- RACE_17_1_UNLOCK,
- RACE_17_2_UNLOCK,
- RACE_18_1_UNLOCK,
- RACE_18_2_UNLOCK,
- RACE_19_1_UNLOCK,
- RACE_19_2_UNLOCK,
- RACE_20_1_UNLOCK,
- RACE_20_2_UNLOCK
-]
+BRIDGE_BALLOON = "TI: Bridge Balloon"
+WATERFALL_BALLOON = "TI: Waterfall Balloon"
+OCEAN_BALLOON = "TI: Ocean Balloon"
+RIVER_BALLOON = "TI: River Balloon"
+TAJ_CAR_RACE = "TI: Taj Car Race"
+TAJ_HOVERCRAFT_RACE = "TI: Taj Hovercraft Race"
+TAJ_PLANE_RACE = "TI: Taj Plane Race"
+ANCIENT_LAKE_1 = "DD: Ancient Lake 1"
+ANCIENT_LAKE_2 = "DD: Ancient Lake 2"
+FOSSIL_CANYON_1 = "DD: Fossil Canyon 1"
+FOSSIL_CANYON_2 = "DD: Fossil Canyon 2"
+JUNGLE_FALLS_1 = "DD: Jungle Falls 1"
+JUNGLE_FALLS_2 = "DD: Jungle Falls 2"
+HOT_TOP_VOLCANO_1 = "DD: Hot Top Volcano 1"
+HOT_TOP_VOLCANO_2 = "DD: Hot Top Volcano 2"
+FIRE_MOUNTAIN_KEY = "DD: Fire Mountain Key"
+FIRE_MOUNTAIN = "DD: Fire Mountain"
+TRICKY_2 = "DD: Tricky 2"
+EVERFROST_PEAK_1 = "SM: Everfrost Peak 1"
+EVERFROST_PEAK_2 = "SM: Everfrost Peak 2"
+WALRUS_COVE_1 = "SM: Walrus Cove 1"
+WALRUS_COVE_2 = "SM: Walrus Cove 2"
+SNOWBALL_VALLEY_1 = "SM: Snowball Valley 1"
+SNOWBALL_VALLEY_2 = "SM: Snowball Valley 2"
+FROSTY_VILLAGE_1 = "SM: Frosty Village 1"
+FROSTY_VILLAGE_2 = "SM: Frosty Village 2"
+ICICLE_PYRAMID_KEY = "SM: Icicle Pyramid Key"
+ICICLE_PYRAMID = "SM: Icicle Pyramid"
+BLUEY_2 = "SM: Bluey 2"
+WHALE_BAY_1 = "SI: Whale Bay 1"
+WHALE_BAY_2 = "SI: Whale Bay 2"
+CRESCENT_ISLAND_1 = "SI: Crescent Island 1"
+CRESCENT_ISLAND_2 = "SI: Crescent Island 2"
+PIRATE_LAGOON_1 = "SI: Pirate Lagoon 1"
+PIRATE_LAGOON_2 = "SI: Pirate Lagoon 2"
+TREASURE_CAVES_1 = "SI: Treasure Caves 1"
+TREASURE_CAVES_2 = "SI: Treasure Caves 2"
+DARKWATER_BEACH_KEY = "SI: Darkwater Beach Key"
+DARKWATER_BEACH = "SI: Darkwater Beach"
+BUBBLER_2 = "SI: Bubbler 2"
+WINDMILL_PLAINS_1 = "DF: Windmill Plains 1"
+WINDMILL_PLAINS_2 = "DF: Windmill Plains 2"
+GREENWOOD_VILLAGE_1 = "DF: Greenwood Village 1"
+GREENWOOD_VILLAGE_2 = "DF: Greenwood Village 2"
+BOULDER_CANYON_1 = "DF: Boulder Canyon 1"
+BOULDER_CANYON_2 = "DF: Boulder Canyon 2"
+HAUNTED_WOODS_1 = "DF: Haunted Woods 1"
+HAUNTED_WOODS_2 = "DF: Haunted Woods 2"
+SMOKEY_CASTLE_KEY = "DF: Smokey Castle Key"
+SMOKEY_CASTLE = "DF: Smokey Castle"
+SMOKEY_2 = "DF: Smokey 2"
+WIZPIG_1 = "Wizpig 1"
+SPACEDUST_ALLEY_1 = "FFL: Spacedust Alley 1"
+SPACEDUST_ALLEY_2 = "FFL: Spacedust Alley 2"
+DARKMOON_CAVERNS_1 = "FFL: Darkmoon Caverns 1"
+DARKMOON_CAVERNS_2 = "FFL: Darkmoon Caverns 2"
+SPACEPORT_ALPHA_1 = "FFL: Spaceport Alpha 1"
+SPACEPORT_ALPHA_2 = "FFL: Spaceport Alpha 2"
+STAR_CITY_1 = "FFL: Star City 1"
+STAR_CITY_2 = "FFL: Star City 2"
+WIZPIG_2 = "Wizpig 2"
+
+WORLD_1_UNLOCK = "World 1 Unlock"
+WORLD_2_UNLOCK = "World 2 Unlock"
+WORLD_3_UNLOCK = "World 3 Unlock"
+WORLD_4_UNLOCK = "World 4 Unlock"
+RACE_1_1_UNLOCK = "Race 1-1 Unlock"
+RACE_1_2_UNLOCK = "Race 1-2 Unlock"
+RACE_2_1_UNLOCK = "Race 2-1 Unlock"
+RACE_2_2_UNLOCK = "Race 2-2 Unlock"
+RACE_3_1_UNLOCK = "Race 3-1 Unlock"
+RACE_3_2_UNLOCK = "Race 3-2 Unlock"
+RACE_4_1_UNLOCK = "Race 4-1 Unlock"
+RACE_4_2_UNLOCK = "Race 4-2 Unlock"
+RACE_5_1_UNLOCK = "Race 5-1 Unlock"
+RACE_5_2_UNLOCK = "Race 5-2 Unlock"
+RACE_6_1_UNLOCK = "Race 6-1 Unlock"
+RACE_6_2_UNLOCK = "Race 6-2 Unlock"
+RACE_7_1_UNLOCK = "Race 7-1 Unlock"
+RACE_7_2_UNLOCK = "Race 7-2 Unlock"
+RACE_8_1_UNLOCK = "Race 8-1 Unlock"
+RACE_8_2_UNLOCK = "Race 8-2 Unlock"
+RACE_9_1_UNLOCK = "Race 9-1 Unlock"
+RACE_9_2_UNLOCK = "Race 9-2 Unlock"
+RACE_10_1_UNLOCK = "Race 10-1 Unlock"
+RACE_10_2_UNLOCK = "Race 10-2 Unlock"
+RACE_11_1_UNLOCK = "Race 11-1 Unlock"
+RACE_11_2_UNLOCK = "Race 11-2 Unlock"
+RACE_12_1_UNLOCK = "Race 12-1 Unlock"
+RACE_12_2_UNLOCK = "Race 12-2 Unlock"
+RACE_13_1_UNLOCK = "Race 13-1 Unlock"
+RACE_13_2_UNLOCK = "Race 13-2 Unlock"
+RACE_14_1_UNLOCK = "Race 14-1 Unlock"
+RACE_14_2_UNLOCK = "Race 14-2 Unlock"
+RACE_15_1_UNLOCK = "Race 15-1 Unlock"
+RACE_15_2_UNLOCK = "Race 15-2 Unlock"
+RACE_16_1_UNLOCK = "Race 16-1 Unlock"
+RACE_16_2_UNLOCK = "Race 16-2 Unlock"
+RACE_17_1_UNLOCK = "Race 17-1 Unlock"
+RACE_17_2_UNLOCK = "Race 17-2 Unlock"
+RACE_18_1_UNLOCK = "Race 18-1 Unlock"
+RACE_18_2_UNLOCK = "Race 18-2 Unlock"
+RACE_19_1_UNLOCK = "Race 19-1 Unlock"
+RACE_19_2_UNLOCK = "Race 19-2 Unlock"
+RACE_20_1_UNLOCK = "Race 20-1 Unlock"
+RACE_20_2_UNLOCK = "Race 20-2 Unlock"
+
+WORLD_UNLOCK_LOCATIONS = [
+ WORLD_1_UNLOCK,
+ WORLD_2_UNLOCK,
+ WORLD_3_UNLOCK,
+ WORLD_4_UNLOCK
+]
+
+DOOR_UNLOCK_LOCATIONS = [
+ RACE_1_1_UNLOCK,
+ RACE_1_2_UNLOCK,
+ RACE_2_1_UNLOCK,
+ RACE_2_2_UNLOCK,
+ RACE_3_1_UNLOCK,
+ RACE_3_2_UNLOCK,
+ RACE_4_1_UNLOCK,
+ RACE_4_2_UNLOCK,
+ RACE_5_1_UNLOCK,
+ RACE_5_2_UNLOCK,
+ RACE_6_1_UNLOCK,
+ RACE_6_2_UNLOCK,
+ RACE_7_1_UNLOCK,
+ RACE_7_2_UNLOCK,
+ RACE_8_1_UNLOCK,
+ RACE_8_2_UNLOCK,
+ RACE_9_1_UNLOCK,
+ RACE_9_2_UNLOCK,
+ RACE_10_1_UNLOCK,
+ RACE_10_2_UNLOCK,
+ RACE_11_1_UNLOCK,
+ RACE_11_2_UNLOCK,
+ RACE_12_1_UNLOCK,
+ RACE_12_2_UNLOCK,
+ RACE_13_1_UNLOCK,
+ RACE_13_2_UNLOCK,
+ RACE_14_1_UNLOCK,
+ RACE_14_2_UNLOCK,
+ RACE_15_1_UNLOCK,
+ RACE_15_2_UNLOCK,
+ RACE_16_1_UNLOCK,
+ RACE_16_2_UNLOCK,
+ RACE_17_1_UNLOCK,
+ RACE_17_2_UNLOCK,
+ RACE_18_1_UNLOCK,
+ RACE_18_2_UNLOCK,
+ RACE_19_1_UNLOCK,
+ RACE_19_2_UNLOCK,
+ RACE_20_1_UNLOCK,
+ RACE_20_2_UNLOCK
+]
diff --git a/worlds/diddy_kong_racing/Names/RegionName.py b/worlds/diddy_kong_racing/Names/RegionName.py
index 226bef1979c8..40504b279e9d 100644
--- a/worlds/diddy_kong_racing/Names/RegionName.py
+++ b/worlds/diddy_kong_racing/Names/RegionName.py
@@ -1,36 +1,36 @@
-TIMBERS_ISLAND = "Timber's Island"
-DINO_DOMAIN = "Dino Domain"
-ANCIENT_LAKE = "DD: Ancient Lake"
-FOSSIL_CANYON = "DD: Fossil Canyon"
-JUNGLE_FALLS = "DD: Jungle Falls"
-HOT_TOP_VOLCANO = "DD: Hot Top Volcano"
-FIRE_MOUNTAIN = "DD: Fire Mountain"
-TRICKY = "DD: Tricky"
-SNOWFLAKE_MOUNTAIN = "Snowflake Mountain"
-EVERFROST_PEAK = "SM: Everfrost Peak"
-WALRUS_COVE = "SM: Walrus Cove"
-SNOWBALL_VALLEY = "SM: Snowball Valley"
-FROSTY_VILLAGE = "SM: Frosty Village"
-ICICLE_PYRAMID = "SM: Icicle Pyramid"
-BLUEY = "SM: Bluey"
-SHERBET_ISLAND = "Sherbet Island"
-WHALE_BAY = "SI: Whale Bay"
-CRESCENT_ISLAND = "SI: Crescent Island"
-PIRATE_LAGOON = "SI: Pirate Lagoon"
-TREASURE_CAVES = "SI: Treasure Caves"
-DARKWATER_BEACH = "SI: Darkwater Beach"
-BUBBLER = "SI: Bubbler"
-DRAGON_FOREST = "Dragon Forest"
-WINDMILL_PLAINS = "DF: Windmill Plains"
-GREENWOOD_VILLAGE = "DF: Greenwood Village"
-BOULDER_CANYON = "DF: Boulder Canyon"
-HAUNTED_WOODS = "DF: Haunted Woods"
-SMOKEY_CASTLE = "DF: Smokey Castle"
-SMOKEY = "DF: Smokey"
-WIZPIG_1 = "TI: Wizpig 1"
-FUTURE_FUN_LAND = "Future Fun Land"
-SPACEDUST_ALLEY = "FFL: Spacedust Alley"
-DARKMOON_CAVERNS = "FFL: Darkmoon Caverns"
-SPACEPORT_ALPHA = "FFL: Spaceport Alpha"
-STAR_CITY = "FFL: Star City"
-WIZPIG_2 = "FFL: Wizpig 2"
+TIMBERS_ISLAND = "Timber's Island"
+DINO_DOMAIN = "Dino Domain"
+ANCIENT_LAKE = "DD: Ancient Lake"
+FOSSIL_CANYON = "DD: Fossil Canyon"
+JUNGLE_FALLS = "DD: Jungle Falls"
+HOT_TOP_VOLCANO = "DD: Hot Top Volcano"
+FIRE_MOUNTAIN = "DD: Fire Mountain"
+TRICKY = "DD: Tricky"
+SNOWFLAKE_MOUNTAIN = "Snowflake Mountain"
+EVERFROST_PEAK = "SM: Everfrost Peak"
+WALRUS_COVE = "SM: Walrus Cove"
+SNOWBALL_VALLEY = "SM: Snowball Valley"
+FROSTY_VILLAGE = "SM: Frosty Village"
+ICICLE_PYRAMID = "SM: Icicle Pyramid"
+BLUEY = "SM: Bluey"
+SHERBET_ISLAND = "Sherbet Island"
+WHALE_BAY = "SI: Whale Bay"
+CRESCENT_ISLAND = "SI: Crescent Island"
+PIRATE_LAGOON = "SI: Pirate Lagoon"
+TREASURE_CAVES = "SI: Treasure Caves"
+DARKWATER_BEACH = "SI: Darkwater Beach"
+BUBBLER = "SI: Bubbler"
+DRAGON_FOREST = "Dragon Forest"
+WINDMILL_PLAINS = "DF: Windmill Plains"
+GREENWOOD_VILLAGE = "DF: Greenwood Village"
+BOULDER_CANYON = "DF: Boulder Canyon"
+HAUNTED_WOODS = "DF: Haunted Woods"
+SMOKEY_CASTLE = "DF: Smokey Castle"
+SMOKEY = "DF: Smokey"
+WIZPIG_1 = "TI: Wizpig 1"
+FUTURE_FUN_LAND = "Future Fun Land"
+SPACEDUST_ALLEY = "FFL: Spacedust Alley"
+DARKMOON_CAVERNS = "FFL: Darkmoon Caverns"
+SPACEPORT_ALPHA = "FFL: Spaceport Alpha"
+STAR_CITY = "FFL: Star City"
+WIZPIG_2 = "FFL: Wizpig 2"
diff --git a/worlds/diddy_kong_racing/Options.py b/worlds/diddy_kong_racing/Options.py
index d627643d8bd5..b33f7fc27668 100644
--- a/worlds/diddy_kong_racing/Options.py
+++ b/worlds/diddy_kong_racing/Options.py
@@ -1,197 +1,197 @@
-from dataclasses import dataclass
-
-from Options import Choice, DefaultOnToggle, OptionGroup, PerGameCommonOptions, Range, Toggle
-
-
-class VictoryCondition(Choice):
- """
- The victory condition for the seed:
- Beat Wizpig 1: Find the 4 Wizpig amulet pieces and beat the first Wizpig race. Future Fun Land items will not be randomized.
- Beat Wizpig 2: Get access to Future Fun Land, find the 4 T.T. amulet pieces and all 47 golden balloons, and beat the second Wizpig race.
- """
- display_name = "Victory condition"
- option_beat_wizpig_1 = 0
- option_beat_wizpig_2 = 1
- default = option_beat_wizpig_1
-
-
-class ShuffleWizpigAmulet(Toggle):
- """Shuffle the 4 Wizpig amulet pieces into the item pool"""
- display_name = "Shuffle Wizpig amulet"
-
-
-class ShuffleTTAmulet(Toggle):
- """Shuffle the 4 T.T. amulet pieces into the item pool"""
- display_name = "Shuffle T.T. amulet"
-
-
-class OpenWorlds(Toggle):
- """All worlds, including Future Fun Land, will be open from the start"""
- display_name = "Open worlds"
-
-
-class DoorRequirementProgression(Choice):
- """
- The progression of door requirement amounts:
- Vanilla: Same requirement amounts as vanilla, roughly exponential with a big jump at the end of Dragon Forest
- Looks like this: [1, 1, 2, 2, 2, 3, 3, 5, 6, 6, 7, 8, 9, 10, 10, 10, 10, 11, 11, 13, 14, 16, 16, 16, 16, 17, 17, 18, 20, 20, 22, 22, 23, 24, 30, 37, 39, 40, 41, 42, 43, 44, 45, 46]
- Linear: Door requirements go up at a consistent rate
- Looks like this if max door requirement = 46: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46]
- Exponential: Door requirements are clustered towards lower numbers, same trend as vanilla but without big gaps
- Looks like this if max door requirement = 46: [1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 7, 7, 8, 8, 9, 10, 11, 12, 12, 13, 14, 16, 17, 18, 19, 21, 22, 24, 25, 27, 29, 31, 33, 36, 38, 41, 44, 46]
- """
- display_name = "Door requirement progression"
- option_vanilla = 0
- option_linear = 1
- option_exponential = 2
- default = option_vanilla
-
-
-class MaximumDoorRequirement(Range):
- """Maximum balloon requirement for a numbered door (does not include the Wizpig 2 door). Only used if door requirement progression is not vanilla."""
- display_name = "Maximum door requirement"
- range_start = 1
- range_end = 46
- default = range_end
-
-
-class ShuffleDoorRequirements(Toggle):
- """The balloon requirements to open all numbered doors will be shuffled"""
- display_name = "Shuffle door requirements"
-
-
-class ShuffleRaceEntrances(Toggle):
- """The race entrances behind all numbered doors will be shuffled"""
- display_name = "Shuffle race entrances"
-
-
-class Boss1RegionalBalloons(Range):
- """The number of regional balloons required to unlock boss race 1 for that region"""
- display_name = "Boss 1 regional balloons"
- range_start = 0
- range_end = 4
- default = range_end
-
-
-class Boss2RegionalBalloons(Range):
- """The number of regional balloons required to unlock boss race 2 for that region"""
- display_name = "Boss 2 regional balloons"
- range_start = 0
- range_end = 8
- default = range_end
-
-
-class Wizpig1AmuletPieces(Range):
- """The number of Wizpig amulet pieces required to unlock Wizpig race 1"""
- display_name = "Wizpig 1 amulet pieces"
- range_start = 0
- range_end = 4
- default = range_end
-
-
-class Wizpig2AmuletPieces(Range):
- """The number of T.T. amulet pieces required to unlock Wizpig race 2, along with the balloon requirement"""
- display_name = "Wizpig 2 amulet pieces"
- range_start = 0
- range_end = 4
- default = range_end
-
-
-class Wizpig2Balloons(Range):
- """The number of balloons required to unlock Wizpig race 2, along with the T.T. amulet piece requirement"""
- display_name = "Wizpig 2 balloons"
- range_start = 0
- range_end = 47
- default = range_end
-
-
-class RandomizeCharacterOnMapChange(Toggle):
- """Randomly change your character every time the map is changed"""
- display_name = "Randomize character on map change"
-
-
-class PowerUpBalloonType(Choice):
- """
- Alter the power-up balloons in races (does not affect boss races):
- Random (visible): Power-up balloons will visually shuffle between all types.
- Random (hidden): Power-up balloons will shuffle between all types, but always appear rainbow.
- """
- display_name = "Power-up balloon type"
- option_vanilla = 0
- option_random_visible = 1
- option_random_hidden = 2
- default = option_vanilla
-
-
-class MirroredTracks(Choice):
- """Whether tracks will be mirrored"""
- display_name = "Mirrored tracks"
- option_vanilla = 0
- option_mirrored = 1
- option_random_per_track = 2
-
-
-class RandomizeMusic(Toggle):
- """Randomize music"""
- display_name = "Randomize music"
-
-
-class SkipTrophyRaces(DefaultOnToggle):
- """Start with all 1st place trophies, so you only need to beat Wizpig 1 to unlock Future Fun Land"""
- display_name = "Skip trophy races"
-
-
-@dataclass
-class DiddyKongRacingOptions(PerGameCommonOptions):
- victory_condition: VictoryCondition
- shuffle_wizpig_amulet: ShuffleWizpigAmulet
- shuffle_tt_amulet: ShuffleTTAmulet
- open_worlds: OpenWorlds
- door_requirement_progression: DoorRequirementProgression
- maximum_door_requirement: MaximumDoorRequirement
- shuffle_door_requirements: ShuffleDoorRequirements
- shuffle_race_entrances: ShuffleRaceEntrances
- boss_1_regional_balloons: Boss1RegionalBalloons
- boss_2_regional_balloons: Boss2RegionalBalloons
- wizpig_1_amulet_pieces: Wizpig1AmuletPieces
- wizpig_2_amulet_pieces: Wizpig2AmuletPieces
- wizpig_2_balloons: Wizpig2Balloons
- randomize_character_on_map_change: RandomizeCharacterOnMapChange
- mirrored_tracks: MirroredTracks
- randomize_music: RandomizeMusic
- power_up_balloon_type: PowerUpBalloonType
- skip_trophy_races: SkipTrophyRaces
-
-
-OPTION_GROUPS: list[OptionGroup] = [
- OptionGroup("Victory", [
- VictoryCondition
- ]),
- OptionGroup("Items", [
- ShuffleWizpigAmulet,
- ShuffleTTAmulet
- ]),
- OptionGroup("Doors", [
- OpenWorlds,
- DoorRequirementProgression,
- MaximumDoorRequirement,
- ShuffleDoorRequirements,
- ShuffleRaceEntrances
- ]),
- OptionGroup("Bosses", [
- Boss1RegionalBalloons,
- Boss2RegionalBalloons,
- Wizpig1AmuletPieces,
- Wizpig2AmuletPieces,
- Wizpig2Balloons
- ]),
- OptionGroup("Gameplay", [
- RandomizeCharacterOnMapChange,
- MirroredTracks,
- PowerUpBalloonType
- ]),
- OptionGroup("Other", [
- RandomizeMusic,
- SkipTrophyRaces
- ])
-]
+from dataclasses import dataclass
+
+from Options import Choice, DefaultOnToggle, OptionGroup, PerGameCommonOptions, Range, Toggle
+
+
+class VictoryCondition(Choice):
+ """
+ The victory condition for the seed:
+ Beat Wizpig 1: Find the 4 Wizpig amulet pieces and beat the first Wizpig race. Future Fun Land items will not be randomized.
+ Beat Wizpig 2: Get access to Future Fun Land, find the 4 T.T. amulet pieces and all 47 golden balloons, and beat the second Wizpig race.
+ """
+ display_name = "Victory condition"
+ option_beat_wizpig_1 = 0
+ option_beat_wizpig_2 = 1
+ default = option_beat_wizpig_1
+
+
+class ShuffleWizpigAmulet(Toggle):
+ """Shuffle the 4 Wizpig amulet pieces into the item pool"""
+ display_name = "Shuffle Wizpig amulet"
+
+
+class ShuffleTTAmulet(Toggle):
+ """Shuffle the 4 T.T. amulet pieces into the item pool"""
+ display_name = "Shuffle T.T. amulet"
+
+
+class OpenWorlds(Toggle):
+ """All worlds, including Future Fun Land, will be open from the start"""
+ display_name = "Open worlds"
+
+
+class DoorRequirementProgression(Choice):
+ """
+ The progression of door requirement amounts:
+ Vanilla: Same requirement amounts as vanilla, roughly exponential with a big jump at the end of Dragon Forest
+ Looks like this: [1, 1, 2, 2, 2, 3, 3, 5, 6, 6, 7, 8, 9, 10, 10, 10, 10, 11, 11, 13, 14, 16, 16, 16, 16, 17, 17, 18, 20, 20, 22, 22, 23, 24, 30, 37, 39, 40, 41, 42, 43, 44, 45, 46]
+ Linear: Door requirements go up at a consistent rate
+ Looks like this if max door requirement = 46: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46]
+ Exponential: Door requirements are clustered towards lower numbers, same trend as vanilla but without big gaps
+ Looks like this if max door requirement = 46: [1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 7, 7, 8, 8, 9, 10, 11, 12, 12, 13, 14, 16, 17, 18, 19, 21, 22, 24, 25, 27, 29, 31, 33, 36, 38, 41, 44, 46]
+ """
+ display_name = "Door requirement progression"
+ option_vanilla = 0
+ option_linear = 1
+ option_exponential = 2
+ default = option_vanilla
+
+
+class MaximumDoorRequirement(Range):
+ """Maximum balloon requirement for a numbered door (does not include the Wizpig 2 door). Only used if door requirement progression is not vanilla."""
+ display_name = "Maximum door requirement"
+ range_start = 1
+ range_end = 46
+ default = range_end
+
+
+class ShuffleDoorRequirements(Toggle):
+ """The balloon requirements to open all numbered doors will be shuffled"""
+ display_name = "Shuffle door requirements"
+
+
+class ShuffleRaceEntrances(Toggle):
+ """The race entrances behind all numbered doors will be shuffled"""
+ display_name = "Shuffle race entrances"
+
+
+class Boss1RegionalBalloons(Range):
+ """The number of regional balloons required to unlock boss race 1 for that region"""
+ display_name = "Boss 1 regional balloons"
+ range_start = 0
+ range_end = 4
+ default = range_end
+
+
+class Boss2RegionalBalloons(Range):
+ """The number of regional balloons required to unlock boss race 2 for that region"""
+ display_name = "Boss 2 regional balloons"
+ range_start = 0
+ range_end = 8
+ default = range_end
+
+
+class Wizpig1AmuletPieces(Range):
+ """The number of Wizpig amulet pieces required to unlock Wizpig race 1"""
+ display_name = "Wizpig 1 amulet pieces"
+ range_start = 0
+ range_end = 4
+ default = range_end
+
+
+class Wizpig2AmuletPieces(Range):
+ """The number of T.T. amulet pieces required to unlock Wizpig race 2, along with the balloon requirement"""
+ display_name = "Wizpig 2 amulet pieces"
+ range_start = 0
+ range_end = 4
+ default = range_end
+
+
+class Wizpig2Balloons(Range):
+ """The number of balloons required to unlock Wizpig race 2, along with the T.T. amulet piece requirement"""
+ display_name = "Wizpig 2 balloons"
+ range_start = 0
+ range_end = 47
+ default = range_end
+
+
+class TrackVersion(Choice):
+ """Whether regular race tracks will be Adventure 1 (normal) or Adventure 2 (faster CPUs, mirrored tracks, and harder silver coin placements)"""
+ display_name = "Track version"
+ option_adventure_1 = 0
+ option_adventure_2 = 1
+ option_random_per_track = 2
+
+
+class PowerUpBalloonType(Choice):
+ """
+ Alter the power-up balloons in races (does not affect boss races):
+ Random (visible): Power-up balloons will visually shuffle between all types.
+ Random (hidden): Power-up balloons will shuffle between all types, but always appear rainbow.
+ """
+ display_name = "Power-up balloon type"
+ option_vanilla = 0
+ option_random_visible = 1
+ option_random_hidden = 2
+ default = option_vanilla
+
+
+class RandomizeCharacterOnMapChange(Toggle):
+ """Randomly change your character every time the map is changed"""
+ display_name = "Randomize character on map change"
+
+
+class RandomizeMusic(Toggle):
+ """Randomize music"""
+ display_name = "Randomize music"
+
+
+class SkipTrophyRaces(DefaultOnToggle):
+ """Start with all 1st place trophies, so you only need to beat Wizpig 1 to unlock Future Fun Land"""
+ display_name = "Skip trophy races"
+
+
+@dataclass
+class DiddyKongRacingOptions(PerGameCommonOptions):
+ victory_condition: VictoryCondition
+ shuffle_wizpig_amulet: ShuffleWizpigAmulet
+ shuffle_tt_amulet: ShuffleTTAmulet
+ open_worlds: OpenWorlds
+ door_requirement_progression: DoorRequirementProgression
+ maximum_door_requirement: MaximumDoorRequirement
+ shuffle_door_requirements: ShuffleDoorRequirements
+ shuffle_race_entrances: ShuffleRaceEntrances
+ boss_1_regional_balloons: Boss1RegionalBalloons
+ boss_2_regional_balloons: Boss2RegionalBalloons
+ wizpig_1_amulet_pieces: Wizpig1AmuletPieces
+ wizpig_2_amulet_pieces: Wizpig2AmuletPieces
+ wizpig_2_balloons: Wizpig2Balloons
+ track_version: TrackVersion
+ power_up_balloon_type: PowerUpBalloonType
+ randomize_character_on_map_change: RandomizeCharacterOnMapChange
+ randomize_music: RandomizeMusic
+ skip_trophy_races: SkipTrophyRaces
+
+
+OPTION_GROUPS: list[OptionGroup] = [
+ OptionGroup("Victory", [
+ VictoryCondition
+ ]),
+ OptionGroup("Items", [
+ ShuffleWizpigAmulet,
+ ShuffleTTAmulet
+ ]),
+ OptionGroup("Doors", [
+ OpenWorlds,
+ DoorRequirementProgression,
+ MaximumDoorRequirement,
+ ShuffleDoorRequirements,
+ ShuffleRaceEntrances
+ ]),
+ OptionGroup("Bosses", [
+ Boss1RegionalBalloons,
+ Boss2RegionalBalloons,
+ Wizpig1AmuletPieces,
+ Wizpig2AmuletPieces,
+ Wizpig2Balloons
+ ]),
+ OptionGroup("Gameplay", [
+ TrackVersion,
+ PowerUpBalloonType,
+ RandomizeCharacterOnMapChange
+ ]),
+ OptionGroup("Other", [
+ RandomizeMusic,
+ SkipTrophyRaces
+ ])
+]
diff --git a/worlds/diddy_kong_racing/Regions.py b/worlds/diddy_kong_racing/Regions.py
index 2d74c1777281..17e78753ccc2 100644
--- a/worlds/diddy_kong_racing/Regions.py
+++ b/worlds/diddy_kong_racing/Regions.py
@@ -120,9 +120,7 @@
RegionName.SMOKEY: [
LocationName.SMOKEY_2
],
- RegionName.WIZPIG_1: [
- LocationName.WIZPIG_1
- ],
+ RegionName.WIZPIG_1: [],
RegionName.FUTURE_FUN_LAND: [],
RegionName.SPACEDUST_ALLEY: [
LocationName.SPACEDUST_ALLEY_1,
@@ -140,9 +138,7 @@
LocationName.STAR_CITY_1,
LocationName.STAR_CITY_2
],
- RegionName.WIZPIG_2: [
- LocationName.WIZPIG_2
- ]
+ RegionName.WIZPIG_2: []
}
VANILLA_REGION_ORDER: list[str] = [
@@ -193,29 +189,28 @@
def create_regions(world: DiddyKongRacingWorld) -> None:
+ for region_name, locations in DIDDY_KONG_RACING_REGIONS.items():
+ if region_name == RegionName.WIZPIG_2 and world.options.victory_condition.value == 0:
+ break
+
+ region = create_region(world, region_name, locations)
+ world.multiworld.regions.append(region)
+
if world.options.victory_condition.value == 0:
+ victory_region = RegionName.WIZPIG_1
victory_item_location = LocationName.WIZPIG_1
elif world.options.victory_condition.value == 1:
+ victory_region = RegionName.WIZPIG_2
victory_item_location = LocationName.WIZPIG_2
else:
raise Exception("Unexpected victory condition")
- world.multiworld.regions += [
- create_region(world, region_name, locations, victory_item_location)
- for region_name, locations in DIDDY_KONG_RACING_REGIONS.items()
- ]
-
- world.get_location(victory_item_location).place_locked_item(
- world.create_event_item(ItemName.VICTORY)
- )
+ place_victory_item(world, victory_region, victory_item_location)
-def create_region(world: DiddyKongRacingWorld, name: str, locations: list[str], victory_item_location: str) -> Region:
+def create_region(world: DiddyKongRacingWorld, name: str, locations: list[str]) -> Region:
region = Region(name, world.player, world.multiworld)
if locations:
- if victory_item_location in locations:
- region.add_locations({victory_item_location: None})
-
active_locations = world.location_name_to_id
location_to_id = {
location: active_locations.get(location, 0) for location in locations
@@ -226,6 +221,14 @@ def create_region(world: DiddyKongRacingWorld, name: str, locations: list[str],
return region
+def place_victory_item(world: DiddyKongRacingWorld, region_name: str, location_name: str):
+ victory_region = world.get_region(region_name)
+ victory_region.add_locations({location_name: None})
+ world.get_location(location_name).place_locked_item(
+ world.create_event_item(ItemName.VICTORY)
+ )
+
+
def connect_regions(world: DiddyKongRacingWorld) -> None:
add_named_exits(world, RegionName.TIMBERS_ISLAND, [
RegionName.DINO_DOMAIN,
@@ -251,9 +254,11 @@ def connect_regions(world: DiddyKongRacingWorld) -> None:
RegionName.SMOKEY_CASTLE,
RegionName.SMOKEY
])
- add_named_exits(world, RegionName.FUTURE_FUN_LAND, [
- RegionName.WIZPIG_2
- ])
+
+ if world.options.victory_condition.value == 1:
+ add_named_exits(world, RegionName.FUTURE_FUN_LAND, [
+ RegionName.WIZPIG_2
+ ])
# Skip for Universal Tracker, this will be done from slot_data
if not hasattr(world.multiworld, "generation_is_fake"):
@@ -321,7 +326,7 @@ def connect_track_regions(world: DiddyKongRacingWorld) -> None:
def reconnect_found_entrance(world: DiddyKongRacingWorld, key: str) -> None:
- found_region_map_value = int(key.removeprefix(DATASTORAGE_KEY_PREFIX.replace("{player}", str(world.player))))
+ found_region_map_value = int(key[key.rfind("_") + 1:])
found_region_name = MAP_VALUE_TO_REGION_NAME[found_region_map_value]
found_region_index = VANILLA_REGION_ORDER.index(found_region_name)
found_region = world.get_region(found_region_name)
diff --git a/worlds/diddy_kong_racing/RomPatcher.py b/worlds/diddy_kong_racing/RomPatcher.py
index 41069f4057d6..47bc576fef11 100644
--- a/worlds/diddy_kong_racing/RomPatcher.py
+++ b/worlds/diddy_kong_racing/RomPatcher.py
@@ -1,107 +1,107 @@
-import asyncio
-import hashlib
-import os
-import pathlib
-import sys
-import zipfile
-
-import bsdiff4
-
-from CommonClient import logger
-from Utils import open_filename
-
-patched_rom_filename_template: str = "Diddy-Kong-Racing-AP-{}.z64"
-vanilla_rom_md5: str = "4f0e07f0eeac7e5d7ce3a75461888d03"
-vanilla_swapped_rom_md5: str = "e00c0e6bfb0ce740e3e1c50ba82bc01a"
-patched_rom_md5: str = "823797deffc0a992b84c2ce962d5fb94"
-
-
-async def apply_patch(version_number: str) -> None:
- fpath = pathlib.Path(__file__)
- archipelago_root = None
- for i in range(0, 5, +1):
- if fpath.parents[i].stem == "Archipelago":
- archipelago_root = pathlib.Path(__file__).parents[i]
- break
-
- patched_rom_filename = patched_rom_filename_template.format(version_number)
- patched_rom_path = None
- if archipelago_root:
- patched_rom_path = os.path.join(archipelago_root, patched_rom_filename)
-
- if not patched_rom_path or get_file_md5(patched_rom_path) != patched_rom_md5:
- await asyncio.sleep(0.01)
- rom = open_filename("Select your Diddy Kong Racing US 1.0 ROM",
- (("Rom Files", (".z64", ".n64")), ("All Files", "*")))
- if not rom:
- logger.error(
- "ERROR: No ROM selected. Please restart the Diddy Kong Racing client and select your Diddy Kong Racing US 1.0 ROM."
- )
- raise Exception
-
- if not patched_rom_path:
- base_dir = os.path.dirname(rom)
- patched_rom_path = os.path.join(base_dir, patched_rom_filename)
-
- patch_rom(rom, patched_rom_path, "Diddy_Kong_Racing.patch")
-
- if patched_rom_path:
- logger.info("Patched Diddy Kong Racing is located in " + patched_rom_path)
- logger.info("Please open the patched Diddy Kong Racing ROM in Bizhawk and run connector_diddy_kong_racing.lua")
-
-
-def get_file_md5(patched_rom: str) -> str | None:
- if os.path.isfile(patched_rom):
- patched_rom_file = read_file(patched_rom)
- return hashlib.md5(patched_rom_file).hexdigest()
-
- return None
-
-
-def patch_rom(vanilla_rom_path: str, output_path: str, patch_path: str) -> None:
- rom = read_file(vanilla_rom_path)
- rom_md5 = hashlib.md5(rom).hexdigest()
- if rom_md5 == vanilla_swapped_rom_md5:
- rom = swap(rom)
- elif rom_md5 != vanilla_rom_md5:
- logger.error(
- "ERROR: Unknown ROM selected. Please restart the Diddy Kong Racing client and select your Diddy Kong Racing US 1.0 ROM."
- )
- raise Exception
-
- patch_file = open_file(patch_path).read()
- write_file(output_path, bsdiff4.patch(rom, patch_file))
-
-
-def read_file(file_path: str) -> bytes:
- with open(file_path, "rb") as fi:
- data = fi.read()
-
- return data
-
-
-def write_file(file_path: str, data: bytes) -> None:
- with open(file_path, "wb") as fi:
- fi.write(data)
-
-
-def swap(data: bytes) -> bytes:
- swapped_data = bytearray(b'\0' * len(data))
- for i in range(0, len(data), 2):
- swapped_data[i] = data[i + 1]
- swapped_data[i + 1] = data[i]
-
- return bytes(swapped_data)
-
-
-def open_file(resource: str):
- filename = sys.modules[__name__].__file__
- apworld_extension = ".apworld"
- if apworld_extension in filename:
- zip_path = pathlib.Path(filename[:filename.index(apworld_extension) + len(apworld_extension)])
- with zipfile.ZipFile(zip_path) as zf:
- zip_file_path = "diddy_kong_racing/" + resource
-
- return zf.open(zip_file_path, "r")
- else:
- return open(os.path.join(pathlib.Path(__file__).parent, resource), "rb")
+import asyncio
+import hashlib
+import os
+import pathlib
+import sys
+import zipfile
+
+import bsdiff4
+
+from CommonClient import logger
+from Utils import open_filename
+
+patched_rom_filename_template: str = "Diddy-Kong-Racing-AP-{}.z64"
+vanilla_rom_md5: str = "4f0e07f0eeac7e5d7ce3a75461888d03"
+vanilla_swapped_rom_md5: str = "e00c0e6bfb0ce740e3e1c50ba82bc01a"
+patched_rom_md5: str = "e4550b74f39a8f4d37120dcabb651056"
+
+
+async def apply_patch(version_number: str) -> None:
+ fpath = pathlib.Path(__file__)
+ archipelago_root = None
+ for i in range(0, 5, +1):
+ if fpath.parents[i].stem == "Archipelago":
+ archipelago_root = pathlib.Path(__file__).parents[i]
+ break
+
+ patched_rom_filename = patched_rom_filename_template.format(version_number)
+ patched_rom_path = None
+ if archipelago_root:
+ patched_rom_path = os.path.join(archipelago_root, patched_rom_filename)
+
+ if not patched_rom_path or get_file_md5(patched_rom_path) != patched_rom_md5:
+ await asyncio.sleep(0.01)
+ rom = open_filename("Select your Diddy Kong Racing US 1.0 ROM",
+ (("Rom Files", (".z64", ".n64")), ("All Files", "*")))
+ if not rom:
+ logger.error(
+ "ERROR: No ROM selected. Please restart the Diddy Kong Racing client and select your Diddy Kong Racing US 1.0 ROM."
+ )
+ raise Exception
+
+ if not patched_rom_path:
+ base_dir = os.path.dirname(rom)
+ patched_rom_path = os.path.join(base_dir, patched_rom_filename)
+
+ patch_rom(rom, patched_rom_path, "Diddy_Kong_Racing.patch")
+
+ if patched_rom_path:
+ logger.info("Patched Diddy Kong Racing is located in " + patched_rom_path)
+ logger.info("Please open the patched Diddy Kong Racing ROM in Bizhawk and run connector_diddy_kong_racing.lua")
+
+
+def get_file_md5(patched_rom: str) -> str | None:
+ if os.path.isfile(patched_rom):
+ patched_rom_file = read_file(patched_rom)
+ return hashlib.md5(patched_rom_file).hexdigest()
+
+ return None
+
+
+def patch_rom(vanilla_rom_path: str, output_path: str, patch_path: str) -> None:
+ rom = read_file(vanilla_rom_path)
+ rom_md5 = hashlib.md5(rom).hexdigest()
+ if rom_md5 == vanilla_swapped_rom_md5:
+ rom = swap(rom)
+ elif rom_md5 != vanilla_rom_md5:
+ logger.error(
+ "ERROR: Unknown ROM selected. Please restart the Diddy Kong Racing client and select your Diddy Kong Racing US 1.0 ROM."
+ )
+ raise Exception
+
+ patch_file = open_file(patch_path).read()
+ write_file(output_path, bsdiff4.patch(rom, patch_file))
+
+
+def read_file(file_path: str) -> bytes:
+ with open(file_path, "rb") as fi:
+ data = fi.read()
+
+ return data
+
+
+def write_file(file_path: str, data: bytes) -> None:
+ with open(file_path, "wb") as fi:
+ fi.write(data)
+
+
+def swap(data: bytes) -> bytes:
+ swapped_data = bytearray(b'\0' * len(data))
+ for i in range(0, len(data), 2):
+ swapped_data[i] = data[i + 1]
+ swapped_data[i + 1] = data[i]
+
+ return bytes(swapped_data)
+
+
+def open_file(resource: str):
+ filename = sys.modules[__name__].__file__
+ apworld_extension = ".apworld"
+ if apworld_extension in filename:
+ zip_path = pathlib.Path(filename[:filename.index(apworld_extension) + len(apworld_extension)])
+ with zipfile.ZipFile(zip_path) as zf:
+ zip_file_path = "diddy_kong_racing/" + resource
+
+ return zf.open(zip_file_path, "r")
+ else:
+ return open(os.path.join(pathlib.Path(__file__).parent, resource), "rb")
diff --git a/worlds/diddy_kong_racing/Rules.py b/worlds/diddy_kong_racing/Rules.py
index 786c61bff0e1..e8b6419db5f5 100644
--- a/worlds/diddy_kong_racing/Rules.py
+++ b/worlds/diddy_kong_racing/Rules.py
@@ -37,13 +37,13 @@
def set_rules(world: DiddyKongRacingWorld) -> None:
- # Skip for Universal Tracker, this will be called from interpret_slot_data, otherwise entrances won't exist yet
+ # Skip for Universal Tracker, these will be called from interpret_slot_data as they depend on slot data
if not hasattr(world.multiworld, "generation_is_fake"):
set_region_access_rules(world)
+ set_door_unlock_rules(world)
+ set_race_2_location_rules(world)
- set_door_unlock_rules(world)
set_overworld_balloon_rules(world)
- set_race_2_location_rules(world)
set_amulet_rules(world)
world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.VICTORY, world.player)
@@ -90,10 +90,12 @@ def set_region_access_rules(world: DiddyKongRacingWorld) -> None:
RegionName.SPACEDUST_ALLEY: spacedust_alley_door_1(world),
RegionName.DARKMOON_CAVERNS: darkmoon_caverns_door_1(world),
RegionName.SPACEPORT_ALPHA: spaceport_alpha_door_1(world),
- RegionName.STAR_CITY: star_city_door_1(world),
- RegionName.WIZPIG_2: wizpig_2(world),
+ RegionName.STAR_CITY: star_city_door_1(world)
}
+ if world.options.victory_condition.value == 1:
+ region_access_rules[RegionName.WIZPIG_2] = wizpig_2(world)
+
for region, rule in region_access_rules.items():
entrance_name = convert_region_name_to_entrance_name(region)
entrance = world.get_entrance(entrance_name)
diff --git a/worlds/diddy_kong_racing/__init__.py b/worlds/diddy_kong_racing/__init__.py
index f5baf44f532f..5bceddafcbf9 100644
--- a/worlds/diddy_kong_racing/__init__.py
+++ b/worlds/diddy_kong_racing/__init__.py
@@ -1,211 +1,212 @@
-from __future__ import annotations
-
-from typing import Any
-
-from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial
-from worlds.AutoWorld import WebWorld, World
-from worlds.LauncherComponents import Component, components, launch_subprocess, Type
-from .DoorUnlocks import place_door_unlock_items, place_vanilla_door_unlock_items, \
- shuffle_door_unlock_items, vanilla_door_unlock_info_list
-from .Items import ALL_ITEM_TABLE, DiddyKongRacingItem, ITEM_NAME_GROUPS
-from .Locations import ALL_LOCATION_TABLE
-from .Names import ItemName, LocationName, RegionName
-from .Options import DiddyKongRacingOptions, OPTION_GROUPS
-from .Regions import connect_regions, connect_track_regions, create_regions, reconnect_found_entrance
-from .Rules import set_door_unlock_rules, set_region_access_rules, set_rules
-
-
-def run_client():
- from worlds.diddy_kong_racing.DKRClient import main
- launch_subprocess(main)
-
-
-components.append(Component("Diddy Kong Racing Client", func=run_client, component_type=Type.CLIENT))
-
-
-class DiddyKongRacingWeb(WebWorld):
- setup = Tutorial("Setup Diddy Kong Racing",
- """A guide to setting up Archipelago Diddy Kong Racing on your computer.""",
- "English",
- "setup_en.md",
- "setup/en",
- ["zakwiz"])
-
- tutorials = [setup]
- option_groups = OPTION_GROUPS
-
-
-class DiddyKongRacingWorld(World):
- """Diddy Kong Racing is a kart racing game with a story mode, complete with bosses and hidden collectibles."""
-
- game = "Diddy Kong Racing"
- apworld_version = "DKRv1.1.0"
- web = DiddyKongRacingWeb()
- topology_preset = True
- item_name_to_id = {}
-
- for name, data in ALL_ITEM_TABLE.items():
- item_name_to_id[name] = data.dkr_id
-
- location_name_to_id = {name: data.dkr_id for name, data in ALL_LOCATION_TABLE.items()}
-
- item_name_groups = ITEM_NAME_GROUPS
- options_dataclass = DiddyKongRacingOptions
- options: DiddyKongRacingOptions
- origin_region_name: str = RegionName.TIMBERS_ISLAND
- slot_data: dict[str, Any] = {}
- mirrored_tracks: list[bool]
- music: list[int] = list(range(20))
- entrance_order: list[int] = list(range(20))
- door_unlock_requirements: list[int] = [0] * len(vanilla_door_unlock_info_list)
- found_entrances_datastorage_key: list[str] = []
-
- def __init__(self, world: MultiWorld, player: int) -> None:
- super(DiddyKongRacingWorld, self).__init__(world, player)
-
- def create_regions(self) -> None:
- create_regions(self)
- connect_regions(self)
-
- def create_items(self) -> None:
- for name, dkr_id in ALL_ITEM_TABLE.items():
- if not self.is_item_pre_filled(name):
- for _ in range(dkr_id.count):
- item = self.create_item(name)
- self.multiworld.itempool.append(item)
-
- # Skip for Universal Tracker, this will be done from slot_data
- if not hasattr(self.multiworld, "generation_is_fake"):
- if self.options.shuffle_door_requirements:
- shuffle_door_unlock_items(self)
- else:
- place_vanilla_door_unlock_items(self)
-
- place_door_unlock_items(self)
-
- def set_rules(self) -> None:
- set_rules(self)
-
- def generate_basic(self) -> None:
- self.set_mirrored_tracks()
- self.set_music()
-
- def pre_fill(self) -> None:
- if self.is_ffl_unused():
- future_fun_land_balloon = self.create_item(ItemName.FUTURE_FUN_LAND_BALLOON)
- for ffl_exit in self.multiworld.get_region(RegionName.FUTURE_FUN_LAND, self.player).exits:
- if ffl_exit.connected_region.name != RegionName.TIMBERS_ISLAND:
- for ffl_location in ffl_exit.connected_region.locations:
- self.place_locked_item(ffl_location.name, future_fun_land_balloon)
-
- if not self.options.shuffle_wizpig_amulet:
- wizpig_amulet_item = self.create_item(ItemName.WIZPIG_AMULET_PIECE)
- self.place_locked_item(LocationName.TRICKY_2, wizpig_amulet_item)
- self.place_locked_item(LocationName.BLUEY_2, wizpig_amulet_item)
- self.place_locked_item(LocationName.BUBBLER_2, wizpig_amulet_item)
- self.place_locked_item(LocationName.SMOKEY_2, wizpig_amulet_item)
-
- if not self.options.shuffle_tt_amulet:
- tt_amulet_item = self.create_item(ItemName.TT_AMULET_PIECE)
- self.place_locked_item(LocationName.FIRE_MOUNTAIN, tt_amulet_item)
- self.place_locked_item(LocationName.ICICLE_PYRAMID, tt_amulet_item)
- self.place_locked_item(LocationName.DARKWATER_BEACH, tt_amulet_item)
- self.place_locked_item(LocationName.SMOKEY_CASTLE, tt_amulet_item)
-
- def fill_slot_data(self) -> dict[str, Any]:
- dkr_options: dict[str, Any] = {
- "apworld_version": self.apworld_version,
- "player_name": self.multiworld.player_name[self.player],
- "seed": self.random.randint(12212, 69996),
- "victory_condition": self.options.victory_condition.value,
- "shuffle_wizpig_amulet": "true" if self.options.shuffle_wizpig_amulet else "false",
- "shuffle_tt_amulet": "true" if self.options.shuffle_tt_amulet else "false",
- "open_worlds": "true" if self.options.open_worlds else "false",
- "door_requirement_progression": self.options.door_requirement_progression.value,
- "maximum_door_requirement": self.options.maximum_door_requirement.value,
- "shuffle_door_requirements": "true" if self.options.shuffle_door_requirements.value else "false",
- "door_unlock_requirements": self.door_unlock_requirements,
- "shuffle_race_entrances": "true" if self.options.shuffle_race_entrances else "false",
- "entrance_order": self.entrance_order,
- "boss_1_regional_balloons": self.options.boss_1_regional_balloons.value,
- "boss_2_regional_balloons": self.options.boss_2_regional_balloons.value,
- "wizpig_1_amulet_pieces": self.options.wizpig_1_amulet_pieces.value,
- "wizpig_2_amulet_pieces": self.options.wizpig_2_amulet_pieces.value,
- "wizpig_2_balloons": self.options.wizpig_2_balloons.value,
- "randomize_character_on_map_change": "true" if self.options.randomize_character_on_map_change else "false",
- "mirrored_tracks": self.mirrored_tracks,
- "music": self.music,
- "power_up_balloon_type": self.options.power_up_balloon_type.value,
- "skip_trophy_races": "true" if self.options.skip_trophy_races else "false"
- }
-
- return dkr_options
-
- def is_item_pre_filled(self, item_name: str) -> bool:
- if self.is_ffl_unused() and item_name == ItemName.FUTURE_FUN_LAND_BALLOON:
- return True
-
- if not self.options.shuffle_wizpig_amulet and item_name == ItemName.WIZPIG_AMULET_PIECE:
- return True
-
- if not self.options.shuffle_tt_amulet and item_name == ItemName.TT_AMULET_PIECE:
- return True
-
- return False
-
- def create_item(self, item_name: str) -> Item:
- item = ALL_ITEM_TABLE.get(item_name)
-
- item_classification = ItemClassification.progression
- if self.options.victory_condition.value != 1:
- if item_name == ItemName.TT_AMULET_PIECE:
- item_classification = ItemClassification.filler
-
- created_item = DiddyKongRacingItem(
- self.item_id_to_name[item.dkr_id],
- item_classification,
- item.dkr_id,
- self.player
- )
-
- return created_item
-
- def create_event_item(self, name: str) -> Item:
- created_item = DiddyKongRacingItem(name, ItemClassification.progression, None, self.player)
-
- return created_item
-
- def place_locked_item(self, location_name: str, item: Item) -> None:
- self.multiworld.get_location(location_name, self.player).place_locked_item(item)
-
- def is_ffl_unused(self) -> bool:
- return self.options.victory_condition.value == 0 and not self.options.open_worlds
-
- def set_mirrored_tracks(self) -> None:
- num_tracks = 20
- if self.options.mirrored_tracks.value == 0:
- self.mirrored_tracks = [False] * num_tracks
- elif self.options.mirrored_tracks.value == 1:
- self.mirrored_tracks = [True] * num_tracks
- else:
- self.mirrored_tracks = []
- for _ in range(num_tracks):
- self.mirrored_tracks.append(bool(self.random.getrandbits(1)))
-
- def set_music(self) -> None:
- if self.options.randomize_music.value:
- self.random.shuffle(self.music)
-
- # For Universal Tracker
- def interpret_slot_data(self, slot_data: dict[str, Any]) -> None:
- self.entrance_order = slot_data["entrance_order"]
- connect_track_regions(self)
- self.door_unlock_requirements = slot_data["door_unlock_requirements"]
- place_door_unlock_items(self)
- set_region_access_rules(self)
- set_door_unlock_rules(self)
-
- # For Universal Tracker
- def reconnect_found_entrances(self, found_key: str, data_storage_value: Any) -> None:
- if data_storage_value:
- reconnect_found_entrance(self, found_key)
+from __future__ import annotations
+
+from typing import Any
+
+from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial
+from worlds.AutoWorld import WebWorld, World
+from worlds.LauncherComponents import Component, components, launch_subprocess, Type
+from .DoorUnlocks import place_door_unlock_items, place_vanilla_door_unlock_items, \
+ shuffle_door_unlock_items, vanilla_door_unlock_info_list
+from .Items import ALL_ITEM_TABLE, DiddyKongRacingItem, ITEM_NAME_GROUPS
+from .Locations import ALL_LOCATION_TABLE
+from .Names import ItemName, LocationName, RegionName
+from .Options import DiddyKongRacingOptions, OPTION_GROUPS
+from .Regions import connect_regions, connect_track_regions, create_regions, reconnect_found_entrance
+from .Rules import set_door_unlock_rules, set_region_access_rules, set_race_2_location_rules, set_rules
+
+
+def run_client():
+ from worlds.diddy_kong_racing.DKRClient import main
+ launch_subprocess(main)
+
+
+components.append(Component("Diddy Kong Racing Client", func=run_client, component_type=Type.CLIENT))
+
+
+class DiddyKongRacingWeb(WebWorld):
+ setup = Tutorial("Setup Diddy Kong Racing",
+ """A guide to setting up Archipelago Diddy Kong Racing on your computer.""",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["zakwiz"])
+
+ tutorials = [setup]
+ option_groups = OPTION_GROUPS
+
+
+class DiddyKongRacingWorld(World):
+ """Diddy Kong Racing is a kart racing game with a story mode, complete with bosses and hidden collectibles."""
+
+ game = "Diddy Kong Racing"
+ apworld_version = "DKRv1.1.1"
+ web = DiddyKongRacingWeb()
+ topology_preset = True
+ item_name_to_id = {}
+
+ for name, data in ALL_ITEM_TABLE.items():
+ item_name_to_id[name] = data.dkr_id
+
+ location_name_to_id = {name: data.dkr_id for name, data in ALL_LOCATION_TABLE.items()}
+
+ item_name_groups = ITEM_NAME_GROUPS
+ options_dataclass = DiddyKongRacingOptions
+ options: DiddyKongRacingOptions
+ origin_region_name: str = RegionName.TIMBERS_ISLAND
+ slot_data: dict[str, Any] = {}
+ track_versions: list[bool]
+ music: list[int] = list(range(20))
+ entrance_order: list[int] = list(range(20))
+ door_unlock_requirements: list[int] = [0] * len(vanilla_door_unlock_info_list)
+ found_entrances_datastorage_key: list[str] = []
+
+ def __init__(self, world: MultiWorld, player: int) -> None:
+ super(DiddyKongRacingWorld, self).__init__(world, player)
+
+ def create_regions(self) -> None:
+ create_regions(self)
+ connect_regions(self)
+
+ def create_items(self) -> None:
+ for name, dkr_id in ALL_ITEM_TABLE.items():
+ if not self.is_item_pre_filled(name):
+ for _ in range(dkr_id.count):
+ item = self.create_item(name)
+ self.multiworld.itempool.append(item)
+
+ # Skip for Universal Tracker, this will be done from slot_data
+ if not hasattr(self.multiworld, "generation_is_fake"):
+ if self.options.shuffle_door_requirements:
+ shuffle_door_unlock_items(self)
+ else:
+ place_vanilla_door_unlock_items(self)
+
+ place_door_unlock_items(self)
+
+ def set_rules(self) -> None:
+ set_rules(self)
+
+ def generate_basic(self) -> None:
+ self.set_track_versions()
+ self.set_music()
+
+ def pre_fill(self) -> None:
+ if self.is_ffl_unused():
+ future_fun_land_balloon = self.create_item(ItemName.FUTURE_FUN_LAND_BALLOON)
+ for ffl_exit in self.multiworld.get_region(RegionName.FUTURE_FUN_LAND, self.player).exits:
+ if ffl_exit.connected_region.name != RegionName.TIMBERS_ISLAND:
+ for ffl_location in ffl_exit.connected_region.locations:
+ self.place_locked_item(ffl_location.name, future_fun_land_balloon)
+
+ if not self.options.shuffle_wizpig_amulet:
+ wizpig_amulet_item = self.create_item(ItemName.WIZPIG_AMULET_PIECE)
+ self.place_locked_item(LocationName.TRICKY_2, wizpig_amulet_item)
+ self.place_locked_item(LocationName.BLUEY_2, wizpig_amulet_item)
+ self.place_locked_item(LocationName.BUBBLER_2, wizpig_amulet_item)
+ self.place_locked_item(LocationName.SMOKEY_2, wizpig_amulet_item)
+
+ if not self.options.shuffle_tt_amulet:
+ tt_amulet_item = self.create_item(ItemName.TT_AMULET_PIECE)
+ self.place_locked_item(LocationName.FIRE_MOUNTAIN, tt_amulet_item)
+ self.place_locked_item(LocationName.ICICLE_PYRAMID, tt_amulet_item)
+ self.place_locked_item(LocationName.DARKWATER_BEACH, tt_amulet_item)
+ self.place_locked_item(LocationName.SMOKEY_CASTLE, tt_amulet_item)
+
+ def fill_slot_data(self) -> dict[str, Any]:
+ dkr_options: dict[str, Any] = {
+ "apworld_version": self.apworld_version,
+ "player_name": self.multiworld.player_name[self.player],
+ "seed": self.random.randint(12212, 69996),
+ "victory_condition": self.options.victory_condition.value,
+ "shuffle_wizpig_amulet": "true" if self.options.shuffle_wizpig_amulet else "false",
+ "shuffle_tt_amulet": "true" if self.options.shuffle_tt_amulet else "false",
+ "open_worlds": "true" if self.options.open_worlds else "false",
+ "door_requirement_progression": self.options.door_requirement_progression.value,
+ "maximum_door_requirement": self.options.maximum_door_requirement.value,
+ "shuffle_door_requirements": "true" if self.options.shuffle_door_requirements.value else "false",
+ "door_unlock_requirements": self.door_unlock_requirements,
+ "shuffle_race_entrances": "true" if self.options.shuffle_race_entrances else "false",
+ "entrance_order": self.entrance_order,
+ "boss_1_regional_balloons": self.options.boss_1_regional_balloons.value,
+ "boss_2_regional_balloons": self.options.boss_2_regional_balloons.value,
+ "wizpig_1_amulet_pieces": self.options.wizpig_1_amulet_pieces.value,
+ "wizpig_2_amulet_pieces": self.options.wizpig_2_amulet_pieces.value,
+ "wizpig_2_balloons": self.options.wizpig_2_balloons.value,
+ "randomize_character_on_map_change": "true" if self.options.randomize_character_on_map_change else "false",
+ "track_versions": self.track_versions,
+ "music": self.music,
+ "power_up_balloon_type": self.options.power_up_balloon_type.value,
+ "skip_trophy_races": "true" if self.options.skip_trophy_races else "false"
+ }
+
+ return dkr_options
+
+ def is_item_pre_filled(self, item_name: str) -> bool:
+ if self.is_ffl_unused() and item_name == ItemName.FUTURE_FUN_LAND_BALLOON:
+ return True
+
+ if not self.options.shuffle_wizpig_amulet and item_name == ItemName.WIZPIG_AMULET_PIECE:
+ return True
+
+ if not self.options.shuffle_tt_amulet and item_name == ItemName.TT_AMULET_PIECE:
+ return True
+
+ return False
+
+ def create_item(self, item_name: str) -> Item:
+ item = ALL_ITEM_TABLE.get(item_name)
+
+ item_classification = ItemClassification.progression
+ if self.options.victory_condition.value != 1:
+ if item_name == ItemName.TT_AMULET_PIECE:
+ item_classification = ItemClassification.filler
+
+ created_item = DiddyKongRacingItem(
+ self.item_id_to_name[item.dkr_id],
+ item_classification,
+ item.dkr_id,
+ self.player
+ )
+
+ return created_item
+
+ def create_event_item(self, name: str) -> Item:
+ created_item = DiddyKongRacingItem(name, ItemClassification.progression, None, self.player)
+
+ return created_item
+
+ def place_locked_item(self, location_name: str, item: Item) -> None:
+ self.multiworld.get_location(location_name, self.player).place_locked_item(item)
+
+ def is_ffl_unused(self) -> bool:
+ return self.options.victory_condition.value == 0 and not self.options.open_worlds
+
+ def set_track_versions(self) -> None:
+ num_tracks = 20
+ if self.options.track_version.value == 0:
+ self.track_versions = [False] * num_tracks
+ elif self.options.track_version.value == 1:
+ self.track_versions = [True] * num_tracks
+ else:
+ self.track_versions = []
+ for _ in range(num_tracks):
+ self.track_versions.append(bool(self.random.getrandbits(1)))
+
+ def set_music(self) -> None:
+ if self.options.randomize_music.value:
+ self.random.shuffle(self.music)
+
+ # For Universal Tracker
+ def interpret_slot_data(self, slot_data: dict[str, Any]) -> None:
+ self.entrance_order = slot_data["entrance_order"]
+ connect_track_regions(self)
+ self.door_unlock_requirements = slot_data["door_unlock_requirements"]
+ place_door_unlock_items(self)
+ set_region_access_rules(self)
+ set_door_unlock_rules(self)
+ set_race_2_location_rules(self)
+
+ # For Universal Tracker
+ def reconnect_found_entrances(self, found_key: str, data_storage_value: Any) -> None:
+ if data_storage_value:
+ reconnect_found_entrance(self, found_key)
diff --git a/worlds/diddy_kong_racing/archipelago.json b/worlds/diddy_kong_racing/archipelago.json
new file mode 100644
index 000000000000..cf3125d91f8a
--- /dev/null
+++ b/worlds/diddy_kong_racing/archipelago.json
@@ -0,0 +1 @@
+{"game": "Diddy Kong Racing", "authors": ["zakwiz", "jjjj12212"], "minimum_ap_version": "0.6.3", "world_version": "1.1.1", "compatible_version": 7, "version": 7}
\ No newline at end of file
diff --git a/worlds/diddy_kong_racing/docs/en_Diddy Kong Racing.md b/worlds/diddy_kong_racing/docs/en_Diddy Kong Racing.md
index 11a02fd42276..107a4c7df71b 100644
--- a/worlds/diddy_kong_racing/docs/en_Diddy Kong Racing.md
+++ b/worlds/diddy_kong_racing/docs/en_Diddy Kong Racing.md
@@ -1,39 +1,39 @@
-# Diddy Kong Racing
-
-## Where is the settings page?
-
-The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
-config file.
-
-## What does randomization do to this game?
-
-Randomizes items, allows changing door requirements, allows shuffling the player character on every map change, and
-allows shuffling power-up balloons
-
-## What items and locations get shuffled?
-
-- Golden balloons
-- Keys
-- Wizpig amulet pieces
-- T.T. amulet pieces
-
-## When the player receives an item, what happens?
-
-When the player receives an item, it will be displayed on the client.
-
-## Unique local commands
-
-The following commands are available when using the Diddy Kong Racing Client to play with Archipelago:
-
-- `/n64` Check N64 Connection State
-
-## Known quirks
-
-- If you beat the first boss race in a region before completing all the race 1's for that region, after completing a
- race 1, if you don't have enough balloons to enter race 2, you will be stuck inside the locked door. To get around
- this, you can go back into the race, pause, and return to lobby.
-- If you meet the regional balloon requirement to unlock the second boss race for a region before winning first boss
- race of that region, you will skip to the second boss race (this is to avoid a glitch).
-- Sometimes the incorrect boss cutscene plays at the start of the second boss race
-- Amulet cutscenes show you having 1 more piece than you actually have, unless the item in that location was vanilla.
-- Collected keys reappear when reloading a save file.
+# Diddy Kong Racing
+
+## Where is the settings page?
+
+The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
+config file.
+
+## What does randomization do to this game?
+
+Randomizes items, allows changing door requirements, allows shuffling the player character on every map change, and
+allows shuffling power-up balloons
+
+## What items and locations get shuffled?
+
+- Golden balloons
+- Keys
+- Wizpig amulet pieces
+- T.T. amulet pieces
+
+## When the player receives an item, what happens?
+
+When the player receives an item, it will be displayed on the client.
+
+## Unique local commands
+
+The following commands are available when using the Diddy Kong Racing Client to play with Archipelago:
+
+- `/n64` Check N64 Connection State
+
+## Known quirks
+
+- If you beat the first boss race in a region before completing all the race 1's for that region, after completing a
+ race 1, if you don't have enough balloons to enter race 2, you will be stuck inside the locked door. To get around
+ this, you can go back into the race, pause, and return to lobby.
+- If you meet the regional balloon requirement to unlock the second boss race for a region before winning first boss
+ race of that region, you will skip to the second boss race (this is to avoid a glitch).
+- Sometimes the incorrect boss cutscene plays at the start of the second boss race
+- Amulet cutscenes show you having 1 more piece than you actually have, unless the item in that location was vanilla.
+- Collected keys reappear when reloading a save file.
diff --git a/worlds/diddy_kong_racing/docs/setup_en.md b/worlds/diddy_kong_racing/docs/setup_en.md
index 207691487d7f..6c74eb8fac59 100644
--- a/worlds/diddy_kong_racing/docs/setup_en.md
+++ b/worlds/diddy_kong_racing/docs/setup_en.md
@@ -1,57 +1,57 @@
-# Setup Guide For Diddy Kong Racing Archipelago
-
-## Important
-
-As we are using BizHawk, this guide is only applicable to Windows and Linux systems.
-
-## Required Software/Files
-
-- BizHawk: [BizHawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
- - Only version 2.10 is supported.
- - Detailed installation instructions for BizHawk can be found at the above link.
- - Windows users must run the prereq installer first, which can also be found at the above link.
-- Grab the latest release from https://github.com/zakwiz/DiddyKongRacingAP
-- A Diddy Kong Racing v1.0 ROM (USA ONLY).
-
-## Configuring BizHawk
-
-Once BizHawk has been installed, open EmuHawk and change the following settings:
-
-- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to
- continue playing in the background, even if another window is selected.
-- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want
- to disable most of these, which you can do quickly using `Esc`.
-- If playing with a controller, when you bind controls, disable "P1 A Up", "P1 A Down", "P1 A Left", and "P1 A Right" as
- these interfere with aiming if bound. Set directional input using the Analog tab instead.
-- Under N64 enable "Use Expansion Slot". (The N64 menu only appears after loading a ROM.)
-
-It is strongly recommended to associate N64 rom extensions (*.n64, *.z64) to the EmuHawk we've just installed. To do so,
-we simply have to search any N64 rom we happened to own, right click and select "Open with…", unfold the list that
-appears and select the bottom option "Look for another application", then browse to the BizHawk folder and select
-EmuHawk.exe.
-
-## How To Install - Server Side
-
-Double-click `diddy_kong_racing.apworld` to install it into the `custom_worlds` folder of your Archipelago install.
-
-## How To Install - Client Side
-
-- Double-click `diddy_kong_racing.apworld` to install it into the `custom_worlds` folder of your Archipelago install.
-- Move `connector_diddy_kong_racing.lua` into the `data/lua` folder of your Archipelago install.
-- Run the Archipelago launcher and select Diddy Kong Racing Client.
-- The client will prompt you to select your ROM so it can be patched.
-- Once the patching is complete, connect the Diddy Kong Racing client to the server by clicking the Connect button.
-- Open Bizhawk (version 2.10 required) and open your patched Diddy Kong Racing ROM (it should be in the top-level folder
- of your Archipelago install).
-- Run the `connector_diddy_kong_racing.lua` script from the `data/lua` folder of your Archipelago install (drag and drop
- it into Bizhawk).
-
-## Generating Your World
-
-Familiarize yourself on how Archipelago works. Here is a guide to learn how to generate your
-world: https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game
-
-## Connect To The Multiserver
-
-To connect the client to the multiserver, simply put `:` in the textfield on top and press `connect` (if
-the server uses password, then it will prompt after connection).
+# Setup Guide For Diddy Kong Racing Archipelago
+
+## Important
+
+As we are using BizHawk, this guide is only applicable to Windows and Linux systems.
+
+## Required Software/Files
+
+- BizHawk: [BizHawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
+ - Only version 2.10 is supported.
+ - Detailed installation instructions for BizHawk can be found at the above link.
+ - Windows users must run the prereq installer first, which can also be found at the above link.
+- Grab the latest release from https://github.com/zakwiz/DiddyKongRacingAP
+- A Diddy Kong Racing v1.0 ROM (USA ONLY).
+
+## Configuring BizHawk
+
+Once BizHawk has been installed, open EmuHawk and change the following settings:
+
+- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to
+ continue playing in the background, even if another window is selected.
+- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want
+ to disable most of these, which you can do quickly using `Esc`.
+- If playing with a controller, when you bind controls, disable "P1 A Up", "P1 A Down", "P1 A Left", and "P1 A Right" as
+ these interfere with aiming if bound. Set directional input using the Analog tab instead.
+- Under N64 enable "Use Expansion Slot". (The N64 menu only appears after loading a ROM.)
+
+It is strongly recommended to associate N64 rom extensions (*.n64, *.z64) to the EmuHawk we've just installed. To do so,
+we simply have to search any N64 rom we happened to own, right click and select "Open with…", unfold the list that
+appears and select the bottom option "Look for another application", then browse to the BizHawk folder and select
+EmuHawk.exe.
+
+## How To Install - Server Side
+
+Double-click `diddy_kong_racing.apworld` to install it into the `custom_worlds` folder of your Archipelago install.
+
+## How To Install - Client Side
+
+- Double-click `diddy_kong_racing.apworld` to install it into the `custom_worlds` folder of your Archipelago install.
+- Move `connector_diddy_kong_racing.lua` into the `data/lua` folder of your Archipelago install.
+- Run the Archipelago launcher and select Diddy Kong Racing Client.
+- The client will prompt you to select your ROM so it can be patched.
+- Once the patching is complete, connect the Diddy Kong Racing client to the server by clicking the Connect button.
+- Open Bizhawk (version 2.10 required) and open your patched Diddy Kong Racing ROM (it should be in the top-level folder
+ of your Archipelago install).
+- Run the `connector_diddy_kong_racing.lua` script from the `data/lua` folder of your Archipelago install (drag and drop
+ it into Bizhawk).
+
+## Generating Your World
+
+Familiarize yourself on how Archipelago works. Here is a guide to learn how to generate your
+world: https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game
+
+## Connect To The Multiserver
+
+To connect the client to the multiserver, simply put `:` in the textfield on top and press `connect` (if
+the server uses password, then it will prompt after connection).
diff --git a/worlds/dk64/ap_version.py b/worlds/dk64/ap_version.py
index 0a135d21efa6..fdcac8c9e4b7 100644
--- a/worlds/dk64/ap_version.py
+++ b/worlds/dk64/ap_version.py
@@ -1,3 +1,3 @@
"""Holds the version for Archipelago."""
-version = "1.0.55"
+version = "1.0.56"
diff --git a/worlds/dk64/archipelago/DK64Client.py b/worlds/dk64/archipelago/DK64Client.py
index b752d56a2232..6dbedc9f66d0 100644
--- a/worlds/dk64/archipelago/DK64Client.py
+++ b/worlds/dk64/archipelago/DK64Client.py
@@ -752,6 +752,22 @@ def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.game = self.slot_info[self.slot].game
self.slot_data = args.get("slot_data", {})
+ if self.slot_data.get("Version"):
+ server_ver = self.slot_data.get("Version")
+ server_major = server_ver.split(".")[0]
+ server_minor = server_ver.split(".")[1]
+ server_patch = server_ver.split(".")[2]
+ # Get the current version from the ap_version.py file
+ ap_major = ap_version.split(".")[0]
+ ap_minor = ap_version.split(".")[1]
+ ap_patch = ap_version.split(".")[2]
+ if server_major != ap_major or server_minor != ap_minor:
+ logger.error("Your DK64 APworld does not match with the generated world.")
+ logger.error(f"Your version: {ap_version} | Generated version: {server_ver}")
+ raise Exception("Your DK64 APworld does not match with the generated world.\n" + f"Your version: {ap_version} | Generated version: {server_ver}")
+ if server_patch != ap_patch:
+ logger.warning("Your DK64 APworld does not match with the generated world, but this should not be a breaking change.")
+ logger.warning("While we try to maintain backwards compatibility on patch versions, be warned that something might break.")
if self.slot_data.get("death_link"):
if "DeathLink" not in self.tags:
create_task_log_exception(self.update_death_link(True))
diff --git a/worlds/dk64/docs/setup_en.md b/worlds/dk64/docs/setup_en.md
index 134db6bc3e87..e92d7f0b2c92 100644
--- a/worlds/dk64/docs/setup_en.md
+++ b/worlds/dk64/docs/setup_en.md
@@ -1,49 +1,49 @@
-Archipelago is a way to randomize several games together into one cohesive experience. The items from all games are placed into a pool and distributed out to all games in that session. This means you might find someone else's grenade eggs in a cage in DK64, and someone else might find the bean in Water Temple for your seed.
-# Requirements list
-- [Project64 4.0](./Consoles-and-Emulators:-Project-64-4.0)
-- [Archipelago 0.6+](https://github.com/ArchipelagoMW/Archipelago/releases)
-- [APWorld](https://dev.dk64randomizer.com/dk64.apworld)
-
-# Resources
-
-## Poptracker
-Developers: UmedMuzl
-| Download: [Link](https://github.com/AmedMusl/dk64pt)
-
-# Tutorials
-**Settings Tutorial by Keiper**
-[](https://www.youtube.com/watch?v=xb8QsapzdWg)
-# Setup Guide
-## Installation Procedures
-1. Download and install the Archipelago Multiworld Suite from the link above, making sure to install the most recent version.
-2. Download and install the Project 64 4.0 Emulator from the link above, making sure to install the most recent version. Run the emulator at least once to make sure it is working.
-3. Download the [APworld file](https://dev.dk64randomizer.com/dk64.apworld).
-4. Using the AP launcher, hit "Install AP World" and point it to the dk64.apworld file you downloaded.
- **NOTE: We HIGHLY suggest you run Archipelago as Administrator for the first time run of it, it helps automatically set up your Project 64 Installation.**
-## Create a Config (.yaml) File
-### What is a config file and why do I need one?
-Your config 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 config 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 config file?
-Run the ArchipelagoLauncher.exe from your Archipelago install and click `Generate Template Options` This will produce a `/Players/Templates` folder in your Archipelago install, which contains default config files for every game in your `custom_worlds` folder. You can manually edit the config file using a text editor of your choice.
-## Generating a Seed and Setting up the Client
-1. After modifying your yaml, place it into your Archipelago/player folder
-2. Open the Archipelago Launcher and click "Generate". This will create a zip file in Archipelago output
- - You will need to open this .zip to get your `.lanky` patch file
-3. Navigate to the Archipelago website and go to the Host Game page
-4. Click upload file and pass it the .zip created in your output folder
-5. Click the "Create New Room" link.
-6. Go to the [DK64 Randomizer](https://dev.dk64randomizer.com) site and click on 'Generate from Patch File'. Load in your DK64 ROM and select your '.lanky' file. You can also select your cosmetics before clicking `Generate Seed`. Once you are ready, click on `Generate Seed`
- - The patch will be placed in the same folder as your patch file by default.
-7. Open the `DK64 Randomizer` client in the AP window
- - This will load `adapter.js` into your emulator folder on first time launch for ease of use
-8. Once you have loaded into the game, click the `Connect` button at the top of the DK64 Client. You are now connected and ready to play!
-## Joining a MultiWorld Game
-### Obtain your patch file and create your ROM
-When you join a multiworld game, you will be asked to provide your config file to whoever is hosting Once that is done, the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch files. Your patch file should have a `.lanky` extension.
-Put your patch file on your desktop or somewhere convenient. Open the DK64 Randomizer site as mentioned above, and Generate your seed.
-### Connect to the client
-1. When you launch the client, go to Project64 and run the `adapter.js` script in the emulator.
-2. In the server page, there will be a port number. Copy this port number into the top of your DK64 Client.
- - The field should read `archipelago.gg:`
-3. Click the `Connect` button at the top of the LMClient. Once you have loaded into the game, the client should log that Project64 has been connected. you are now connected and ready to play!
+Archipelago is a way to randomize several games together into one cohesive experience. The items from all games are placed into a pool and distributed out to all games in that session. This means you might find someone else's grenade eggs in a cage in DK64, and someone else might find the bean in Water Temple for your seed.
+# Requirements list
+- [Project64 4.0](./Consoles-and-Emulators:-Project-64-4.0)
+- [Archipelago 0.6+](https://github.com/ArchipelagoMW/Archipelago/releases)
+- [APWorld](https://dev.dk64randomizer.com/dk64.apworld)
+
+# Resources
+
+## Poptracker
+Developers: UmedMuzl
+| Download: [Link](https://github.com/AmedMusl/dk64pt)
+
+# Tutorials
+**Settings Tutorial by Keiper**
+[](https://www.youtube.com/watch?v=xb8QsapzdWg)
+# Setup Guide
+## Installation Procedures
+1. Download and install the Archipelago Multiworld Suite from the link above, making sure to install the most recent version.
+2. Download and install the Project 64 4.0 Emulator from the link above, making sure to install the most recent version. Run the emulator at least once to make sure it is working.
+3. Download the [APworld file](https://dev.dk64randomizer.com/dk64.apworld).
+4. Using the AP launcher, hit "Install AP World" and point it to the dk64.apworld file you downloaded.
+ **NOTE: We HIGHLY suggest you run Archipelago as Administrator for the first time run of it, it helps automatically set up your Project 64 Installation.**
+## Create a Config (.yaml) File
+### What is a config file and why do I need one?
+Your config 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 config 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 config file?
+Run the ArchipelagoLauncher.exe from your Archipelago install and click `Generate Template Options` This will produce a `/Players/Templates` folder in your Archipelago install, which contains default config files for every game in your `custom_worlds` folder. You can manually edit the config file using a text editor of your choice.
+## Generating a Seed and Setting up the Client
+1. After modifying your yaml, place it into your Archipelago/player folder
+2. Open the Archipelago Launcher and click "Generate". This will create a zip file in Archipelago output
+ - You will need to open this .zip to get your `.lanky` patch file
+3. Navigate to the Archipelago website and go to the Host Game page
+4. Click upload file and pass it the .zip created in your output folder
+5. Click the "Create New Room" link.
+6. Go to the [DK64 Randomizer](https://dev.dk64randomizer.com) site and click on 'Generate from Patch File'. Load in your DK64 ROM and select your '.lanky' file. You can also select your cosmetics before clicking `Generate Seed`. Once you are ready, click on `Generate Seed`
+ - The patch will be placed in the same folder as your patch file by default.
+7. Open the `DK64 Randomizer` client in the AP window
+ - This will load `adapter.js` into your emulator folder on first time launch for ease of use
+8. Once you have loaded into the game, click the `Connect` button at the top of the DK64 Client. You are now connected and ready to play!
+## Joining a MultiWorld Game
+### Obtain your patch file and create your ROM
+When you join a multiworld game, you will be asked to provide your config file to whoever is hosting Once that is done, the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch files. Your patch file should have a `.lanky` extension.
+Put your patch file on your desktop or somewhere convenient. Open the DK64 Randomizer site as mentioned above, and Generate your seed.
+### Connect to the client
+1. When you launch the client, go to Project64 and run the `adapter.js` script in the emulator.
+2. In the server page, there will be a port number. Copy this port number into the top of your DK64 Client.
+ - The field should read `archipelago.gg:`
+3. Click the `Connect` button at the top of the LMClient. Once you have loaded into the game, the client should log that Project64 has been connected. you are now connected and ready to play!
You will know everything is working when you see `Archipelago Connected` on the save file
\ No newline at end of file
diff --git a/worlds/dk64/vendor/linux.zip b/worlds/dk64/vendor/linux.zip
index 5da87951f76e..4bedfb3da91e 100644
Binary files a/worlds/dk64/vendor/linux.zip and b/worlds/dk64/vendor/linux.zip differ
diff --git a/worlds/dk64/vendor/windows.zip b/worlds/dk64/vendor/windows.zip
index 8a750d60c9f8..11dfe0821b10 100644
Binary files a/worlds/dk64/vendor/windows.zip and b/worlds/dk64/vendor/windows.zip differ
diff --git a/worlds/dkc2/__init__.py b/worlds/dkc2/__init__.py
index 1de73825271c..94ba59e9c78a 100644
--- a/worlds/dkc2/__init__.py
+++ b/worlds/dkc2/__init__.py
@@ -79,7 +79,6 @@ class DKC2World(World):
# borrowed from DKC3's description
game = "Donkey Kong Country 2"
web = DKC2Web()
-
settings: ClassVar[DKC2Settings]
options_dataclass = DKC2Options
diff --git a/worlds/earthbound/Client.py b/worlds/earthbound/Client.py
index 461d12153c36..3b0b690fdb44 100644
--- a/worlds/earthbound/Client.py
+++ b/worlds/earthbound/Client.py
@@ -192,10 +192,12 @@ async def game_watcher(self, ctx: "SNIContext") -> None:
if outgoing_energy is None: #None Catcher
return
- await ctx.send_msgs([{
- "cmd": "Get",
- "keys": [f"GiftBoxes;{ctx.team}"]
- }])
+
+ if f"GiftBoxes;{ctx.team}" not in ctx.stored_data:
+ await ctx.send_msgs([{
+ "cmd": "SetNotify",
+ "keys": [f"GiftBoxes;{ctx.team}"]
+ }])
# GIFTING DATA
if f"GiftBox;{ctx.team};{ctx.slot}" not in ctx.stored_data:
@@ -219,10 +221,10 @@ async def game_watcher(self, ctx: "SNIContext") -> None:
"keys": [f"GiftBox;{ctx.team};{ctx.slot}"]
}])
- await ctx.send_msgs([{
- "cmd": "SetNotify",
- "keys": [f"GiftBox;{ctx.team};{ctx.slot}", f"GiftBoxes;{ctx.team}"]
- }])
+ await ctx.send_msgs([{
+ "cmd": "SetNotify",
+ "keys": [f"GiftBox;{ctx.team};{ctx.slot}", f"GiftBoxes;{ctx.team}"]
+ }])
inbox = ctx.stored_data.get(f"GiftBox;{ctx.team};{ctx.slot}")
motherbox = ctx.stored_data.get(f"GiftBoxes;{ctx.team}")
@@ -337,7 +339,7 @@ async def game_watcher(self, ctx: "SNIContext") -> None:
outbox_full_byte = outbox_full_byte[0] & ~0x08
await snes_write(ctx, [(WRAM_START + 0xB622, bytes([outbox_full_byte]))])
- if game_clear[0] & 0x01 == 0x01: # Goal should ignore the item queue and textbox check
+ if (game_clear[0] & 0x01 == 0x01) and not ctx.finished_game: # Goal should ignore the item queue and textbox check
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
@@ -376,21 +378,25 @@ async def game_watcher(self, ctx: "SNIContext") -> None:
if prog_shops:
await ctx.send_msgs([{"cmd": "CreateHints", "locations": prog_shops, "player": ctx.slot}])
- await ctx.send_msgs([{
- "cmd": "Set",
- "key": f"{ctx.team}_{ctx.slot}_melody_status",
- "default": None,
- "want_reply": True,
- "operations": [{"operation": "replace", "value": int.from_bytes(melody_table, "little")}]
- }])
-
- await ctx.send_msgs([{
- "cmd": "Set",
- "key": f"{ctx.team}_{ctx.slot}_earthpower",
- "default": None,
- "want_reply": True,
- "operations": [{"operation": "replace", "value": int.from_bytes(earth_power_absorbed, "little")}]
- }])
+ melody_data = f"{ctx.team}_{ctx.slot}_melody_status"
+ earth_power_data = f"{ctx.team}_{ctx.slot}_earthpower"
+ current_melodies = int.from_bytes(melody_table, "little")
+ earth_power_state = int.from_bytes(earth_power_absorbed, "little")
+
+ if melody_data not in ctx.stored_data or (ctx.stored_data[melody_data] != current_melodies) or (ctx.stored_data[earth_power_data] != earth_power_state):
+ await ctx.send_msgs([{
+ "cmd": "Set",
+ "key": melody_data,
+ "default": None,
+ "want_reply": True,
+ "operations": [{"operation": "replace", "value": int.from_bytes(melody_table, "little")}]},
+ {
+ "cmd": "Set",
+ "key": earth_power_data,
+ "default": None,
+ "want_reply": True,
+ "operations": [{"operation": "replace", "value": int.from_bytes(earth_power_absorbed, "little")}]
+ }])
# death link handling goes here
if "DeathLink" in ctx.tags and ctx.last_death_link + 1 < time.time():
diff --git a/worlds/earthbound/Options.py b/worlds/earthbound/Options.py
index 09e79d5f63d3..7a7b120f8a47 100644
--- a/worlds/earthbound/Options.py
+++ b/worlds/earthbound/Options.py
@@ -1,6 +1,7 @@
from dataclasses import dataclass
from Options import (Toggle, DefaultOnToggle, DeathLink, Choice, Range, PerGameCommonOptions, StartInventoryPool,
- OptionGroup, FreeText, Visibility)
+ OptionGroup, FreeText, Visibility, PlandoBosses)
+from .modules.boss_shuffle import boss_plando_keys
class GiygasRequired(DefaultOnToggle):
@@ -90,10 +91,21 @@ class PSIShuffle(Choice):
option_extended = 2
-class BossShuffle(Toggle):
+class BossShuffle(PlandoBosses):
"""Shuffles boss encounters amongst each other."""
display_name = "Boss Shuffle"
+ option_false = 0
+ option_true = 1
+ default = 0
+ bosses = boss_plando_keys
+ locations = boss_plando_keys
+ duplicate_bosses = False
+
+ @classmethod
+ def can_place_boss(cls, boss: str, location: str) -> bool:
+ return True
+
class DecoupleDiamondDog(Toggle):
"""Shuffles Diamond Dog as a boss separate from Carbon Dog. Carbon Dog will transform into a random boss.
diff --git a/worlds/earthbound/Regions.py b/worlds/earthbound/Regions.py
index cc51d20c0d89..dfa799221d1d 100644
--- a/worlds/earthbound/Regions.py
+++ b/worlds/earthbound/Regions.py
@@ -8,10 +8,6 @@
class EBLocation(Location):
game: str = "EarthBound"
- region: str
-
- def __init__(self, player: int, name: str = " ", address: int | None = None, parent: Region | None = None) -> None:
- super().__init__(player, name, address, parent)
def init_areas(world: "EarthBoundWorld", locations: list[LocationData]) -> None:
@@ -233,7 +229,6 @@ def connect_area_exits(world: "EarthBoundWorld"):
def create_location(player: int, location_data: LocationData, region: Region) -> Location:
location = EBLocation(player, location_data.name, location_data.code, region)
- location.region = location_data.region
return location
diff --git a/worlds/earthbound/Rom.py b/worlds/earthbound/Rom.py
index d4d378eea3e3..d283607f2610 100644
--- a/worlds/earthbound/Rom.py
+++ b/worlds/earthbound/Rom.py
@@ -152,7 +152,7 @@ def patch_rom(world: "EarthBoundWorld", rom: LocalRom, player: int) -> None:
rom.write_bytes(0x01FE8B, bytearray(starting_area_coordinates[world.start_location][2:4])) # Respawn position
if world.options.skip_epilogue:
- rom.write_bytes(0x2EA42F, struct.pack("I", 0xEEA432))
+ rom.write_bytes(0x09C4D4, struct.pack("I", 0xEEA437))
if world.starting_character == "Poo":
rom.write_bytes(starting_levels[world.starting_character], bytearray([0x06]))
diff --git a/worlds/earthbound/Rules.py b/worlds/earthbound/Rules.py
index 71feba8c4ae6..57f60b1cf3b7 100644
--- a/worlds/earthbound/Rules.py
+++ b/worlds/earthbound/Rules.py
@@ -96,10 +96,10 @@ def set_location_rules(world: "EarthBoundWorld") -> None:
set_rule(world.multiworld.get_location("Dusty Dunes - Mine Food Cart Slot 6", player), lambda state: state.has("Mining Permit", player))
set_rule(world.multiworld.get_location("Dusty Dunes - Mine Food Cart Slot 7", player), lambda state: state.has("Mining Permit", player))
- set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 1", player), lambda state: state.has("Jar of Fly Honey", player))
- set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 2", player), lambda state: state.has("Jar of Fly Honey", player))
- set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 3", player), lambda state: state.has("Jar of Fly Honey", player))
- set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 4", player), lambda state: state.has("Jar of Fly Honey", player))
+ set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 1", player), lambda state: state.has("Threed Tunnels Clear", player))
+ set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 2", player), lambda state: state.has("Threed Tunnels Clear", player))
+ set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 3", player), lambda state: state.has("Threed Tunnels Clear", player))
+ set_rule(world.multiworld.get_location("Saturn Valley Shop - Post-Belch Saturn Slot 4", player), lambda state: state.has("Threed Tunnels Clear", player))
set_rule(world.multiworld.get_location("Deep Darkness - Arms Dealer Slot 1", player), lambda state: state.has("ATM Access", player))
set_rule(world.multiworld.get_location("Deep Darkness - Arms Dealer Slot 2", player), lambda state: state.has("ATM Access", player))
@@ -125,3 +125,4 @@ def set_location_rules(world: "EarthBoundWorld") -> None:
set_rule(world.multiworld.get_location("Dalaam Restaurant - Slot 2", player), lambda state: state.has("ATM Access", player))
set_rule(world.multiworld.get_location("Dalaam Restaurant - Slot 3", player), lambda state: state.has("ATM Access", player))
set_rule(world.multiworld.get_location("Dalaam Restaurant - Slot 4", player), lambda state: state.has("ATM Access", player))
+
\ No newline at end of file
diff --git a/worlds/earthbound/__init__.py b/worlds/earthbound/__init__.py
index adccbd5e994b..c2a98bd7dcca 100644
--- a/worlds/earthbound/__init__.py
+++ b/worlds/earthbound/__init__.py
@@ -52,11 +52,15 @@ class EBWeb(WebWorld):
["Pink Switch"]
)
+
tutorials = [setup_en]
option_groups = eb_option_groups
# option_presets = eb_option_presets
+class EBItem(Item):
+ game: str = "EarthBound"
+
class EarthBoundWorld(World):
"""EarthBound is a contemporary-themed JRPG. Take four psychically-endowed children
@@ -95,10 +99,10 @@ def __init__(self, multiworld: MultiWorld, player: int):
self.progressive_filler_bracelets: int = 0
self.progressive_filler_other: int = 0
self.world_version: str = world_version
- self.armor_list: Dict[str, EBArmor]
- self.weapon_list: Dict[str, EBWeapon]
- self.boss_slots: Dict[str, SlotInfo]
- self.boss_info: Dict[str, BossData]
+ self.armor_list = Dict[str, EBArmor]
+ self.weapon_list = Dict[str, EBWeapon]
+ self.boss_slots = Dict[str, SlotInfo]
+ self.boss_info = Dict[str, BossData]
self.starting_character: str | None = None
self.locals = []
self.rom_name = None
@@ -332,7 +336,7 @@ def pre_fill(self) -> None:
add_item_rule(self.multiworld.get_location("Dalaam - Throne Character", self.player), lambda item: item.name in self.item_name_groups["Characters"])
add_item_rule(self.multiworld.get_location("Deep Darkness - Barf Character", self.player), lambda item: item.name in self.item_name_groups["Characters"])
- fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), prefill_locations, prefill_items, True, True)
+ fill_restrictive(self.multiworld, self.multiworld.get_all_state(False, collect_pre_fill_items=False), prefill_locations, prefill_items, True, True)
setup_hints(self)
def get_pre_fill_items(self) -> list[Item]:
@@ -345,9 +349,13 @@ def get_pre_fill_items(self) -> list[Item]:
@classmethod
def stage_generate_output(cls, multiworld: MultiWorld, output_directory: str) -> None:
- multiworld.eb_spheres = list(multiworld.get_spheres())
- for world in multiworld.get_game_worlds("EarthBound"):
- world.get_all_spheres.set()
+ try:
+ multiworld.earthbound_locations_by_sphere = list(multiworld.get_spheres())
+ except Exception:
+ raise
+ finally:
+ for world in multiworld.get_game_worlds("EarthBound"):
+ world.get_all_spheres.set()
def generate_output(self, output_directory: str) -> None:
self.has_generated_output = True # Make sure data defined in generate output doesn't get added to spoiler only mode
@@ -473,9 +481,9 @@ def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
if area not in spoiler_excluded_areas:
spoiler_handle.write(f" {area}: Level {self.area_levels[area]}\n")
- def create_item(self, name: str) -> Item:
+ def create_item(self, name: str) -> EBItem:
data = item_table[name]
- return Item(name, data.classification, data.code, self.player)
+ return EBItem(name, data.classification, data.code, self.player)
def get_filler_item_name(self) -> str: # Todo: make this suck less
weights = {"rare": self.options.rare_filler_weight.value, "uncommon": self.options.uncommon_filler_weight.value, "common": self.options.common_filler_weight.value,
diff --git a/worlds/earthbound/archipelago.json b/worlds/earthbound/archipelago.json
index 17ebab907d45..326433222cba 100644
--- a/worlds/earthbound/archipelago.json
+++ b/worlds/earthbound/archipelago.json
@@ -1 +1 @@
-{"compatible_version": 7, "version": 7, "world_version": "4.2.7", "game": "EarthBound"}
\ No newline at end of file
+{"game": "EarthBound", "authors": ["Pink Switch"], "minimum_ap_version": "0.6.3", "world_version": "4.3.1", "compatible_version": 7, "version": 7}
\ No newline at end of file
diff --git a/worlds/earthbound/game_data/local_data.py b/worlds/earthbound/game_data/local_data.py
index 347b0b129aa7..8fe96e571914 100644
--- a/worlds/earthbound/game_data/local_data.py
+++ b/worlds/earthbound/game_data/local_data.py
@@ -1,4 +1,4 @@
-world_version = "4.2.7"
+world_version = "4.3.1"
check_table = {
0xEB0000: [0x11, 3],
diff --git a/worlds/earthbound/game_data/static_location_data.py b/worlds/earthbound/game_data/static_location_data.py
index 1c7721fa2329..49dc504b2777 100644
--- a/worlds/earthbound/game_data/static_location_data.py
+++ b/worlds/earthbound/game_data/static_location_data.py
@@ -837,7 +837,7 @@
"Snow Wood - Bottom Left Locker"
},
- "Snow Wood Boarding School (not lockers)": {
+ "Snow Wood Boarding School": {
"Snow Wood - Many Present Room Present #1",
"Snow Wood - Many Present Room Present #2",
"Snow Wood - Many Present Room Present #3",
@@ -846,7 +846,11 @@
"Snow Wood - Many Present Room Present #6",
"Snow Wood - Many Present Room Present #7",
"Snow Wood - Maxwell Item",
- "Snow Wood - Bedroom"
+ "Snow Wood - Bedroom",
+ "Snow Wood - Upper Right Locker",
+ "Snow Wood - Upper Left Locker",
+ "Snow Wood - Bottom Right Locker",
+ "Snow Wood - Bottom Left Locker"
},
"Winters": {
@@ -1378,7 +1382,8 @@
"Fourside Department Store - Arms Dealer Slot 2",
"Fourside Department Store - Arms Dealer Slot 3",
"Fourside Department Store - Arms Dealer Slot 4",
- "Fourside Department Store - Arms Dealer Slot 5"
+ "Fourside Department Store - Arms Dealer Slot 5",
+ "Fourside - Department Store Blackout"
},
"the Saturn Valley Shop": {
diff --git a/worlds/earthbound/modules/area_scaling.py b/worlds/earthbound/modules/area_scaling.py
index a5c9e1598d95..0ff68cb0761a 100644
--- a/worlds/earthbound/modules/area_scaling.py
+++ b/worlds/earthbound/modules/area_scaling.py
@@ -462,7 +462,7 @@ def calculate_scaling(world: "EarthBoundWorld") -> None:
if item.name == "Franklin Badge":
badge_scaled = True
- for num, sphere in enumerate(world.multiworld.eb_spheres):
+ for num, sphere in enumerate(world.multiworld.earthbound_locations_by_sphere):
if num + 1 not in inventory:
inventory[num + 1] = []
diff --git a/worlds/earthbound/modules/boss_shuffle.py b/worlds/earthbound/modules/boss_shuffle.py
index fcb4a221fbcb..44bbf7c38d9d 100644
--- a/worlds/earthbound/modules/boss_shuffle.py
+++ b/worlds/earthbound/modules/boss_shuffle.py
@@ -1,4 +1,5 @@
from typing import NamedTuple, TYPE_CHECKING
+from logging import warning
import struct
if TYPE_CHECKING:
from .. import EarthBoundWorld
@@ -34,12 +35,77 @@
"Heavily Armed Pokey": 0xEF49AA,
"Starman Junior": 0xEF3A59,
"Diamond Dog": 0xEF3D08,
- "Giygas (4)": 0xEF40F2
+ "Giygas": 0xEF40F2
}
+boss_plando_keys = {
+ "Frank",
+ "Frankystein Mark II",
+ "Frankystein",
+ "Captain Strong",
+ "Strong",
+ "Everdred",
+ "Mr. Carpainter",
+ "Mr Carpainter",
+ "Carpainter",
+ "Mondo Mole",
+ "Boogey Tent",
+ "Mini Barf",
+ "Master Belch",
+ "Belch",
+ "Trillionage Sprout",
+ "Guardian Digger",
+ "Dept. Store Spook",
+ "Dept Store Spook",
+ "Evil Mani Mani",
+ "Mani Mani",
+ "Clumsy Robot",
+ "Shrooom!",
+ "Shrooom",
+ "Shroom!",
+ "Shroooooom!",
+ "Shroom",
+ "Plague Rat of Doom",
+ "Thunder and Storm",
+ "Kraken",
+ "The Kraken",
+ "Guardian General",
+ "Master Barf",
+ "Starman Deluxe",
+ "Starman DX",
+ "Electro Specter",
+ "Carbon Dog",
+ "Ness's Nightmare",
+ "Nesss Nightmare",
+ "Heavily Armed Pokey",
+ "Pokey"
+ "Starman Junior",
+ "Diamond Dog",
+ "Giygas"
+}
+
+boss_typo_key = {
+ "Frankystein": "Frankystein Mark II",
+ "Strong": "Captain Strong",
+ "Mr Carpainter": "Mr. Carpainter",
+ "Carpainter": "Mr. Carpainter",
+ "Belch": "Master Belch",
+ "Dept Store Spook": "Dept. Store Spook",
+ "Evil Mani Mani": "Evil Mani-Mani",
+ "Mani Mani": "Evil Mani-Mani",
+ "Shroom": "Shrooom!",
+ "Shrooom": "Shrooom!",
+ "Shroom!": "Shrooom!",
+ "Shroooooom!": "Shrooom!",
+ "The Kraken": "Kraken",
+ "Starman DX": "Starman Deluxe",
+ "Nesss Nightmare": "Ness's Nightmare",
+ "Pokey": "Heavily Armed Pokey"
+}
+
banned_transformations = ["Master Belch", "Master Barf", "Kraken", "Heavily Armed Pokey"]
-hard_final_bosses = ["Carbon Dog", "Kraken", "Clumsy Robot", "Starman Junior", "Starman Deluxe", "Giygas (4)", "Thunder and Storm", "Electro Specter",
+hard_final_bosses = ["Carbon Dog", "Kraken", "Clumsy Robot", "Starman Junior", "Starman Deluxe", "Giygas", "Thunder and Storm", "Electro Specter",
"Evil Mani-Mani", "Ness's Nightmare", "Shrooom!", "Master Belch"]
@@ -60,6 +126,8 @@ class BossData(NamedTuple):
def initialize_bosses(world: "EarthBoundWorld") -> None:
+ from ..Options import BossShuffle
+
world.boss_list = [
"Frank",
"Frankystein Mark II",
@@ -89,7 +157,7 @@ def initialize_bosses(world: "EarthBoundWorld") -> None:
"Heavily Armed Pokey",
"Starman Junior",
"Diamond Dog",
- "Giygas (4)"
+ "Giygas"
]
world.boss_slots = {
@@ -100,9 +168,9 @@ def initialize_bosses(world: "EarthBoundWorld") -> None:
"Frankystein Mark II": SlotInfo([0x0F96F0], [], [0x066146, 0x06648B, 0x0664FC], [0x068406]),
"Titanic Ant": SlotInfo([], [], [], [0x06840D]),
"Captain Strong": SlotInfo([], [0x5FC2B, 0x05FCF7, 0x065F88, 0x066085],
- [0x05FC59], [0x068468]),
+ [0x05FC59, 0x3317DB], [0x068468]),
"Everdred": SlotInfo([0x0F9A64, 0x0F9FB4], [0x2EEEEA], [0x095C70], [0x06846F]),
- "Mr. Carpainter": SlotInfo([0x0FA27E],
+ "Mr. Carpainter": SlotInfo([0x0FA27E, 0x0FA5D0],
[0x0990DA, 0x0684D0],
[0x0993DB, 0x09945E, 0x099311, 0x099364, 0x098EF6, 0x099143, 0x099028,
0x0983BB, 0x09840C, 0x09835B, 0x09056F, 0x0794EC],
@@ -138,7 +206,7 @@ def initialize_bosses(world: "EarthBoundWorld") -> None:
"Heavily Armed Pokey": SlotInfo([0x09C2EC], [0x2EEEC3, 0x2EEECC], [], []),
"Starman Junior": SlotInfo([], [], [], []),
"Diamond Dog": SlotInfo([], [], [], []),
- "Giygas (4)": SlotInfo([0x09C2BF, 0x09C2E5], [0x2EF0A9], [0x2EF09F], [])
+ "Giygas": SlotInfo([0x09C2BF, 0x09C2E5], [0x2EF0A9], [0x2EF09F], [])
}
world.boss_info = {
@@ -170,45 +238,81 @@ def initialize_bosses(world: "EarthBoundWorld") -> None:
"Heavily Armed Pokey": BossData(0x01CA, 0xEEF064, 0xEEF056, 0x000E, 0xD8, 0x69),
"Starman Junior": BossData(0x012F, 0xEEF082, 0xEEF07A, 0x01DA, 0xD6, 0x94),
"Diamond Dog": BossData(0x014A, 0xEEF052, 0xEEF089, 0x01D9, 0x53, 0x61),
- "Giygas (4)": BossData(0x0172, 0xEEF095, 0xEEF095, 0x01DD, 0xDC, 0x49)
+ "Giygas": BossData(0x0172, 0xEEF095, 0xEEF095, 0x01DD, 0xDC, 0x49)
}
if world.options.skip_prayer_sequences:
# Boss shuffle sprites needs to apply to the skip prayer cleanup too
- world.boss_slots["Giygas (4)"].sprite_addrs.append(0x07B9AC)
+ world.boss_slots["Giygas"].sprite_addrs.append(0x07B9AC)
world.boss_slots["Heavily Armed Pokey"].sprite_addrs.append(0x07B9A7)
# mole/rat text
# todo; Giygas sprites/text
world.boss_slot_order = world.boss_list.copy()
+ if type(world.options.boss_shuffle.value) == str:
+ boss_plando = world.options.boss_shuffle.value.split(";")
+ shuffle_result = boss_plando.pop()
+ else:
+ boss_plando = []
+ shuffle_result = world.options.boss_shuffle.value
- if world.options.boss_shuffle:
+ if shuffle_result == "true" or shuffle_result == 1:
world.random.shuffle(world.boss_list)
if not world.options.decouple_diamond_dog:
world.boss_list.remove("Diamond Dog")
insert_index = 28 if not world.options.boss_shuffle_add_giygas else 27
world.boss_list.insert(insert_index, "Diamond Dog")
- if not world.options.boss_shuffle_add_giygas:
- world.boss_list.remove("Giygas (4)")
- world.boss_list.insert(29, "Giygas (4)")
- if world.boss_list[25] == "Carbon Dog" and world.boss_list[27] in banned_transformations:
- original_boss = world.boss_list[27]
- transformation_replacement = world.random.randint(0, 24)
- world.boss_list[27] = world.boss_list[transformation_replacement]
- world.boss_list[transformation_replacement] = original_boss
+ if not world.options.boss_shuffle_add_giygas:
+ world.boss_list.remove("Giygas")
+ world.boss_list.insert(29, "Giygas")
if world.options.safe_final_boss:
while world.boss_list[25] in hard_final_bosses:
i = world.random.randrange(len(world.boss_list))
if (world.boss_list[i] == "Diamond Dog" and not world.options.decouple_diamond_dog) or (
- world.boss_list[i] == "Giygas (4)" and not world.options.boss_shuffle_add_giygas
+ world.boss_list[i] == "Giygas" and not world.options.boss_shuffle_add_giygas
):
continue
world.boss_list[25], world.boss_list[i] = world.boss_list[i], world.boss_list[25]
+ did_plando_diam_dog = False
+ diamond_dog_plando_slot = None
+
+ for item in boss_plando:
+ boss_block = item.split("-")
+ boss = boss_block[0].title() # Boss is what's being placed
+ slot = boss_block[1].title() # SLot is where the boss is going.
+ if boss in boss_typo_key:
+ boss = boss_typo_key[boss]
+
+ if slot in boss_typo_key:
+ slot = boss_typo_key[boss]
+
+ if slot == "Diamond Dog":
+ did_plando_diam_dog = True
+ diamond_dog_plando_slot = boss
+
+ old_index = world.boss_list.index(boss) # This should be the slot where the chosen boss currently is
+ new_index = world.boss_slot_order.index(slot) # Boss slots should use the original position
+
+ world.boss_list[old_index] = world.boss_list[new_index] # We want to replace the boss that was originally there with the boss we're swapping with
+ world.boss_list[new_index] = boss
+
+ if world.boss_list[25] == "Carbon Dog" and world.boss_list[27] in banned_transformations:
+ if did_plando_diam_dog:
+ warning(f"""Unable to plando {diamond_dog_plando_slot} for {world.multiworld.get_player_name(world.player)}'s EarthBound world.
+This boss cannot be placed onto Diamond Dog's slot if Carbon Dog is on Heavily Armed Pokey's slot.
+This message is likely the result of randomization and can be safely ignored.""") # Why is this spacing the only way to get the message to render legibly
+ original_boss = world.boss_list[27]
+ transformation_replacement = world.random.randint(0, 24)
+ while world.boss_list[transformation_replacement] in banned_transformations:
+ transformation_replacement = world.random.randint(0, 24)
+ world.boss_list[27] = world.boss_list[transformation_replacement]
+ world.boss_list[transformation_replacement] = original_boss
+
def write_bosses(world: "EarthBoundWorld", rom: "LocalRom") -> None:
rom.write_bytes(0x15E527, bytearray([0x00, 0x00])) # Blank out Pokey's end battle action
rom.write_bytes(0x15B8B9, bytearray([0x00, 0x00]))
@@ -298,7 +402,7 @@ def write_bosses(world: "EarthBoundWorld", rom: "LocalRom") -> None:
rom.write_bytes(0x2FFF17, struct.pack("H", world.boss_info[world.boss_list[27]].enemy_id)) # Add diamond dog
rom.write_bytes(0x2FFF19, bytearray([0xFF]))
rom.write_bytes(world.enemies[world.boss_list[27]].address + 91, bytearray([0x00])) # Force to front row
- elif world.boss_list[25] == "Giygas (4)":
+ elif world.boss_list[25] == "Giygas":
rom.write_bytes(0x0121DF, bytearray([0x00]))
rom.write_bytes(0x2FFF16, bytearray([0xFF]))
else:
diff --git a/worlds/earthbound/modules/enemizer/enemy_attributes.py b/worlds/earthbound/modules/enemizer/enemy_attributes.py
index 868a4a7e959e..255cc6389e85 100644
--- a/worlds/earthbound/modules/enemizer/enemy_attributes.py
+++ b/worlds/earthbound/modules/enemizer/enemy_attributes.py
@@ -574,8 +574,8 @@
"Mini Barf",
"Captain Strong",
"Giygas (6)",
- "Clumsy Robot (3)"
-
+ "Clumsy Robot (3)",
+ "Giygas"
]
robots = [
diff --git a/worlds/earthbound/modules/enemizer/randomize_enemy_attacks.py b/worlds/earthbound/modules/enemizer/randomize_enemy_attacks.py
index b4aeddad1dcc..bc703929fb5b 100644
--- a/worlds/earthbound/modules/enemizer/randomize_enemy_attacks.py
+++ b/worlds/earthbound/modules/enemizer/randomize_enemy_attacks.py
@@ -201,6 +201,7 @@
}
+
def randomize_enemy_attacks(world: "EarthBoundWorld", rom: "LocalRom") -> None:
"""Generates random attacks for enemies.
Certain attacks need to have an argument variable attached.
diff --git a/worlds/earthbound/modules/enemy_data.py b/worlds/earthbound/modules/enemy_data.py
index 0d659f748491..395a9c78dcc9 100644
--- a/worlds/earthbound/modules/enemy_data.py
+++ b/worlds/earthbound/modules/enemy_data.py
@@ -131,10 +131,10 @@ def initialize_enemies(world: "EarthBoundWorld") -> None:
"Criminal Caterpillar": ["fire", "fire", "fire", "fire"],
"Evil Eye": ["null", "diamond_eyes", "paralysis", "null"],
"Master Criminal Worm": ["fire", "fire", "fire", "fire"],
- "Giygas (4)": ["giygas_phase2_thunder", "giygas_phase2_freeze", "giygas_phase2_flash", "null"],
+ "Giygas": ["giygas_phase2_thunder", "giygas_phase2_freeze", "giygas_phase2_flash", "null"],
"Giygas (5)": ["giygas_phase3_thunder", "giygas_phase3_freeze", "giygas_phase3_flash", "null"],
"Giygas (6)": ["giygas_phase4_thunder", "giygas_phase4_freeze", "giygas_phase4_flash", "null"],
- "Starman Junior": ["fire", "freeze", "null", "fire"],
+ "Starman Junior": ["fire", "null", "freeze", "fire"],
"Ultimate Octobot": ["null", "stuffiness_beam", "null", "stuffiness_beam"],
"Mechanical Octobot": ["null", "null", "null", "stuffiness_beam"]
}
@@ -244,7 +244,7 @@ def initialize_enemies(world: "EarthBoundWorld") -> None:
"Trick or Trick Kid": 0x01BC,
"Cave Boy": 0x0149,
"Abstract Art": 0x012C,
- "Shattered Man": 0x0133,
+ "Shattered Man": 0x0142,
"Fierce Shattered Man": 0x0133,
"Ego Orb": 0x0147,
"Yes Man Junior": 0x011C,
@@ -483,7 +483,7 @@ def initialize_enemies(world: "EarthBoundWorld") -> None:
# "Heavily Armed Pokey": EarthBoundEnemy("Heavily Armed Pokey", 0x15e537, 1746, 999, 0, 0, 51, 150, 274, 72, False), Cutscene?
"Giygas (2)": EarthBoundEnemy("Giygas (2)", 0x15e595, 9999, 999, 70000, 0, 80, 255, 255, 80, 5, 255, False),
"Giygas (3)": EarthBoundEnemy("Giygas (3)", 0x15e5f3, 9999, 0, 0, 0, 80, 255, 255, 80, 5, 255, False),
- "Giygas (4)": EarthBoundEnemy("Giygas (4)", 0x15e651, 2000, 0, 0, 0, 80, 255, 255, 80, 5, 255, False),
+ "Giygas": EarthBoundEnemy("Giygas", 0x15e651, 2000, 0, 0, 0, 80, 255, 255, 80, 5, 255, False),
"Giygas (5)": EarthBoundEnemy("Giygas (5)", 0x15e6af, 9999, 0, 0, 0, 80, 255, 255, 80, 5, 255, False),
"Farm Zombie": EarthBoundEnemy("Farm Zombie", 0x15e70d, 171, 0, 700, 58, 10, 31, 24, 19, 15, 24, False),
"Criminal Caterpillar": EarthBoundEnemy("Criminal Caterpillar", 0x15e76b, 250, 168, 30384, 0, 134, 37, 16, 23, 0, 0, False),
@@ -508,7 +508,8 @@ def initialize_enemies(world: "EarthBoundWorld") -> None:
"Carbon Dog": world.enemies[world.boss_list[27]], # This should be the enemy that gets shuffled WITH carbon dog, right? Fix???
"Skate Punk": [shuffled_enemies["Pogo Punk"], shuffled_enemies["Yes Man Junior"]],
"Loaded Dice": [shuffled_enemies["Care Free Bomb"], shuffled_enemies["Beautiful UFO"], shuffled_enemies["High-class UFO"]],
- "Loaded Dice 2": [shuffled_enemies["Electro Swoosh"], shuffled_enemies["Fobby"], shuffled_enemies["Uncontrollable Sphere"]]
+ "Loaded Dice 2": [shuffled_enemies["Electro Swoosh"], shuffled_enemies["Fobby"], shuffled_enemies["Uncontrollable Sphere"]],
+ "Starman Super": [shuffled_enemies["Starman"]]
}
world.regional_enemies = {"Northern Onett": {shuffled_enemies["Spiteful Crow"], shuffled_enemies["Runaway Dog"], shuffled_enemies["Coil Snake"]},
@@ -567,6 +568,13 @@ def initialize_enemies(world: "EarthBoundWorld") -> None:
"Endgame": {world.enemies[world.boss_list[25]], world.enemies["Giygas (2)"], world.enemies[world.boss_list[28]], world.enemies["Giygas (3)"], world.enemies["Giygas (5)"], world.enemies["Giygas (6)"]},
}
+
+ if world.options.randomize_enemy_attacks:
+ del flunkies["Loaded Dice 2"]
+ del flunkies["Skate Punk"]
+ del flunkies["Loaded Dice"]
+ del flunkies["Starman Super"]
+
for region in world.regional_enemies:
enemy_list = world.regional_enemies[region]
updated_list = set(enemy_list)
@@ -579,7 +587,7 @@ def initialize_enemies(world: "EarthBoundWorld") -> None:
if enemy.name in flunkies:
# Can I just always update instead of doing an add? Would probably need to make singulars a list of one item...
- if enemy.name in ["Starman Deluxe", "Skate Punk", "Loaded Dice", "Loaded Dice 2"]:
+ if enemy.name in ["Starman Deluxe", "Skate Punk", "Loaded Dice", "Loaded Dice 2", "Starman Super"]:
updated_list.update(flunkies[enemy.name])
else:
updated_list.add(flunkies[enemy.name])
diff --git a/worlds/earthbound/modules/enemy_shuffler.py b/worlds/earthbound/modules/enemy_shuffler.py
index 8b8485e6b081..5827e7c69a69 100644
--- a/worlds/earthbound/modules/enemy_shuffler.py
+++ b/worlds/earthbound/modules/enemy_shuffler.py
@@ -1173,20 +1173,22 @@ def apply_enemy_shuffle(world: "EarthBoundWorld", rom: "LocalRom") -> None:
rom.write_bytes(0x10df5e, bytearray([enemy_ids[world.acting_enemy_list["Even Slimier Little Pile"]]]))
# Calls for help
- rom.write_bytes(0x15A601, bytearray([enemy_ids[world.acting_enemy_list["Care Free Bomb"]]])) # Loaded Dice
- rom.write_bytes(0x15A602, bytearray([enemy_ids[world.acting_enemy_list["Beautiful UFO"]]]))
- rom.write_bytes(0x15A603, bytearray([enemy_ids[world.acting_enemy_list["High-class UFO"]]]))
- rom.write_bytes(0x15A604, bytearray([enemy_ids[world.acting_enemy_list["Care Free Bomb"]]]))
- rom.write_bytes(0x15DD73, bytearray([enemy_ids[world.acting_enemy_list["Electro Swoosh"]]])) # Loaded Dice 2
- rom.write_bytes(0x15DD74, bytearray([enemy_ids[world.acting_enemy_list["Fobby"]]]))
- rom.write_bytes(0x15DD75, bytearray([enemy_ids[world.acting_enemy_list["Uncontrollable Sphere"]]]))
- rom.write_bytes(0x15DD76, bytearray([enemy_ids[world.acting_enemy_list["Electro Swoosh"]]]))
+ if not world.options.randomize_enemy_attacks:
+ rom.write_bytes(0x15A601, bytearray([enemy_ids[world.acting_enemy_list["Care Free Bomb"]]])) # Loaded Dice
+ rom.write_bytes(0x15A602, bytearray([enemy_ids[world.acting_enemy_list["Beautiful UFO"]]]))
+ rom.write_bytes(0x15A603, bytearray([enemy_ids[world.acting_enemy_list["High-class UFO"]]]))
+ rom.write_bytes(0x15A604, bytearray([enemy_ids[world.acting_enemy_list["Care Free Bomb"]]]))
- rom.write_bytes(0x15AD5B, bytearray([enemy_ids[world.acting_enemy_list["Yes Man Junior"]]])) # Skate Punk
- rom.write_bytes(0x15AD5C, bytearray([enemy_ids[world.acting_enemy_list["Pogo Punk"]]]))
+ rom.write_bytes(0x15DD73, bytearray([enemy_ids[world.acting_enemy_list["Electro Swoosh"]]])) # Loaded Dice 2
+ rom.write_bytes(0x15DD74, bytearray([enemy_ids[world.acting_enemy_list["Fobby"]]]))
+ rom.write_bytes(0x15DD75, bytearray([enemy_ids[world.acting_enemy_list["Uncontrollable Sphere"]]]))
+ rom.write_bytes(0x15DD76, bytearray([enemy_ids[world.acting_enemy_list["Electro Swoosh"]]]))
- rom.write_bytes(0x15AED2, bytearray([enemy_ids[world.acting_enemy_list["Starman"]]])) # S Super
+ rom.write_bytes(0x15AD5B, bytearray([enemy_ids[world.acting_enemy_list["Yes Man Junior"]]])) # Skate Punk
+ rom.write_bytes(0x15AD5C, bytearray([enemy_ids[world.acting_enemy_list["Pogo Punk"]]]))
+
+ rom.write_bytes(0x15AED2, bytearray([enemy_ids[world.acting_enemy_list["Starman"]]])) # S Super
rom.write_bytes(0x15B108, bytearray([enemy_ids[world.acting_enemy_list["Starman Super"]]])) # SDX
rom.write_bytes(0x15DA86, bytearray([enemy_ids[world.acting_enemy_list["Starman Super"]]]))
diff --git a/worlds/earthbound/modules/hint_data.py b/worlds/earthbound/modules/hint_data.py
index 7b9294d097ff..e8643b19b73b 100644
--- a/worlds/earthbound/modules/hint_data.py
+++ b/worlds/earthbound/modules/hint_data.py
@@ -243,13 +243,11 @@ def parse_hint_data(world: "EarthBoundWorld", location: Location, rom: "LocalRom
if hint == "item_at_location":
if world.player == location.item.player and location.item.name in character_item_table and location.item.name != "Photograph":
player_text = "your friend "
- # In-game text command to display party member names
- item_text = bytearray([0x1C, 0x02, party_id_nums[location.item.name]])
+ item_text = bytearray([0x1C, 0x02, party_id_nums[location.item.name]]) # In-game text command to display party member names
elif world.player == location.item.player:
player_text = "your "
if location.item.name in item_id_table:
- # In-game text command to display item names
- item_text = bytearray([0x1C, 0x05, item_id_table[location.item.name]])
+ item_text = bytearray([0x1C, 0x05, item_id_table[location.item.name]]) # In-game text command to display item names
else:
# if the item doesn't have a name (e.g it's PSI)
item_text = text_encoder(location.item.name, 128)
diff --git a/worlds/earthbound/modules/shopsanity.py b/worlds/earthbound/modules/shopsanity.py
index fe4990a676f9..c0eaed5cfbef 100644
--- a/worlds/earthbound/modules/shopsanity.py
+++ b/worlds/earthbound/modules/shopsanity.py
@@ -9,6 +9,13 @@
from . import EarthBoundWorld
from .Rom import LocalRom
+high_purchase_areas = {
+ "Twoson",
+ "Fourside",
+ "Scaraba",
+ "Andonuts Lab Area" # long walk with no atm
+}
+
shop_locations = {
"Onett Drugstore - Right Counter Slot 1",
"Onett Drugstore - Right Counter Slot 2",
@@ -364,11 +371,11 @@ def write_shop_checks(world: "EarthBoundWorld", rom: "LocalRom", shop_checks: li
if ItemClassification.trap in location.item.classification:
price = 0
else:
- price = world.random.randint(1, (75 * (world.accessible_regions.index(location.parent_region.name) + 1)))
- if not world.accessible_regions.index(location.parent_region.name):
- price = int(price / 2)
- elif location.parent_region.name == "Onett" and not world.options.random_start_location:
- price = int(price / 3)
+ price = world.random.randint(1, (75 * world.area_levels[location.parent_region.name]))
+
+ if location.parent_region.name in high_purchase_areas:
+ price = int(price / 1.5)
+
item_struct = struct.pack(' None:
logger.info(announcement)
self.print_to_game(announcement)
+ def toggle_filter_connection_changes(self) -> None:
+ self.filter_connection_changes = not self.filter_connection_changes
+ if self.filter_connection_changes:
+ announcement = "Connection changes are now filtered."
+ else:
+ announcement = "Connection changes are no longer filtered."
+ logger.info(announcement)
+ self.print_to_game(announcement)
+
def toggle_bridge_chat_out(self) -> None:
self.bridge_chat_out = not self.bridge_chat_out
if self.bridge_chat_out:
@@ -388,6 +403,9 @@ async def factorio_server_watcher(ctx: FactorioContext):
elif re.match(r"^[0-9.]+ Script @[^ ]+\.lua:\d+: Player command toggle-ap-send-filter$", msg):
factorio_server_logger.debug(msg)
ctx.toggle_filter_item_sends()
+ elif re.match(r"^[0-9.]+ Script @[^ ]+\.lua:\d+: Player command toggle-ap-connection-change-filter$", msg):
+ factorio_server_logger.debug(msg)
+ ctx.toggle_filter_connection_changes()
elif re.match(r"^[0-9.]+ Script @[^ ]+\.lua:\d+: Player command toggle-ap-chat$", msg):
factorio_server_logger.debug(msg)
ctx.toggle_bridge_chat_out()
@@ -566,7 +584,7 @@ def launch(*new_args: str):
# args handling
parser = get_base_parser(description="Optional arguments to Factorio Client follow. "
- "Remaining arguments get passed into bound Factorio instance."
+ "Remaining arguments get passed into bound Factorio instance. "
"Refer to Factorio --help for those.")
parser.add_argument('--rcon-port', default='24242', type=int, help='Port to use to communicate with Factorio')
parser.add_argument('--rcon-password', help='Password to authenticate with RCON.')
@@ -586,6 +604,7 @@ def launch(*new_args: str):
raise FileNotFoundError(f"Could not find file {server_settings} for server_settings. Aborting.")
initial_filter_item_sends = bool(settings.filter_item_sends)
+ initial_filter_connection_changes = bool(settings.filter_connection_changes)
initial_bridge_chat_out = bool(settings.bridge_chat_out)
if not os.path.exists(os.path.dirname(executable)):
@@ -600,7 +619,7 @@ def launch(*new_args: str):
asyncio.run(main(lambda: FactorioContext(
args.connect, args.password,
- initial_filter_item_sends, initial_bridge_chat_out,
+ initial_filter_connection_changes, initial_filter_item_sends, initial_bridge_chat_out,
rcon_port, rcon_password, server_settings, rest
)))
colorama.deinit()
diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py
index 0a789669d5d6..d7a4977eb3fd 100644
--- a/worlds/factorio/Options.py
+++ b/worlds/factorio/Options.py
@@ -345,6 +345,7 @@ class FactorioWorldGen(OptionDict):
"seed": None,
"starting_area": 1,
"peaceful_mode": False,
+ "no_enemies_mode": False,
"cliff_settings": {
"name": "cliff",
"cliff_elevation_0": 10,
@@ -394,6 +395,7 @@ class FactorioWorldGen(OptionDict):
Optional("height"): And(int, lambda n: n >= 0),
Optional("starting_area"): FloatRange(0.166, 6),
Optional("peaceful_mode"): LuaBool,
+ Optional("no_enemies_mode"): LuaBool,
Optional("cliff_settings"): {
"name": str, "cliff_elevation_0": FloatRange(0, 99),
"cliff_elevation_interval": FloatRange(0.066, 241), # 40/frequency
diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua
index cd0c00e9877f..3f754d925ce9 100644
--- a/worlds/factorio/data/mod_template/control.lua
+++ b/worlds/factorio/data/mod_template/control.lua
@@ -859,6 +859,10 @@ commands.add_command("toggle-ap-send-filter", "Toggle filtering of item sends th
log("Player command toggle-ap-send-filter") -- notifies client
end)
+commands.add_command("toggle-ap-connection-change-filter", "Toggle filtering of players joining or parting", function(call)
+ log("Player command toggle-ap-connection-change-filter") -- notifies client
+end)
+
commands.add_command("toggle-ap-chat", "Toggle sending of chat messages from players on the Factorio server to Archipelago.", function(call)
log("Player command toggle-ap-chat") -- notifies client
end)
diff --git a/worlds/factorio/docs/setup_en.md b/worlds/factorio/docs/setup_en.md
index 0219486b2009..1cf5c09d6a4f 100644
--- a/worlds/factorio/docs/setup_en.md
+++ b/worlds/factorio/docs/setup_en.md
@@ -179,6 +179,18 @@ factorio_options:
filter_item_sends: true
```
+### filter_connection_changes
+
+By default, all player joined and player stopped tracking notifications are displayed in-game. In larger async seeds this may become overly spammy.
+To hide all connection changes, do one of the following:
+- Type `/toggle-ap-connection-change-filter` in-game
+- Type `/toggle_connection_change_filter` in the Achipelago Client
+- In your `host.yaml` set
+```
+factorio_options:
+ filter_connection_changes: true
+```
+
### bridge_chat_out
By default, in-game chat is bridged to Archipelago. If you prefer to be able to speak privately, you can disable this
feature by doing one of the following:
diff --git a/worlds/factorio/settings.py b/worlds/factorio/settings.py
index a2296e7395a0..15d6f637aa3d 100644
--- a/worlds/factorio/settings.py
+++ b/worlds/factorio/settings.py
@@ -19,8 +19,12 @@ class FilterItemSends(settings.Bool):
class BridgeChatOut(settings.Bool):
"""Whether to send chat messages from players on the Factorio server to Archipelago."""
+
+ class FilterConnectionChanges(settings.Bool):
+ """Whether to filter connection changes displayed in-game."""
executable: Executable = Executable("factorio/bin/x64/factorio")
server_settings: typing.Optional[ServerSettings] = None
filter_item_sends: typing.Union[FilterItemSends, bool] = False
+ filter_connection_changes: typing.Union[FilterConnectionChanges, bool] = False
bridge_chat_out: typing.Union[BridgeChatOut, bool] = True
diff --git a/worlds/fe8/archipelago.json b/worlds/fe8/archipelago.json
new file mode 100644
index 000000000000..99b81cad9dbe
--- /dev/null
+++ b/worlds/fe8/archipelago.json
@@ -0,0 +1 @@
+{"compatible_version": 7, "version": 7, "game": "Fire Emblem Sacred Stones"}
\ No newline at end of file
diff --git a/worlds/fe8/fe8py.py b/worlds/fe8/fe8py.py
index 70af4c874f59..d8abaa21e129 100644
--- a/worlds/fe8/fe8py.py
+++ b/worlds/fe8/fe8py.py
@@ -1019,6 +1019,7 @@ def redistribute_growths(self, total: int) -> list[int]:
break
i = self.random.choice(available_indices)
result[i] += overflow
+ overflow = 0
if result[i] > 255:
overflow = result[i]-255
result[i] = 255
diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py
index 4e26be1653a6..7bf773f5cf9e 100644
--- a/worlds/ffmq/Regions.py
+++ b/worlds/ffmq/Regions.py
@@ -155,6 +155,10 @@ def check_foresta(region):
return True
check_foresta(loc.parent_region)
+ if self.options.map_shuffle or self.options.crest_shuffle:
+ process_rules(self.multiworld.get_entrance("Subregion Frozen Fields to Subregion Aquaria", self.player),
+ ["SummerAquaria"])
+
if self.options.logic == "friendly":
process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player),
["MagicMirror"])
diff --git a/worlds/fftii/Client.py b/worlds/fftii/Client.py
new file mode 100644
index 000000000000..fa05b25bd4f0
--- /dev/null
+++ b/worlds/fftii/Client.py
@@ -0,0 +1,459 @@
+import logging
+
+from typing import TYPE_CHECKING
+
+from NetUtils import ClientStatus
+
+import worlds._bizhawk as bizhawk
+
+from worlds._bizhawk.client import BizHawkClient
+
+from .data import memory
+from .data.items import item_data_lookup, gear_item_names, gil_item_names, gil_item_sizes, zodiac_stone_names, \
+ jp_item_names, jp_item_sizes, job_names, special_character_names, world_map_pass_names, earned_job_names
+from .data.locations import linked_reward_names
+from .data.logic.JobUnlocks import unlock_dict
+from .data.memory import stones_lookup, seed_hash_length, pass_paths, finale_path
+
+if TYPE_CHECKING:
+ from worlds._bizhawk.context import BizHawkClientContext
+
+logger = logging.getLogger("Client")
+
+guard_list = [
+ (memory.world_loaded_address, [memory.world_loaded_value[3]], "MainRAM"),
+ (memory.world_loaded_address + 1, [memory.world_loaded_value[2]], "MainRAM"),
+ (memory.world_loaded_address + 2, [memory.world_loaded_value[1]], "MainRAM"),
+ (memory.world_loaded_address + 3, [memory.world_loaded_value[0]], "MainRAM"),
+]
+
+def get_byte_bit_from_index(index):
+ return index // 8, 2 ** (index % 8)
+
+def get_bit_value_from_position(position):
+ return 2 ** position
+
+class FinalFantasyTacticsIvaliceIslandClient(BizHawkClient):
+ game = "Final Fantasy Tactics Ivalice Island"
+ system = "PSX"
+ patch_suffix = ".apfftii"
+
+ def __init__(self) -> None:
+ self.ram = "MainRAM"
+ self.location_name_to_id: dict[str, int] | None = None
+ self.item_name_to_id: dict[str, int] | None = None
+ self.logged_version = False
+
+ async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
+ try:
+ # Check ROM name/patch version
+ rom_name = ((await bizhawk.read(
+ ctx.bizhawk_ctx,
+ [(memory.cd_name_location, len(memory.cd_name), self.ram)]))[0])
+ try:
+ rom_name = rom_name.decode("utf-8")
+ except UnicodeDecodeError:
+ return False
+ if rom_name != memory.cd_name:
+ return False
+ except bizhawk.RequestFailedError:
+ return False # Not able to get a response, say no for now
+
+ ctx.game = self.game
+ ctx.items_handling = 0b111
+ ctx.want_slot_data = True
+ return True
+
+ async def set_auth(self, ctx: "BizHawkClientContext") -> None:
+ import base64
+ auth_raw = (await bizhawk.read(
+ ctx.bizhawk_ctx,
+ [(memory.rom_name_location_in_ram, memory.rom_name_length, self.ram)]))[0]
+ ctx.auth = base64.b64encode(auth_raw).decode()
+
+ async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
+ if ctx.server is None:
+ return
+
+ if ctx.slot is None:
+ return
+ try:
+ if self.location_name_to_id is None:
+ from . import FinalFantasyTacticsIvaliceIslandWorld
+ self.location_name_to_id = FinalFantasyTacticsIvaliceIslandWorld.location_name_to_id
+ self.item_name_to_id = FinalFantasyTacticsIvaliceIslandWorld.item_name_to_id
+ if await self.check_valid_game(ctx):
+ await self.check_victory(ctx)
+ await self.location_check(ctx)
+ await self.received_items_check(ctx)
+ await self.write_pass_paths(ctx)
+
+ except bizhawk.RequestFailedError:
+ # The connector didn't respond. Exit handler and return to main loop to reconnect
+ pass
+
+ async def check_valid_game(self, ctx: "BizHawkClientContext") -> bool:
+ game_started_address, game_started_bit = get_byte_bit_from_index(memory.game_started_flag_address)
+ game_started_data = await self.read_ram_value_guarded(ctx, memory.event_flags_location + game_started_address)
+ if game_started_data is None:
+ return False
+ if game_started_data & game_started_bit == 0:
+ return False
+ return True
+
+ async def location_check(self, ctx: "BizHawkClientContext"):
+ locations_checked = []
+ locations_checked.extend(await self.check_major_locations(ctx))
+ locations_checked.extend(await self.check_poaches(ctx))
+ locations_checked.extend(await self.check_job_unlocks(ctx))
+
+ found_locations = await ctx.check_locations(locations_checked)
+ for location in found_locations:
+ ctx.locations_checked.add(location)
+
+ async def check_major_locations(self, ctx: "BizHawkClientContext") -> list[int]:
+ locations_checked = []
+ major_locations_data = await self.read_ram_values_guarded(
+ ctx,
+ memory.event_flags_location,
+ memory.event_flags_length)
+ if major_locations_data is None:
+ return locations_checked
+ for location, flag in memory.locations_to_read.items():
+ offset, bit = get_byte_bit_from_index(flag)
+ if major_locations_data[offset] & bit:
+ locations_checked.append(self.location_name_to_id[location])
+ if location in linked_reward_names.keys():
+ for linked_location in linked_reward_names[location]:
+ locations_checked.append(self.location_name_to_id[linked_location])
+ return locations_checked
+
+ async def check_poaches(self, ctx: "BizHawkClientContext") -> list[int]:
+ locations_checked = []
+ poach_data = await self.read_ram_values_guarded(
+ ctx,
+ memory.poaching_flags_location,
+ memory.poaching_flags_length)
+ if poach_data is None:
+ return locations_checked
+ for location, flag in memory.poaching_addresses.items():
+ offset, bit = get_byte_bit_from_index(flag)
+ if poach_data[offset] & bit:
+ locations_checked.append(self.location_name_to_id[location])
+ return locations_checked
+
+ async def check_job_unlocks(self, ctx: "BizHawkClientContext") -> list[int]:
+ locations_checked = []
+ formation_data = await self.read_ram_values_guarded(ctx, memory.unit_stats_address, memory.unit_stats_length)
+ if formation_data is None:
+ return locations_checked
+ unlocked_jobs = set()
+ for unit_number in range(memory.unit_count):
+ current_unit_jobs = {}
+ base_address = unit_number * memory.unit_stat_size
+ party_id_location = base_address + memory.party_id_offset
+ unit_party_id_data = formation_data[party_id_location]
+ if unit_party_id_data == 0xFF:
+ continue
+ for index, job in enumerate(memory.job_level_order):
+ job_byte_location = base_address + memory.job_level_offset + (index // 2)
+ if index % 2 == 0:
+ job_nybble = (formation_data[job_byte_location] & 0xF0) >> 4
+ else:
+ job_nybble = formation_data[job_byte_location] & 0x0F
+ current_unit_jobs[job] = job_nybble
+ for job, requirements in unlock_dict.items():
+ job_ids = []
+ for earned_job in earned_job_names:
+ job_ids.append(self.item_name_to_id[earned_job])
+ all_jobs_obtained = [item.item for item in ctx.items_received if item.item in job_ids]
+ jobs_obtained_names = [ctx.item_names.lookup_in_game(pass_id) for pass_id in all_jobs_obtained]
+ jobs_obtained_names.extend(["Squire"])
+ unlock_job = self.check_job_unlock_condition(current_unit_jobs, requirements, jobs_obtained_names)
+ if unlock_job:
+ unlocked_jobs.add(f"{job} Unlock")
+ for job in unlocked_jobs:
+ locations_checked.append(self.location_name_to_id[job])
+ return locations_checked
+
+ def check_job_unlock_condition(self, job_levels, unlock_requirements, current_jobs):
+ unlock = True
+ for requirement_job, required_level in unlock_requirements.items():
+ current_level = job_levels[requirement_job]
+ if requirement_job not in current_jobs:
+ return False
+ if current_level < required_level:
+ unlock = False
+ if requirement_job == "Bard":
+ if job_levels["Bard"] == 0:
+ unlock = False
+ if requirement_job == "Dancer":
+ if job_levels["Dancer"] == 0:
+ unlock = False
+ if unlock == False:
+ return False
+ return True
+
+ async def received_items_check(self, ctx: "BizHawkClientContext"):
+ write_list: list[tuple[int, list[int], str]] = []
+
+ items_received_count_low = await self.read_ram_value_guarded(ctx, memory.items_received_low)
+ items_received_count_high = await self.read_ram_value_guarded(ctx, memory.items_received_high)
+ if items_received_count_low is None or items_received_count_high is None:
+ return
+ items_received_count = int.from_bytes([items_received_count_low, items_received_count_high], "little")
+ if items_received_count < len(ctx.items_received):
+ current_item = ctx.items_received[items_received_count]
+ current_item_id = current_item.item
+ current_item_name = ctx.item_names.lookup_in_game(current_item_id, ctx.game)
+ if current_item_name in gear_item_names:
+ write_list_candidate = await self.write_inventory_item(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.append(write_list_candidate)
+ elif current_item_name == "Progressive Shop Level":
+ write_list_candidate = await self.increment_shop_progression(ctx)
+ if write_list_candidate is None:
+ return
+ write_list.append(write_list_candidate)
+ elif current_item_name in gil_item_names:
+ write_list_candidate = await self.write_gil_item(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.extend(write_list_candidate)
+ elif current_item_name in zodiac_stone_names:
+ write_list_candidate = await self.write_zodiac_stones(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.append(write_list_candidate)
+ elif current_item_name in jp_item_names:
+ write_list_candidate = await self.write_jp_items(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.extend(write_list_candidate)
+ write_list_candidate = await self.write_cumulative_boon(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.extend(write_list_candidate)
+ elif current_item_name in special_character_names:
+ write_list_candidate = await self.write_character_recruit(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.append(write_list_candidate)
+ elif current_item_name == "Progressive Ramza Job Form":
+ write_list_candidate = await self.write_ramza_form(ctx)
+ if write_list_candidate is None:
+ return
+ write_list.append(write_list_candidate)
+ elif current_item_name in job_names:
+ write_list_candidate = await self.write_job_unlocks(ctx, current_item_name)
+ if write_list_candidate is None:
+ return
+ write_list.append(write_list_candidate)
+ items_received_count += 1
+ write_list.append((memory.items_received_low, [items_received_count % 256], self.ram))
+ write_list.append((memory.items_received_high, [items_received_count // 256], self.ram))
+ write_successful = await self.write_ram_values_guarded(ctx, write_list)
+ if write_successful:
+ await bizhawk.display_message(ctx.bizhawk_ctx, f"Received {current_item_name}")
+
+ async def check_victory(self, ctx):
+ value = None
+ if ctx.finished_game:
+ return
+ else:
+ if self.location_name_to_id["Graveyard of Airships 2 Story Battle"] in ctx.locations_checked:
+ await ctx.send_msgs([
+ {"cmd": "StatusUpdate",
+ "status": ClientStatus.CLIENT_GOAL}
+ ])
+ ctx.finished_game = True
+
+ async def set_options_flags(self, ctx):
+ write_list = []
+ sidequest_address, sidequest_bit = get_byte_bit_from_index(memory.yaml_options["Sidequests"])
+ sidequest_data = await self.read_ram_value_guarded(ctx, memory.event_flags_location + sidequest_address)
+ if sidequest_data is None:
+ return
+ new_sidequest_data = sidequest_data | sidequest_bit
+ write_list.append((memory.event_flags_location + sidequest_address, [new_sidequest_data], self.ram))
+ await self.write_ram_values_guarded(ctx, write_list)
+
+ async def write_inventory_item(self, ctx: "BizHawkClientContext", item: str) -> tuple[int, list[int], str] | None:
+ item_index = item_data_lookup[item].game_id
+ current_item_data = await self.read_ram_value_guarded(ctx, memory.inventory_start_address + item_index)
+ if current_item_data is None:
+ return
+ current_item_quantity = current_item_data
+ new_item_quantity = min(99, current_item_quantity + 1)
+ return memory.inventory_start_address + item_index, [new_item_quantity], self.ram
+
+ async def increment_shop_progression(self, ctx: "BizHawkClientContext") -> tuple[int, list[int], str] | None:
+ current_shop_data = await self.read_ram_value_guarded(ctx, memory.shop_progression_address)
+ if current_shop_data is None:
+ return
+ new_shop_progression = min(15, current_shop_data + 1)
+ return memory.shop_progression_address, [new_shop_progression], self.ram
+
+ async def write_gil_item(self, ctx: "BizHawkClientContext", gil_item: str) -> list[tuple[int, list[int], str]] | None:
+ current_gil_data = await self.read_ram_values_guarded(ctx, memory.war_funds_address, memory.war_funds_length)
+ if current_gil_data is None:
+ return None
+ current_gil = int.from_bytes(current_gil_data, "little")
+ gil_item_size = int(ctx.slot_data["bonus_gil_item_size"])
+ gil_quantity = gil_item_sizes[gil_item_size][gil_item]
+ new_gil = min(99999999, current_gil + gil_quantity)
+ return [
+ (memory.war_funds_address, [new_gil % 256], self.ram),
+ (memory.war_funds_address + 1, [new_gil // 256 % 256], self.ram),
+ (memory.war_funds_address + 2, [new_gil // (2**16) % 256], self.ram),
+ (memory.war_funds_address + 3, [new_gil // (2**24)], self.ram),
+ ]
+
+ async def write_jp_items(self, ctx: "BizHawkClientContext", jp_item: str) -> list[tuple[int, list[int], str]] | None:
+ jp_item_size = int(ctx.slot_data["jp_boon_size"])
+ jp_quantity = jp_item_sizes[jp_item_size][jp_item]
+
+ formation_data = await self.read_ram_values_guarded(ctx, memory.unit_stats_address, memory.unit_stats_length)
+ if formation_data is None:
+ return None
+ new_formation_data = bytearray(formation_data)
+ for unit_number in range(memory.unit_count):
+ base_address = unit_number * memory.unit_stat_size
+ party_id_location = base_address + memory.party_id_offset
+ unit_party_id_data = formation_data[party_id_location]
+ if unit_party_id_data == 0xFF:
+ continue
+ for job_number in range(memory.job_amount):
+ jp_address = base_address + memory.jp_offset + (job_number * 2)
+ current_jp = int.from_bytes(formation_data[jp_address:jp_address + 2], "little")
+ new_jp = min(current_jp + jp_quantity, 9999)
+ new_jp_lower_byte = new_jp % 256
+ new_jp_upper_byte = new_jp // 256
+ new_formation_data[jp_address] = new_jp_lower_byte
+ new_formation_data[jp_address + 1] = new_jp_upper_byte
+
+ temp_formation_data = await self.read_ram_values_guarded(ctx, memory.temp_unit_stats_address, memory.temp_unit_stats_length)
+ if temp_formation_data is None:
+ return None
+ temp_new_formation_data = bytearray(temp_formation_data)
+ for unit_number in range(memory.temp_unit_count):
+ base_address = unit_number * memory.temp_unit_stat_size
+ for job_number in range(memory.temp_job_amount):
+ jp_address = base_address + memory.temp_jp_offset + (job_number * 2)
+ current_jp = int.from_bytes(temp_formation_data[jp_address:jp_address + 2], "little")
+ new_jp = min(current_jp + jp_quantity, 9999)
+ new_jp_lower_byte = new_jp % 256
+ new_jp_upper_byte = new_jp // 256
+ temp_new_formation_data[jp_address] = new_jp_lower_byte
+ temp_new_formation_data[jp_address + 1] = new_jp_upper_byte
+ return [
+ (memory.unit_stats_address, list(new_formation_data), self.ram),
+ (memory.temp_unit_stats_address, list(temp_new_formation_data), self.ram)
+ ]
+
+ async def write_cumulative_boon(self, ctx: "BizHawkClientContext", jp_item: str) -> list[tuple[int, list[int], str]] | None:
+ jp_item_size = int(ctx.slot_data["jp_boon_size"])
+ jp_quantity = jp_item_sizes[jp_item_size][jp_item]
+ current_jp_data = await self.read_ram_values_guarded(
+ ctx,
+ memory.total_jp_boon_gained,
+ memory.total_jp_boon_gained_length)
+ if current_jp_data is None:
+ return
+ current_jp_amount = int.from_bytes(current_jp_data, "little")
+ new_jp_amount = min(9999, current_jp_amount + jp_quantity)
+ return [
+ (memory.total_jp_boon_gained, [new_jp_amount % 256], self.ram),
+ (memory.total_jp_boon_gained + 1, [new_jp_amount // 256 % 256], self.ram)
+ ]
+
+
+ async def write_zodiac_stones(self, ctx: "BizHawkClientContext", stone_name: str) -> tuple[int, list[int], str] | None:
+ address, bit = stones_lookup[stone_name]
+ current_stone_data = await self.read_ram_value_guarded(ctx, address)
+ if current_stone_data is None:
+ return None
+ new_stone_data = current_stone_data | get_bit_value_from_position(bit)
+ return address, [new_stone_data], self.ram
+
+ async def write_character_recruit(self, ctx: "BizHawkClientContext", character_name: str) -> tuple[int, list[int], str] | None:
+ address, bit = get_byte_bit_from_index(memory.character_recruit_addresses[character_name])
+ recruit_location = memory.event_flags_location + address
+ current_recruit_data = await self.read_ram_value_guarded(ctx, recruit_location)
+ if current_recruit_data is None:
+ return None
+ new_recruit_data = current_recruit_data | bit
+ return recruit_location, [new_recruit_data], self.ram
+
+ async def write_ramza_form(self, ctx: "BizHawkClientContext") -> tuple[int, list[int], str] | None:
+ chapter_2_address, chapter_2_bit = get_byte_bit_from_index(
+ memory.ramza_job_unlock_addresses["Chapter 2 Ramza Squire Job Unlock"])
+ chapter_4_address, chapter_4_bit = get_byte_bit_from_index(
+ memory.ramza_job_unlock_addresses["Chapter 4 Ramza Squire Job Unlock"])
+ chapter_2_location = memory.event_flags_location + chapter_2_address
+ chapter_4_location = memory.event_flags_location + chapter_4_address
+ chapter_2_data = await self.read_ram_value_guarded(ctx, chapter_2_location)
+ chapter_4_data = await self.read_ram_value_guarded(ctx, chapter_4_location)
+ if chapter_2_data is None or chapter_4_data is None:
+ return None
+ if chapter_2_data & chapter_2_bit > 0:
+ new_data = chapter_4_data | chapter_4_bit
+ return chapter_4_location, [new_data], self.ram
+ else:
+ new_data = chapter_2_data | chapter_2_bit
+ return chapter_2_location, [new_data], self.ram
+
+ async def write_job_unlocks(self, ctx: "BizHawkClientContext", job_name: str) -> tuple[int, list[int], str] | None:
+ address, bit = get_byte_bit_from_index(memory.available_jobs_addresses[job_name])
+ job_location = memory.event_flags_location + address
+ job_data = await self.read_ram_value_guarded(ctx, job_location)
+ if job_data is None:
+ return None
+ new_job_data = job_data | bit
+ return job_location, [new_job_data], self.ram
+
+ async def write_pass_paths(self, ctx: "BizHawkClientContext"):
+ pass_ids = []
+ for world_map_pass in world_map_pass_names:
+ pass_ids.append(self.item_name_to_id[world_map_pass])
+ all_passes_obtained = [item.item for item in ctx.items_received if item.item in pass_ids]
+ pass_obtained_names = [ctx.item_names.lookup_in_game(pass_id) for pass_id in all_passes_obtained]
+ flags_to_write = []
+ for pass_name in pass_obtained_names:
+ if pass_name in pass_paths:
+ for companion_pass in pass_paths[pass_name]:
+ if companion_pass in pass_obtained_names:
+ flags_to_write.extend(pass_paths[pass_name][companion_pass])
+
+ stone_ids = []
+ for stone in zodiac_stone_names:
+ stone_ids.append(self.item_name_to_id[stone])
+ all_stones_obtained = [item.item for item in ctx.items_received if item.item in stone_ids]
+ if len(all_stones_obtained) >= ctx.slot_data["zodiac_stones_required"]:
+ flags_to_write.append(finale_path)
+ write_list = []
+ for flag in flags_to_write:
+ address, bit = get_byte_bit_from_index(flag)
+ flag_address = memory.event_flags_location + address
+ flag_data = await self.read_ram_value_guarded(ctx, flag_address)
+ if flag_data is None:
+ return
+ new_flag_data = flag_data | bit
+ write_list.append((flag_address, [new_flag_data], self.ram))
+ await self.write_ram_values_guarded(ctx, write_list)
+
+ async def read_ram_values_guarded(self, ctx: "BizHawkClientContext", location: int, size: int):
+ value = await bizhawk.guarded_read(ctx.bizhawk_ctx, [(location, size, self.ram)], guard_list)
+ if value is None:
+ return None
+ return value[0]
+
+ async def read_ram_value_guarded(self, ctx: "BizHawkClientContext", location: int):
+ value = await bizhawk.guarded_read(ctx.bizhawk_ctx, [(location, 1, self.ram)], guard_list)
+ if value is None:
+ return None
+ return int.from_bytes(value[0], "little")
+
+ async def write_ram_values_guarded(self, ctx: "BizHawkClientContext", write_list: list[tuple[int, list[int], str]]):
+ return await bizhawk.guarded_write(ctx.bizhawk_ctx, write_list, guard_list)
diff --git a/worlds/fftii/ErrorRecalc.py b/worlds/fftii/ErrorRecalc.py
new file mode 100644
index 000000000000..544468c49e6a
--- /dev/null
+++ b/worlds/fftii/ErrorRecalc.py
@@ -0,0 +1,260 @@
+# Copied from the SOTN APWorld under MIT license. Credit goes to fdelduque
+
+import os
+
+ecc_f_lut = bytearray(256)
+ecc_b_lut = bytearray(256)
+edc_lut = [0] * 256
+SYNCHEADER = bytes([0,255,255,255,255,255,255,255,255,255,255,0])
+SECTOR_SIZE = 2352
+
+# Initialize ECC and EDC lookup tables
+for i in range(256):
+ j = (i << 1) ^ (0x11D if i & 0x80 else 0)
+ ecc_f_lut[i] = j
+ ecc_b_lut[i ^ j] = i
+ edc = i
+
+ for j in range(8):
+ edc = (edc >> 1) ^ (0xD8018001 if edc & 1 else 0)
+
+ edc_lut[i] = edc
+
+
+def all_zero(data):
+ return all(v == 0 for v in data)
+
+def compute_edc_block(sector):
+ edc = 0
+ for b in sector:
+ edc = (edc >> 8) ^ edc_lut[(edc ^ b) & 0xFF]
+
+ return edc
+
+def compute_ecc_block(sector, major_count, minor_count, major_mult, minor_inc):
+ size = major_count * minor_count
+ block = bytearray(major_count * 2)
+
+ for major in range(major_count):
+ index = (major >> 1) * major_mult + (major & 1)
+ ecc_a = 0
+ ecc_b = 0
+
+ for _ in range(minor_count):
+ temp = sector[index]
+ index += minor_inc
+ if index >= size:
+ index -= size
+
+ ecc_a ^= temp
+ ecc_b ^= temp
+ ecc_a = ecc_f_lut[ecc_a]
+
+ ecc_a = ecc_b_lut[ecc_f_lut[ecc_a] ^ ecc_b]
+ block[major] = ecc_a
+ block[major + major_count] = ecc_a ^ ecc_b
+
+ return block
+
+def generate_ecc(sector, zeroaddress):
+ if zeroaddress:
+ address = sector[12:12+4]
+ sector[12:12+4] = [0] * 4
+
+ # Generate P code
+ p_code = compute_ecc_block(sector[0xC:], 86, 24, 2, 86)
+ sector[0x81C:0x81C+len(p_code)] = p_code
+
+ # Generate Q code
+ q_code = compute_ecc_block(sector[0xC:], 52, 43, 86, 88)
+ sector[0x8C8:0x8C8+len(q_code)] = q_code
+
+ if zeroaddress:
+ sector[12:12+4] = address
+
+def verify_edc(sector):
+ mode = sector[0x0F]
+ if mode == 0:
+ return all_zero(sector[0x10:0x10+0x920])
+ elif mode == 1:
+ ref = int.from_bytes(sector[0x810:0x810+4], byteorder='little')
+ edc = compute_edc_block(sector[:0x810])
+ return edc == ref
+ elif mode == 2:
+ form = sector[0x12] & 0x20
+ if not form:
+ edc = compute_edc_block(sector[0x10:0x10+0x808])
+ ref = int.from_bytes(sector[0x818:0x818+4], byteorder='little')
+ return edc == ref
+ else:
+ ref = int.from_bytes(sector[0x92C:0x92C+4], byteorder='little')
+ if ref == 0:
+ return True
+ edc = compute_edc_block(sector[0x10:0x10+0x91C])
+ return edc == ref
+
+class RecalcStats:
+ recalc_sectors: int = 0
+ edc_blocks_computed: int = 0
+ ecc_blocks_generated: int = 0
+
+ def total_sectors(self):
+ return self.recalc_sectors
+
+class FullRecalcStats(RecalcStats):
+ pass
+
+class DiffRecalcStats(RecalcStats):
+ identical_sectors: int = 0
+
+ def total_sectors(self):
+ return self.identical_sectors + self.recalc_sectors
+
+class ValidateStats:
+ ok_sectors: int = 0
+ bad_sectors: int = 0
+ missing_sync_headers: int = 0
+
+ def total_sectors(self):
+ return self.ok_sectors + self.bad_sectors
+
+class ErrorRecalculator:
+ calculate_form_2_edc: bool
+
+ def __init__(self, calculate_form_2_edc = False):
+ """
+ :param bool calculate_form_2_edc: Specify whether Mode 2 Form 2 sectors should have their edc calculated or zeroed
+ """
+ self.calculate_form_2_edc = calculate_form_2_edc
+
+ def ecc_edc_generate(self, sector, stats: RecalcStats):
+ sector[0:len(SYNCHEADER)] = SYNCHEADER
+ mode = sector[0x0F]
+ if mode == 0:
+ sector[0x10:0x10+0x920] = [0] * 0x920
+ elif mode == 1:
+ edc = compute_edc_block(sector[:0x810])
+ sector[0x810:0x810+4] = edc.to_bytes(4, byteorder='little')
+ sector[0x814:0x814+8] = [0] * 8
+ generate_ecc(sector, False)
+
+ stats.edc_blocks_computed += 1
+ stats.ecc_blocks_generated += 1
+ elif mode == 2:
+ form = sector[0x12] & 0x20
+ if not form:
+ # Form 1
+ edc = compute_edc_block(sector[0x10:0x10+0x808])
+ sector[0x818:0x818+4] = edc.to_bytes(4, byteorder='little')
+ generate_ecc(sector, True)
+
+ stats.edc_blocks_computed += 1
+ stats.ecc_blocks_generated += 1
+ else:
+ # Form 2
+ if self.calculate_form_2_edc:
+ edc = compute_edc_block(sector[0x10:0x10+0x91C])
+ stats.edc_blocks_computed += 1
+ else:
+ edc = 0
+
+ sector[0x92C:0x92C+4] = edc.to_bytes(4, byteorder='little')
+ # Form 2 doesn't generate ecc
+
+
+
+ def _diff_recalc(self, target_file: os.PathLike, base_file: os.PathLike):
+ '''
+ Do a smart ECC/EDC recalc based on a diff with a base file
+
+ This function compares the new file with a base file. It skips over every sector that has not changed from the
+ base file, and recalculated ECC/EDC only for sectors with changed data.
+
+ It is meant to be an optimization that avoids work over a full recalc. It incurs a penalty in having to compare
+ byte-for-byte between 2 files before recalculating ECC/EDC for a sector, but can save a lot of time if the 2 files
+ are similar, for exemple if the target file is a patched version of base_file.
+
+ It is assumed that the ECC/EDC blocks in the base file are already valid.
+ '''
+ stats = DiffRecalcStats()
+ target_file_size = os.path.getsize(target_file)
+ base_file_size = os.path.getsize(base_file)
+
+ if target_file_size != base_file_size:
+ raise "File sizes of base and target files do not match"
+
+ if target_file_size % SECTOR_SIZE != 0:
+ raise f"Files are not in {SECTOR_SIZE} bytes sectors"
+
+ sector_count = int(target_file_size / SECTOR_SIZE)
+ with open(target_file, 'r+b') as ftarget:
+ with open(base_file, 'r+b') as fbase:
+ for sector_no in range(16, sector_count):
+ start = sector_no*SECTOR_SIZE
+ ftarget.seek(start, os.SEEK_SET)
+ fbase.seek(start, os.SEEK_SET)
+
+ target_sector = ftarget.read(SECTOR_SIZE)
+ base_sector = fbase.read(SECTOR_SIZE)
+
+ if base_sector == target_sector:
+ stats.identical_sectors += 1
+ else:
+ sector = bytearray(target_sector)
+ self.ecc_edc_generate(sector, stats)
+ ftarget.seek(start, os.SEEK_SET)
+ ftarget.write(sector)
+ stats.recalc_sectors += 1
+
+ return stats
+
+
+ def _full_recalc(self, file: os.PathLike):
+ '''
+ Recalculate EDC/ECC for all sectors in a file
+ '''
+ stats = FullRecalcStats()
+ file_size = os.path.getsize(file)
+
+ if file_size % SECTOR_SIZE != 0:
+ raise f"File is not in {SECTOR_SIZE} bytes sectors"
+
+ sector_count = int(file_size / SECTOR_SIZE)
+ with open(file_size, 'r+b') as f:
+ for sector_no in range(16, sector_count):
+ start = sector_no*SECTOR_SIZE
+ f.seek(start, os.SEEK_SET)
+
+ sector = bytearray(f.read(SECTOR_SIZE))
+ self.ecc_edc_generate(sector, stats)
+ f.seek(start, os.SEEK_SET)
+ f.write(sector)
+ stats.recalc_sectors += 1
+
+ return stats
+
+ def recalc(self, target_file: os.PathLike, base_file: os.PathLike = None) -> DiffRecalcStats | FullRecalcStats:
+ if not base_file:
+ return self._full_recalc(target_file)
+ else:
+ return self._diff_recalc(target_file, base_file)
+
+ def validate(self, target_file: os.PathLike) -> ValidateStats:
+ with open(target_file, "rb") as f:
+ fd = bytearray(f.read())
+ sector_count = len(fd) / 2352
+ stats = ValidateStats()
+
+ for sector_no in range(16, int(sector_count)):
+ start = sector_no * 2352
+ sector = fd[start:start+2352]
+
+ if not sector[0:len(SYNCHEADER)] == SYNCHEADER:
+ stats.missing_sync_headers += 1
+
+ if verify_edc(sector):
+ stats.ok_sectors += 1
+ else:
+ stats.bad_sectors += 1
+
+ return stats
diff --git a/worlds/fftii/Items.py b/worlds/fftii/Items.py
new file mode 100644
index 000000000000..4929c4eeeb6d
--- /dev/null
+++ b/worlds/fftii/Items.py
@@ -0,0 +1,28 @@
+from BaseClasses import ItemClassification
+from .data.items import all_item_names, major_item_names, useful_item_names, nonbalanced_major_item_names
+
+
+class ItemData:
+ name: str
+ classification: ItemClassification
+ id: int
+
+ def __init__(self, name: str, classification: ItemClassification, id: int):
+ self.name = name
+ self.classification = classification
+ self.id = id
+
+ def __repr__(self):
+ return self.name
+
+item_table: dict[str, ItemData] = dict()
+item_table["Farlem"] = ItemData("Farlem", ItemClassification.progression, 1)
+
+for index, item in enumerate(all_item_names, start=2):
+ classification = ItemClassification.progression if item in major_item_names else ItemClassification.filler
+ classification = ItemClassification.progression_skip_balancing if item in nonbalanced_major_item_names else classification
+ classification = ItemClassification.useful if item in useful_item_names else classification
+ item_table[item] = ItemData(item, classification, index + 1)
+
+
+valid_item_names = [*all_item_names, "Zodiac Stones"]
\ No newline at end of file
diff --git a/worlds/fftii/Locations.py b/worlds/fftii/Locations.py
new file mode 100644
index 000000000000..22ad041dbefb
--- /dev/null
+++ b/worlds/fftii/Locations.py
@@ -0,0 +1,42 @@
+from BaseClasses import Location
+from .data.logic.Requirement import Requirement
+from .data.logic import topologies
+from .data.locations import all_regions, monster_location_names, location_sort_list, location_sort_list_names
+
+
+class FinalFantasyTacticsIILocation(Location):
+ game = "Final Fantasy Tactics Ivalice Island"
+ logic_rule: list[list[str]]
+
+class LocationData:
+ name: str
+ id: int
+ requirements: list[Requirement]
+ battle_level: int
+
+ def __init__(self, name: str, id: int, battle_level):
+ self.name = name
+ self.id = id
+ self.battle_level = battle_level
+
+ def __repr__(self):
+ return self.name
+
+id = 1
+
+all_locations: list[LocationData] = list()
+
+for region in all_regions:
+ for location in region.locations:
+ location_data = LocationData(location.name, id, location.battle_level)
+ location_data.requirements = location.requirements
+ all_locations.append(location_data)
+ id += 1
+
+all_monster_locations = []
+
+for monster in monster_location_names:
+ location_data = LocationData(monster, id, 0)
+ all_locations.append(location_data)
+ all_monster_locations.append(location_data)
+ id += 1
\ No newline at end of file
diff --git a/worlds/fftii/Logic.py b/worlds/fftii/Logic.py
new file mode 100644
index 000000000000..740fe22a5449
--- /dev/null
+++ b/worlds/fftii/Logic.py
@@ -0,0 +1,194 @@
+from copy import copy
+
+from BaseClasses import CollectionState
+from .data.logic.Monsters import RegionAccessRequirement
+from .data.logic.Requirement import Requirement
+from .Items import valid_item_names
+from .Options import FinalFantasyTacticsIIOptions
+
+# Number of shop progression unlocks needed to logically access a battle
+easy_battle_levels = [0, 1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14, 14]
+normal_battle_levels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
+difficult_battle_levels = [0, 1, 1, 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11]
+extreme_battle_levels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+battle_levels = [easy_battle_levels, normal_battle_levels, difficult_battle_levels, extreme_battle_levels]
+
+# 0, 2, 5, 8, and 9 correspond to formations 1, 2, 3, 4, and the rare respectively. These are hard requirements.
+# Others are for story/sidequest battles. These are soft requirements.
+easy_poach_battle_levels = [0, None, 2, None, None, 5, 8, None, 8, 8, 10, None, 14, None, 14]
+normal_poach_battle_levels = [0, None, 2, None, None, 5, 6, None, 8, 8, 8, None, 12, None, 14]
+difficult_poach_battle_levels = [0, None, 2, None, None, 5, 5, None, 8, 8, 6, None, 8, None, 11]
+extreme_poach_battle_levels = [0, None, 2, None, None, 5, 0, None, 8, 8, 0, None, 0, None, 0]
+
+poach_battle_levels = [
+ easy_poach_battle_levels, normal_poach_battle_levels,
+ difficult_poach_battle_levels, extreme_poach_battle_levels
+]
+
+# Number of jobs required to logically access a battle
+easy_job_battle_levels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+normal_job_battle_levels = [1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 10]
+difficult_job_battle_levels = [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]
+extreme_job_battle_levels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+job_battle_levels = [
+ easy_job_battle_levels, normal_job_battle_levels,
+ difficult_job_battle_levels, extreme_job_battle_levels
+]
+
+easy_poach_job_battle_levels = [0, None, 2, None, None, 5, 6, None, 8, 8, 10, None, 12, None, 14]
+normal_poach_job_battle_levels = [0, None, 2, None, None, 4, 4, None, 5, 5, 7, None, 8, None, 10]
+difficult_poach_job_battle_levels = [0, None, 2, None, None, 3, 3, None, 4, 4, 5, None, 6, None, 7]
+extreme_poach_job_battle_levels = [0, None, 1, None, None, 1, 1, None, 1, 1, 1, None, 1, None, 0]
+poach_job_battle_levels = [
+ easy_poach_job_battle_levels, normal_poach_job_battle_levels,
+ difficult_poach_job_battle_levels, extreme_poach_job_battle_levels
+]
+
+class LogicObject:
+ requirements: list[list[str]] = []
+ player: int
+ options: "FinalFantasyTacticsIIOptions"
+ battle_level: int
+ zodiac_stones_required: int
+
+ def __init__(self, player: int, options: "FinalFantasyTacticsIIOptions", battle_level: int, zodiac_stones_required: int):
+ self.player = player
+ self.options = options
+ self.battle_level = battle_level
+ self.zodiac_stones_required = zodiac_stones_required
+
+ def logic_rule(self, state: CollectionState) -> bool:
+ if len(self.requirements) == 0 and self.battle_level < 1:
+ return True
+ expression = None
+ for requirement_list in self.requirements:
+ if "Zodiac Stones" in requirement_list:
+ expression = state.has_group("Zodiac Stones", self.player, self.zodiac_stones_required)
+ elif expression is None:
+ expression = state.has_all(requirement_list, self.player)
+ else:
+ expression = expression or state.has_all(requirement_list, self.player)
+ if self.battle_level > 0:
+ battle_level_expression = (state.has(
+ "Progressive Shop Level",
+ self.player,
+ battle_levels[self.options.logical_difficulty.value][self.battle_level])
+ and state.has_group_unique(
+ "Jobs",
+ self.player,
+ job_battle_levels[self.options.logical_difficulty.value][self.battle_level]))
+ if expression is None:
+ expression = battle_level_expression
+ else:
+ expression = expression and battle_level_expression
+ if expression is None:
+ return True
+ return expression
+
+def create_logic_rule_for_list(
+ requirements: list[Requirement],
+ options: "FinalFantasyTacticsIIOptions",
+ debug: bool = False) -> list:
+ requirements_list = []
+ for requirement in requirements:
+ new_rule = create_logic_rule(requirement, options, debug)
+ for requirement2 in new_rule:
+ requirements_list.append(requirement2)
+ continue
+ if debug:
+ print("Create logic rule for list...")
+ for requirement in requirements_list:
+ print("Logic rule:")
+ print(f"Requirements: {requirement}")
+ print("===\n")
+ return requirements_list
+
+def create_logic_rule(
+ requirement: Requirement,
+ options: "FinalFantasyTacticsIIOptions",
+ debug: bool = False) -> list[str]:
+ if requirement.check_option_enabled(options):
+ requirements_list = []
+ unpack_requirement(
+ requirement,
+ requirements_list,
+ [],
+ options,
+ debug)
+ if debug:
+ print("Create logic rule...")
+ print(f"Requirement: {requirement}")
+ print(f"Requirements List: [")
+ for requirement in requirements_list:
+ print(f" {requirement}")
+ print(f"]")
+ return requirements_list
+ else:
+ if debug:
+ print(f"Requirement {requirement.name} disabled due to options.")
+ return []
+
+def unpack_requirement(
+ requirement: Requirement,
+ possibilities: list[list[str]],
+ parent_items: list[str],
+ options: "FinalFantasyTacticsIIOptions",
+ debug = False) -> None:
+ if requirement.check_option_enabled(options):
+ if len(requirement.other_requirements) > 0:
+ for nested_requirement in requirement.other_requirements:
+ current_parent_items = copy(parent_items)
+ for item_needed in requirement.items_needed:
+ assert item_needed in valid_item_names, (item_needed, requirement)
+ parent_items.extend(requirement.items_needed)
+ unpack_requirement(
+ nested_requirement,
+ possibilities,
+ parent_items,
+ options,
+ debug
+ )
+ parent_items = copy(current_parent_items)
+ elif len(requirement.items_needed) > 0:
+ items_needed = copy(requirement.items_needed)
+ for item_needed in items_needed:
+ assert item_needed in valid_item_names, (item_needed, requirement)
+ items_needed.extend(parent_items)
+ possibilities.append(items_needed)
+ else:
+ if debug:
+ print(f"Requirement {requirement.name} disabled due to options.")
+
+class PoachLogicObject:
+ requirements: list[RegionAccessRequirement] = []
+ player: int
+ options: "FinalFantasyTacticsIIOptions"
+
+ def __init__(self, player: int, options: "FinalFantasyTacticsIIOptions"):
+ self.player = player
+ self.options = options
+
+ def poach_logic_rule(self, state: CollectionState) -> bool:
+ expression = None
+ for requirement in self.requirements:
+ if requirement.sidequest and not self.options.sidequest_battles:
+ continue
+ region_list = requirement.access_regions
+ battle_level = requirement.battle_level
+ region_expression = None
+ for region in region_list:
+ if region_expression is None:
+ region_expression = state.can_reach_region(region.name, self.player)
+ else:
+ region_expression = region_expression and state.can_reach_region(region.name, self.player)
+ poach_shop_level = poach_battle_levels[self.options.logical_difficulty.value][battle_level]
+ poach_job_count = poach_job_battle_levels[self.options.logical_difficulty.value][battle_level]
+ assert poach_shop_level is not None, requirement.access_regions
+ assert poach_job_count is not None, requirement.access_regions
+ battle_level_expression = (state.has("Progressive Shop Level", self.player, poach_shop_level)
+ and state.has_group_unique("Jobs", self.player, poach_job_count))
+ if expression is None:
+ expression = region_expression and battle_level_expression
+ else:
+ expression = expression or (region_expression and battle_level_expression)
+ return expression and state.has("Thief", self.player)
\ No newline at end of file
diff --git a/worlds/fftii/Options.py b/worlds/fftii/Options.py
new file mode 100644
index 000000000000..da1929e0a486
--- /dev/null
+++ b/worlds/fftii/Options.py
@@ -0,0 +1,299 @@
+from dataclasses import dataclass
+
+from Options import Range, PerGameCommonOptions, OptionGroup, StartInventoryPool, DefaultOnToggle, Toggle, Choice, \
+ Visibility
+
+
+# Main Options
+class ZodiacStonesInPool(Range):
+ """How many Zodiac Stones will be in the item pool.
+ If set to less than the number required, will instead become however many are required."""
+ display_name = "Zodiac Stones in Pool"
+ range_start = 1
+ range_end = 13
+ default = 6
+
+class ZodiacStonesRequired(Range):
+ """How many Zodiac Stones will be required to beat the game."""
+ display_name = "Zodiac Stones Required"
+ range_start = 1
+ range_end = 13
+ default = 6
+
+class ZodiacStoneLocations(Choice):
+ """Where can Zodiac Stones appear?
+ Vanilla limits their possible locations to those events which had one in the vanilla game. Note that this limits
+ the number of stones in the pool to 11, with sidequests on, and 8 without.
+ Anywhere Local means stones can be at any location in your world.
+ Anywhere means stones can be anywhere in the multiworld."""
+ display_name = "Zodiac Stone Locations"
+ option_vanilla_stones = 0
+ option_anywhere_local = 1
+ option_anywhere = 2
+ default = 0
+
+class FinalBattles(Choice):
+ """What the final goal is.
+ Vanilla requires the completion of all six final battles in sequence at Murond Death City.
+ Altima Only requires only Altima at Murond Death City, and the other endgame battles will be located at Orbonne."""
+ display_name = "Final Battles"
+ option_vanilla = 0
+ option_altima_only = 1
+ default = 0
+
+class SidequestBattles(DefaultOnToggle):
+ """Are sidequest battles (Colliery, Nelveska, Zarghidas, Deep Dungeon) in the pool of locations?"""
+ display_name = "Sidequest Battles"
+
+class JobUnlocks(DefaultOnToggle):
+ """Are job unlocks in the item and location pools?"""
+ display_name = "Job Unlocks"
+
+class RareBattles(Toggle):
+ """Are the rare battles for each battleground in the location pool?"""
+ display_name = "Rare Battles"
+
+class PoachLocations(Toggle):
+ """Are poaches in the location pool? WARNING: Can be grindy and RNG-heavy."""
+ display_name = "Poach Locations"
+
+class LogicalDifficulty(Choice):
+ """Affects the placement of logically required shop levels and jobs in the multiworld."""
+ display_name = "Logical Difficulty"
+ option_easy = 0
+ option_normal = 1
+ option_difficult = 2
+ option_extreme = 3
+ default = 1
+
+# Item options
+class ChemistPlacement(Choice):
+ """Where Chemist is placed in the multiworld when Job Unlocks are enabled.
+ Starting will have you start with Chemist.
+ Early will ensure Chemist is one of your first items in the multiworld.
+ Shuffled will shuffle Chemist into the pool like other jobs."""
+ display_name = "Chemist Placement"
+ option_starting = 0
+ option_early = 1
+ option_shuffled = 2
+ default = 1
+
+class EarlyPass(DefaultOnToggle):
+ """Will a Pass for a neighboring region be an early item?"""
+ display_name = "Early Pass"
+
+class StartingShopLevel(Range):
+ """The starting shop level of your game."""
+ display_name = "Starting Shop Level"
+ range_start = 0
+ range_end = 14
+ default = 0
+
+# Filler item options
+class NormalItemWeight(Range):
+ """Weight of items normally sold in shops in the filler pool."""
+ display_name = "Normal Item Weight"
+ range_start = 0
+ range_end = 10
+ default = 3
+
+class RareItemWeight(Range):
+ """Weight of items not normally sold in shops in the filler pool."""
+ display_name = "Rare Item Weight"
+ range_start = 0
+ range_end = 10
+ default = 1
+
+class BonusGilItemWeight(Range):
+ """Weight of bonus items in the filler pool."""
+ display_name = "Bonus Gil Item Weight"
+ range_start = 0
+ range_end = 10
+ default = 2
+
+class JPBoonItemWeight(Range):
+ """Weight of JP Boon items that award JP to the team in the filler pool"""
+ display_name = "JP Boon Item Weight"
+ range_start = 0
+ range_end = 10
+ default = 1
+
+class BonusGilItemSize(Choice):
+ """Adjusts the value of bonus gil items in the pool.
+ Normal is 5000/10000/40000. Frugal halves that, Expensive doubles."""
+ display_name = "Bonus Gil Item Size"
+ option_frugal = 0
+ option_normal = 1
+ option_expensive = 2
+ default = 1
+
+class JPBoonSize(Choice):
+ """Adjusts the value of JP Boon items. Normal is 50/100/250 JP. Frugal halves that, Expensive doubles."""
+ display_name = "JP Boon Item Size"
+ option_frugal = 0
+ option_normal = 1
+ option_expensive = 2
+ default = 1
+
+# QoL options
+
+class EXPGainMultiplier(Choice):
+ """Multiplier to in-battle EXP gains."""
+ display_name = "EXP Gain Multiplier"
+ option_normal = 0
+ option_double = 1
+ option_quadruple = 2
+ default = 1
+
+class JPGainMultiplier(Choice):
+ """Multiplier to in-battle JP gains."""
+ display_name = "JP Gain Multiplier"
+ option_normal = 0
+ option_double = 1
+ option_quadruple = 2
+ default = 1
+
+# Enemy rando options
+
+class EnemyRandomizer(Choice):
+ """Randomizes enemies.
+ Disabled leaves enemies as vanilla.
+ Limited randomizes only bosses with bosses.
+ Simple randomizes only generics (both humans and monsters) with generics.
+ Advanced randomizes all unique units and all generics with their categories.
+ Complex randomizes both generic and special units with each other."""
+ display_name = "Enemy Randomizer"
+ option_disabled = 0
+ option_limited = 1
+ option_simple = 2
+ option_advanced = 3
+ option_complex = 4
+ default = 0
+ visibility = Visibility.none
+
+class CrossEnemyRandomizer(Toggle):
+ """If enabled, randomized humans can become monster units and vice versa."""
+ display_name = "Cross Enemy Randomizer"
+ visibility = Visibility.none
+
+class RandomizeStoryFightsOnly(Toggle):
+ """If enabled, only story and sidequest fights will be randomized,
+ and random encounters will be left alone"""
+ display_name = "Randomize Story Fights Only"
+ visibility = Visibility.none
+
+class EnemyRandomizerLocality(Choice):
+ """Controls the scope of enemy randomization.
+ Battle randomizes enemy units of the same job differently per battle.
+ Region randomizes enemy units of the same job differently per region.
+ Global randomizes all enemy units in the game of the same job to the same randpmized job."""
+ display_name = "Enemy Randomizer Locality"
+ option_battle = 0
+ option_region = 1
+ option_global = 2
+ default = 0
+ visibility = Visibility.none
+
+class LucaviRandomizer(Choice):
+ """Controls if Lucavi and Altima are included in randomization.
+ Disabled leaves Lucavi (Queklain, Velius, Zalera, Adramelk, Hashmalum, Elidibs) the same as vanilla.
+ Lucavi enables those locations and jobs to be included in randomization.
+ Include Altima also includes Altima 1 and 2 in the randomization. Will cause graphical glitches."""
+ display_name = "Lucavi Randomizer"
+ option_disabled = 0
+ option_lucavi = 1
+ option_include_altima = 2
+ default = 0
+ visibility = Visibility.none
+
+class EnemyRandomizerMethod(Choice):
+ """Controls the randomization method for determining randomized units.
+ Shuffle disallows duplicate randomized units in the chosen locality.
+ Chaos allows duplicate randomized units in the chosen locality."""
+ display_name = "Enemy Randomizer Method"
+ option_shuffle = 0
+ option_chaos = 1
+ default = 0
+ visibility = Visibility.none
+
+# Unused options
+class StartingRegion(Choice):
+ """What region to start in. Gallione is easiest, followed by Lesalia and Lionel. Fovoham, Zeltennia, and Limberry
+ should only be chosen if you're looking for a challenge."""
+ display_name = "Starting Region"
+ option_gallione = 0
+ option_lesalia = 1
+ option_lionel = 2
+ option_fovoham = 3
+ option_zeltennia = 4
+ option_limberry = 5
+ default = 0
+
+@dataclass
+class FinalFantasyTacticsIIOptions(PerGameCommonOptions):
+ zodiac_stones_in_pool: ZodiacStonesInPool
+ zodiac_stones_required: ZodiacStonesRequired
+ zodiac_stone_locations: ZodiacStoneLocations
+ final_battles: FinalBattles
+ sidequest_battles: SidequestBattles
+ job_unlocks: JobUnlocks
+ rare_battles: RareBattles
+ poach_locations: PoachLocations
+ logical_difficulty: LogicalDifficulty
+ chemist_placement: ChemistPlacement
+ early_pass: EarlyPass
+ starting_shop_level: StartingShopLevel
+ normal_item_weight: NormalItemWeight
+ rare_item_weight: RareItemWeight
+ bonus_gil_item_weight: BonusGilItemWeight
+ jp_boon_item_weight: JPBoonItemWeight
+ bonus_gil_item_size: BonusGilItemSize
+ jp_boon_size: JPBoonSize
+ enemy_randomizer: EnemyRandomizer
+ cross_enemy_randomizer: CrossEnemyRandomizer
+ randomize_story_fights_only: RandomizeStoryFightsOnly
+ enemy_randomizer_locality: EnemyRandomizerLocality
+ lucavi_randomizer: LucaviRandomizer
+ enemy_randomizer_method: EnemyRandomizerMethod
+ exp_gain_multiplier: EXPGainMultiplier
+ jp_gain_multiplier: JPGainMultiplier
+ start_inventory_from_pool: StartInventoryPool
+
+fftii_option_groups = [
+ OptionGroup("Main Options", [
+ ZodiacStonesInPool,
+ ZodiacStonesRequired,
+ ZodiacStoneLocations,
+ FinalBattles,
+ SidequestBattles,
+ JobUnlocks,
+ PoachLocations,
+ RareBattles,
+ LogicalDifficulty
+ ]),
+ OptionGroup("Item Options", [
+ ChemistPlacement,
+ EarlyPass,
+ StartingShopLevel
+ ]),
+ OptionGroup("Filler Options", [
+ NormalItemWeight,
+ RareItemWeight,
+ BonusGilItemWeight,
+ JPBoonItemWeight,
+ BonusGilItemSize,
+ JPBoonSize
+ ]),
+ # OptionGroup("Enemy Randomizer Options", [
+ # EnemyRandomizer,
+ # CrossEnemyRandomizer,
+ # RandomizeStoryFightsOnly,
+ # EnemyRandomizerLocality,
+ # LucaviRandomizer,
+ # EnemyRandomizerMethod
+ # ]),
+ OptionGroup("QOL Options", [
+ EXPGainMultiplier,
+ JPGainMultiplier
+ ])
+]
\ No newline at end of file
diff --git a/worlds/fftii/Rom.py b/worlds/fftii/Rom.py
new file mode 100644
index 000000000000..74e4ce0c48da
--- /dev/null
+++ b/worlds/fftii/Rom.py
@@ -0,0 +1,164 @@
+import json
+import os
+import pkgutil
+
+from pathlib import Path
+
+import bsdiff4
+
+import Utils
+from settings import get_settings
+from worlds.Files import APProcedurePatch, APTokenMixin, APPatchExtension
+from .ErrorRecalc import ErrorRecalculator
+from .data import memory
+from .data.locations import rare_battle_location_names, dd_location_names
+from .data.memory import victory_text_offsets, rare_battles_offset, dd_battles_offset
+from .data.text import split_text_into_lines
+from .data.logic.FFTLocation import LocationNames
+
+
+def get_base_rom_as_bytes() -> bytes:
+ with open(get_settings().fftii_options.rom_file, "rb") as infile:
+ base_rom_bytes = bytes(Utils.read_snes_rom(infile))
+ return base_rom_bytes
+
+
+class FinalFantasyTacticsIIPatchExtension(APPatchExtension):
+ game = "Final Fantasy Tactics Ivalice Island"
+
+
+ @staticmethod
+ def write_text_to_location(bytes_to_write, location, rom_data):
+ sector_size = 0x930
+ data_start = 0x18
+ data_size = 0x800
+ header_size = 0x18
+ ec_size = 0x118
+ other_size = header_size + ec_size
+
+ current_sector = location // sector_size
+ current_sector_start = current_sector * sector_size
+ offset_in_sector = location % sector_size
+ data_end = current_sector_start + data_start + data_size
+
+ current_offset = location
+ while len(bytes_to_write) > 0:
+ rom_data[current_offset] = bytes_to_write.pop(0)
+ current_offset += 1
+ if current_offset >= data_end:
+ current_offset = data_end + other_size
+ current_sector = current_offset // sector_size
+ current_sector_start = current_sector * sector_size
+ data_end = current_sector_start + data_start + data_size
+ pass
+
+ @staticmethod
+ def patch_bin(caller, iso, placement_file):
+ patch_dict = json.loads(caller.get_file(placement_file))
+ if patch_dict["APJobs"] == 1:
+ base_patch = pkgutil.get_data(__name__, "fftiiapjobs.bsdiff4")
+ else:
+ base_patch = pkgutil.get_data(__name__, "fftiivanillajobs.bsdiff4")
+ rom_data = bsdiff4.patch(iso, base_patch)
+ rom_data = bytearray(rom_data)
+
+ if "RareBattles" in patch_dict.keys():
+ if patch_dict["RareBattles"] == 1:
+ address, bit = memory.yaml_options["RareBattles"]
+ rom_data[address] = rom_data[address] | bit
+ if patch_dict["Sidequests"] == 1:
+ address, bit = memory.yaml_options["Sidequests"]
+ rom_data[address] = rom_data[address] | bit
+ if patch_dict["FinalBattles"] == 1:
+ address, bit = memory.yaml_options["FinalBattles"]
+ rom_data[address] = rom_data[address] | bit
+ if "EXPMultiplier" in patch_dict.keys():
+ exp_mult_values = [0x00, 0x40, 0x80]
+ exp_mult_value = exp_mult_values[patch_dict["EXPMultiplier"]]
+ address = memory.yaml_options["EXPMultiplier"]
+ rom_data[address] = exp_mult_value
+ if "JPMultiplier" in patch_dict.keys():
+ exp_mult_values = [0x00, 0x40, 0x80]
+ exp_mult_value = exp_mult_values[patch_dict["JPMultiplier"]]
+ address = memory.yaml_options["JPMultiplier"]
+ rom_data[address] = exp_mult_value
+
+ location_dict = patch_dict["LocationDict"]
+ for location, text in location_dict.items():
+ if location in victory_text_offsets:
+ text_lines, byte_lines = split_text_into_lines(text)
+ all_bytes = []
+ for byte_line in byte_lines:
+ all_bytes.extend(byte_line)
+ if isinstance(victory_text_offsets[location], list):
+ for offset in victory_text_offsets[location]:
+ bytes_to_write = all_bytes.copy()
+ FinalFantasyTacticsIIPatchExtension.write_text_to_location(bytes_to_write, offset, rom_data)
+ else:
+ offset = victory_text_offsets[location]
+ FinalFantasyTacticsIIPatchExtension.write_text_to_location(all_bytes, offset, rom_data)
+
+
+ #rom_data[offset:offset + len(all_bytes)] = all_bytes
+ if LocationNames.MANDALIA_RARE.value in location_dict.keys():
+ all_rare_bytes = []
+ for location in rare_battle_location_names:
+ text = location_dict[location]
+ text_lines, byte_lines = split_text_into_lines(text)
+ for byte_line in byte_lines:
+ all_rare_bytes.extend(byte_line)
+ FinalFantasyTacticsIIPatchExtension.write_text_to_location(all_rare_bytes, rare_battles_offset, rom_data)
+ if LocationNames.NOGIAS_SIDEQUEST.value in location_dict.keys():
+ all_dd_bytes = []
+ for location in dd_location_names:
+ text = location_dict[location]
+ text_lines, byte_lines = split_text_into_lines(text)
+ for byte_line in byte_lines:
+ all_dd_bytes.extend(byte_line)
+ FinalFantasyTacticsIIPatchExtension.write_text_to_location(all_dd_bytes, dd_battles_offset, rom_data)
+ rom_name_text = patch_dict["RomName"]
+ rom_name = bytearray(rom_name_text, 'utf-8')
+ rom_name.extend([0] * (20 - len(rom_name)))
+ rom_data[memory.rom_name_location:memory.rom_name_location + 20] = bytes(rom_name[:20])
+ rom_data[memory.volume_name_location:memory.volume_name_location + 20] = bytes(rom_name[:20])
+ seed_hash = int(patch_dict["SeedHash"])
+ seed_hash_bytes = seed_hash.to_bytes(2)
+ rom_data[memory.seed_hash_location:memory.seed_hash_location + 2] = bytes(seed_hash_bytes)
+
+ return rom_data
+
+
+
+class FinalFantasyTacticsIIProcedurePatch(APProcedurePatch, APTokenMixin):
+ game = "Final Fantasy Tactics Ivalice Island"
+ hash = "b156ba386436d20fd5ed8d37bab6b624"
+ patch_file_ending = ".apfftii"
+ result_file_ending = ".cue"
+
+ procedure = [
+ ("patch_bin", ["patch_file.json"])
+ ]
+
+ @classmethod
+ def get_source_data(cls) -> bytes:
+ return get_base_rom_as_bytes()
+
+ def patch(self, target: str) -> None:
+ file_name = target[:-4]
+ if os.path.exists(file_name + ".cue"):
+ os.unlink(file_name + ".cue")
+ if os.path.exists(file_name + "patched" + ".bin"):
+ os.unlink(file_name + "patched" + ".bin")
+
+ super().patch(target)
+ os.rename(target, file_name + "patched" + ".bin")
+ error_recalculator = ErrorRecalculator(calculate_form_2_edc=False)
+ stats = error_recalculator.recalc(target_file=Path(file_name + "patched" + ".bin"),
+ base_file=get_settings().fftii_options.rom_file)
+ print(
+ f"{stats.identical_sectors} identical sectors out of {stats.total_sectors()}, {stats.recalc_sectors} sectors recalculated")
+ print(f"{stats.edc_blocks_computed} EDC blocks computed, {stats.ecc_blocks_generated} ECC blocks generated")
+
+ cue_text = f'FILE "{file_name}patched.bin" BINARY\n TRACK 01 MODE2/2352\n INDEX 01 00:00:00'
+ with open(file_name + ".cue", "w") as cue_file:
+ cue_file.write(cue_text)
\ No newline at end of file
diff --git a/worlds/fftii/__init__.py b/worlds/fftii/__init__.py
new file mode 100644
index 000000000000..381ebec85b3c
--- /dev/null
+++ b/worlds/fftii/__init__.py
@@ -0,0 +1,557 @@
+import json
+import logging
+import os
+
+import Utils
+import settings
+import typing
+
+from typing import Dict, Any, TextIO
+from BaseClasses import MultiWorld, ItemClassification, Tutorial, Item, Region, Entrance
+from Utils import version_tuple
+
+from worlds.AutoWorld import World, WebWorld
+from worlds.generic.Rules import add_rule
+
+from .Items import item_table
+from .Locations import all_locations, FinalFantasyTacticsIILocation
+from .Logic import create_logic_rule, create_logic_rule_for_list, LogicObject, PoachLogicObject
+from .Options import FinalFantasyTacticsIIOptions, fftii_option_groups
+from .Rom import FinalFantasyTacticsIIProcedurePatch
+from .Client import FinalFantasyTacticsIvaliceIslandClient
+
+from .data.items import zodiac_stone_names, world_map_pass_names, job_names, shop_levels, \
+ special_character_names, ramza_job_levels, rare_item_names, shop_item_names, \
+ gil_item_names_weighted, earned_job_names, filler_item_names, jp_item_names_weighted
+from .data.locations import all_regions, world_map_regions, story_battle_locations, character_recruit_locations, \
+ sidequest_battle_locations, job_unlock_locations, rare_battle_locations, default_murond_fights, \
+ shop_unlock_locations, monster_location_names, story_zodiac_stone_locations, sidequest_zodiac_stone_locations, \
+ ramza_job_unlock_locations, location_sort_list_names, locations_with_text, linked_reward_names, \
+ altima_only_story_zodiac_stone_locations, location_groups
+from .data.logic.FFTLocation import LocationNames
+from .data.logic.Monsters import monster_locations_lookup, monster_family_lookup, monster_families
+from .data.logic.regions.Fovoham import fovoham_regions
+from .data.logic.regions.Gallione import gallione_regions
+from .data.logic.regions.Jobs import jobs_regions
+from .data.logic.regions.Lesalia import lesalia_regions
+from .data.logic.regions.Limberry import limberry_regions
+from .data.logic.regions.Lionel import lionel_regions
+from .data.logic.regions.Murond import murond_regions
+from .data.logic.regions.Zeltennia import zeltennia_regions
+from .data.text import create_text_for_own_item, create_text_for_offworld_item
+
+
+class FinalFantasyTacticsIISettings(settings.Group):
+ class RomFile(settings.UserFilePath):
+ """File name of the Final Fantasy Tactics ISO"""
+ description = "Final Fantasy Tactics ISO File"
+ copy_to = "Final Fantasy Tactics (USA).bin"
+ md5s = ["b156ba386436d20fd5ed8d37bab6b624"]
+
+ rom_file: RomFile = RomFile(RomFile.copy_to)
+ rom_start: bool = False
+
+
+class FinalFantasyTacticsIIWeb(WebWorld):
+ theme = "ocean"
+ setup = Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to setting up Final Fantasy Tactics for Archipelago on your computer.",
+ "English",
+ "multiworld_en.md",
+ "multiworld/en",
+ ["Rosalie"]
+ )
+
+ tutorials = [setup]
+
+ rich_text_options_doc = True
+ option_groups = fftii_option_groups
+
+
+class FinalFantasyTacticsIvaliceIslandWorld(World):
+ """
+ An open world mod for Final Fantasy Tactics for Archipelago.
+ Find all the Zodiac Stones and make your way to Murond Death City to confront Altima!
+ """
+ settings: typing.ClassVar[FinalFantasyTacticsIISettings]
+ game = "Final Fantasy Tactics Ivalice Island"
+ options_dataclass = FinalFantasyTacticsIIOptions
+ options: FinalFantasyTacticsIIOptions
+
+ base_id = 0
+ web = FinalFantasyTacticsIIWeb()
+
+ item_name_to_id = {item: item_data.id for item, item_data in item_table.items()}
+ location_name_to_id = {location.name: location.id for location in all_locations}
+ item_name_groups = {
+ "Zodiac Stones": zodiac_stone_names,
+ "Characters": special_character_names,
+ "Jobs": job_names
+ }
+
+ location_name_groups = location_groups
+
+ filler_items: list[str] | None
+ included_locations: list[str]
+ murond_fights: list[str]
+ starting_pass: str
+ zodiac_stones_required: int
+ zodiac_stones_in_pool: int
+
+ version = "0.1.0"
+ debug = False
+ topology_present = debug
+
+ def __init__(self, multiworld: MultiWorld, player: int):
+ super().__init__(multiworld, player)
+ self.filler_items = None
+ self.included_locations = list()
+ self.murond_fights = list()
+ self.starting_pass = "Gallione Pass"
+ self.zodiac_stones_required = 0
+
+ @classmethod
+ def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
+ logging.info(f"Final Fantasy Tactics Ivalice Island APWorld v{cls.version} used for generation.")
+
+ @classmethod
+ def stage_write_spoiler_header(cls, _multiworld: MultiWorld, spoiler_handle: TextIO):
+ spoiler_handle.write(f"\nFinal Fantasy Tactics Ivalice Island APWorld version: v{cls.version}\n")
+
+ def create_item(self, name: str) -> "FinalFantasyTacticsIIItem":
+ return FinalFantasyTacticsIIItem(name, item_table[name].classification, self.item_name_to_id[name], self.player)
+
+ def create_event(self, name: str) -> "FinalFantasyTacticsIIItem":
+ return FinalFantasyTacticsIIItem(name, ItemClassification.progression, None, self.player)
+
+ def generate_early(self) -> None:
+ # Story battles are always in
+ included_locations: list[LocationNames] = []
+ included_locations.extend(story_battle_locations)
+
+ # Character recruitment locations are always in if not tied to a sidequest
+ if self.options.sidequest_battles:
+ character_recruitments = character_recruit_locations
+ else:
+ character_recruitments = [
+ character for character in character_recruit_locations if character not in sidequest_battle_locations
+ ]
+ included_locations.extend(character_recruitments)
+
+ # Shop unlocks are always in
+ included_locations.extend(shop_unlock_locations)
+
+ # Ramza form unlocks are always in
+ included_locations.extend(ramza_job_unlock_locations)
+
+ # Optional locations
+ if self.options.sidequest_battles:
+ included_locations.extend(sidequest_battle_locations)
+ if self.options.job_unlocks:
+ included_locations.extend(job_unlock_locations)
+ if self.options.rare_battles:
+ included_locations.extend(rare_battle_locations)
+
+
+ if self.options.final_battles == self.options.final_battles.option_vanilla:
+ self.murond_fights.extend([fight.value for fight in default_murond_fights])
+
+ self.included_locations = [location.value for location in included_locations]
+
+ if self.options.poach_locations:
+ self.included_locations.extend(monster_location_names)
+
+ # Make Zodiac Stones local if option is set
+
+ if self.options.zodiac_stone_locations == self.options.zodiac_stone_locations.option_anywhere_local:
+ self.options.local_items.value |= set(zodiac_stone_names)
+
+ self.zodiac_stones_required = self.options.zodiac_stones_required.value
+ self.zodiac_stones_in_pool = self.options.zodiac_stones_in_pool.value
+ # Determine number of zodiac stones based on options
+ if self.options.zodiac_stone_locations == self.options.zodiac_stone_locations.option_vanilla_stones:
+ max_stones = len(story_zodiac_stone_locations)
+ if self.options.final_battles == self.options.final_battles.option_altima_only:
+ max_stones += len(altima_only_story_zodiac_stone_locations)
+ if self.options.sidequest_battles:
+ max_stones += len(sidequest_zodiac_stone_locations)
+ self.zodiac_stones_required = min(self.zodiac_stones_required, max_stones)
+ self.zodiac_stones_in_pool = min(self.zodiac_stones_in_pool, max_stones)
+ if self.zodiac_stones_in_pool < self.zodiac_stones_required:
+ self.zodiac_stones_in_pool = self.zodiac_stones_required
+
+ early_items_dict = {}
+ if self.options.early_pass:
+ early_pass = self.random.choice(["Fovoham Pass", "Lesalia Pass", "Murond Pass"])
+ early_items_dict.update({early_pass: 1})
+ if self.options.chemist_placement == self.options.chemist_placement.option_early:
+ early_items_dict["Chemist"] = 1
+ self.multiworld.early_items[self.player].update(early_items_dict)
+
+ if self.options.chemist_placement == self.options.chemist_placement.option_starting:
+ self.options.start_inventory_from_pool.value.update({"Chemist": 1})
+ #self.push_precollected(self.create_item("Chemist"))
+
+ if self.options.starting_shop_level > 0:
+ shop_level = self.options.starting_shop_level.value
+ self.options.start_inventory_from_pool.value.update({"Progressive Shop Level": shop_level})
+ #for i in range(shop_level):
+ # self.push_precollected(self.create_item("Progressive Shop Level"))
+
+ def create_regions(self):
+ menu = Region("Menu", self.player, self.multiworld)
+ self.multiworld.regions.append(menu)
+ for region_data in all_regions:
+ region = Region(region_data.name, self.player, self.multiworld)
+ self.multiworld.regions.append(region)
+
+ # Will be adjusted for different starting regions.
+ starting_region = self.get_region("Gariland")
+ menu.connect(starting_region)
+ self.options.start_inventory.value[self.starting_pass] = 1
+
+ # Debug lists
+ gallione_locations = []
+ fovoham_locations = []
+ lesalia_locations = []
+ lionel_locations = []
+ zeltennia_locations = []
+ limberry_locations = []
+ murond_locations = []
+
+ # Define connections
+ locations_to_add = []
+ for origin_region_data in all_regions:
+ origin_region = self.get_region(origin_region_data.name)
+ for connection in origin_region_data.connections:
+ connecting_region = self.get_region(connection.destination.name)
+ logic_object = LogicObject(self.player, self.options, 0, self.zodiac_stones_required)
+ if self.debug:
+ print(f"Connection: {origin_region.name} to {connecting_region.name}")
+ logic_object.requirements = create_logic_rule_for_list(
+ connection.requirements,
+ self.options,
+ self.debug)
+ connection_name = f"{origin_region.name} to {connecting_region.name}"
+ new_entrance = Entrance(self.player, connection_name, origin_region)
+ new_entrance.access_rule = logic_object.logic_rule
+ origin_region.exits.append(new_entrance)
+ new_entrance.connect(connecting_region)
+ for location in origin_region_data.locations:
+ if origin_region_data in gallione_regions:
+ gallione_locations.append(location)
+ if origin_region_data in fovoham_regions:
+ fovoham_locations.append(location)
+ if origin_region_data in lesalia_regions:
+ lesalia_locations.append(location)
+ if origin_region_data in lionel_regions:
+ lionel_locations.append(location)
+ if origin_region_data in zeltennia_regions:
+ zeltennia_locations.append(location)
+ if origin_region_data in limberry_regions:
+ limberry_locations.append(location)
+ if origin_region_data in murond_regions:
+ murond_locations.append(location)
+ if not location.check_enabled(self.options):
+ if self.debug:
+ print(f"Excluding {location}")
+ continue
+ new_location = FinalFantasyTacticsIILocation(
+ self.player,
+ location.name,
+ self.location_name_to_id[location.name],
+ origin_region
+ )
+ locations_to_add.append(new_location)
+ locations_to_add.sort(key=lambda loc: location_sort_list_names.index(loc.name))
+ for location in locations_to_add:
+ location.parent_region.locations.append(location)
+ for region in jobs_regions:
+ menu.connect(self.get_region(region.name))
+
+ poach_region = Region("Poaching", self.player, self.multiworld)
+ self.multiworld.regions.append(poach_region)
+ menu.connect(poach_region)
+ if self.options.poach_locations:
+ for poach_location in monster_location_names:
+ new_location = FinalFantasyTacticsIILocation(
+ self.player, poach_location, self.location_name_to_id[poach_location], poach_region
+ )
+ poach_region.locations.append(new_location)
+
+ victory_location = self.get_location(LocationNames.AIRSHIPS_2_STORY.value)
+ victory_location.place_locked_item(self.create_item("Farlem"))
+
+ # Debug print list and number of locations for analysis purposes
+ if self.debug:
+ all_region_locations = {
+ "Gallione": gallione_locations,
+ "Fovoham": fovoham_locations,
+ "Lesalia": lesalia_locations,
+ "Lionel": lionel_locations,
+ "Zeltennia": zeltennia_locations,
+ "Limberry": limberry_locations,
+ "Murond": murond_locations
+ }
+ with open("fftlocations.txt", "w") as file:
+ for key, value in all_region_locations.items():
+ locations = [location.name for location in value if location.name in self.included_locations]
+ file.write(f"{key} Locations ({len(locations)}):\n")
+ for location in locations:
+ file.write(f"{location}\n")
+ file.write("\n")
+
+ #for fight in self.murond_fights:
+ # self.get_location(fight).place_locked_item(self.create_item(self.random.choice(rare_item_names)))
+
+ if self.debug:
+ from Utils import visualize_regions
+ visualize_regions(self.get_region("Menu"), f"fftdiagram{self.player}.puml")
+
+ def create_items(self):
+
+ # Get all world map passes we don't start with
+ world_map_passes = [item for item in world_map_pass_names if item != self.starting_pass]
+ self.push_precollected(self.create_item(self.starting_pass))
+
+ # Squire is always unlocked
+ self.options.start_inventory.value["Squire"] = 1
+ self.push_precollected(self.create_item("Squire"))
+
+ # If we don't have jobs in the pool, we "start" with them all
+ if not self.options.job_unlocks:
+ for job_name in earned_job_names:
+ self.options.start_inventory.value[job_name] = 1
+ self.push_precollected(self.create_item(job_name))
+
+ # Pick Zodiac Stones to place ingame
+ zodiac_stones_in_game = self.random.sample(zodiac_stone_names, k=self.zodiac_stones_in_pool)
+
+ # Handle major items
+ major_items = [
+ *world_map_passes, *shop_levels, *special_character_names, *ramza_job_levels
+ ]
+
+ # Place Zodiac Stones if stones are in vanilla spots. Otherwise, add them to itempool.
+ if self.options.zodiac_stone_locations == self.options.zodiac_stone_locations.option_vanilla_stones:
+ stone_locations = [location.value for location in story_zodiac_stone_locations]
+ if self.options.sidequest_battles:
+ stone_locations.extend([location.value for location in sidequest_zodiac_stone_locations])
+ if self.options.final_battles == self.options.final_battles.option_altima_only:
+ stone_locations.extend([location.value for location in altima_only_story_zodiac_stone_locations])
+ stone_locations_pruned = self.random.sample(stone_locations, k=self.zodiac_stones_in_pool)
+ self.random.shuffle(stone_locations)
+ for stone in zodiac_stones_in_game:
+ self.get_location(stone_locations_pruned.pop()).place_locked_item(self.create_item(stone))
+ else:
+ major_items.extend(zodiac_stones_in_game)
+
+ # Add jobs if they're items
+ if self.options.job_unlocks:
+ major_items.extend(earned_job_names)
+
+ # Get unfilled location count.
+ world_locations = self.multiworld.get_unfilled_locations(self.player)
+ location_count = len(world_locations)
+
+ # Get filler item count and determine filler pool
+ filler_item_count = location_count - len(major_items)
+ filler_items = self.determine_filler_item_pool(filler_item_count)
+
+ # Create items
+ itempool = [*major_items, *filler_items]
+ for item in map(self.create_item, itempool):
+ self.multiworld.itempool.append(item)
+
+ def determine_filler_item_pool(self, filler_item_count: int) -> list[str]:
+
+ filler_lists = []
+ normal_weight = self.options.normal_item_weight.value
+ rare_weight = self.options.rare_item_weight.value
+ gil_weight = self.options.bonus_gil_item_weight.value
+ jp_weight = self.options.jp_boon_item_weight.value
+
+ # Shortcut if everything's the same weight (or if someone gets funny and sets them all to zero)
+ if normal_weight == rare_weight == gil_weight == jp_weight:
+ filler_lists = [shop_item_names, rare_item_names, gil_item_names_weighted, jp_item_names_weighted]
+ else:
+ # One instance of a list per weight value
+ for i in range(normal_weight):
+ filler_lists.append(shop_item_names)
+ for i in range(rare_weight):
+ filler_lists.append(rare_item_names)
+ for i in range(gil_weight):
+ filler_lists.append(gil_item_names_weighted)
+ for i in range(jp_weight):
+ filler_lists.append(jp_item_names_weighted)
+ all_filler = []
+ for filler_list in filler_lists:
+ all_filler.extend(filler_list)
+ filler_set = set(all_filler)
+ self.filler_items = sorted(list(filler_set))
+
+ return_list = []
+ # For every itempool slot, just pull a random item from each pool in order based on weight
+ # Could change this if we want the weights to be random and not static
+ for i in range(filler_item_count):
+ chosen_list = filler_lists[i % len(filler_lists)]
+ chosen_item = self.random.choice(chosen_list)
+ return_list.append(chosen_item)
+ return return_list
+
+ def set_rules(self):
+ # Locations that aren't real don't get rules set, of course
+ for location in all_locations:
+ if location.name not in self.included_locations:
+ continue
+
+ ap_location = self.get_location(location.name)
+ if location.name in monster_location_names:
+ # Poach locations rules
+ monster_object = monster_locations_lookup[location.name[6:]]
+ monster_family_name = monster_family_lookup[monster_object.monster_name]
+ monster_family = [
+ monster_locations_lookup[monster_name.value] for monster_name in
+ monster_families[monster_family_name]
+ ]
+ poach_logic_object = PoachLogicObject(self.player, self.options)
+ poach_logic_object.requirements = monster_object.compiled_requirements
+ breed_logic_object = PoachLogicObject(self.player, self.options)
+ breed_logic_object.requirements = []
+ for monster in monster_family:
+ breed_logic_object.requirements.extend(monster.compiled_requirements)
+ add_rule(
+ ap_location,
+ lambda state,
+ poach_object=poach_logic_object,
+ breed_object=breed_logic_object: poach_object.poach_logic_rule(state) or
+ (breed_object.poach_logic_rule(state) and state.has("Mediator", self.player)))
+ else:
+ # Regular location rules
+ logic_object = LogicObject(self.player, self.options, location.battle_level, self.zodiac_stones_required)
+ if self.debug:
+ print(f"\n{location.name} requirements (Battle level {location.battle_level}):")
+ logic_object.requirements = create_logic_rule_for_list(
+ location.requirements, self.options, self.debug)
+ add_rule(ap_location, logic_object.logic_rule)
+
+ # Victory condition
+ add_rule(
+ self.get_location(LocationNames.AIRSHIPS_2_STORY.value),
+ lambda state: state.has_group("Zodiac Stones", self.player, self.zodiac_stones_required)
+ and state.can_reach_region("Murond Death City", self.player))
+ self.multiworld.completion_condition[self.player] = lambda state: state.has("Farlem", self.player)
+
+ def pre_fill(self) -> None:
+ pass
+
+ def generate_basic(self):
+ pass
+
+ def generate_output(self, output_directory: str) -> None:
+ patch_dict: dict[str, Any] = dict()
+ # Hash of the MW seed to associate with save file
+ patch_dict["SeedHash"] = self.multiworld.seed % 0x7FFF
+ patch_dict["APJobs"] = self.options.job_unlocks.value
+ patch_dict["RareBattles"] = self.options.rare_battles.value
+ patch_dict["Sidequests"] = self.options.sidequest_battles.value
+ patch_dict["FinalBattles"] = self.options.final_battles.value
+ patch_dict["RequiredStones"] = self.zodiac_stones_required
+ patch_dict["EXPMultiplier"] = self.options.exp_gain_multiplier.value
+ patch_dict["JPMultiplier"] = self.options.jp_gain_multiplier.value
+ patch_dict["LocationDict"] = self.create_location_dict()
+
+ rom_name_text = f'FFTII{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:9}'
+ rom_name_text = rom_name_text[:20]
+ rom_name = bytearray(rom_name_text, 'utf-8')
+ rom_name.extend([0] * (20 - len(rom_name)))
+ patch_dict["RomName"] = f'FFTII{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:9}'
+
+ patch_dict["OutputFile"] = f'{self.multiworld.get_out_file_name_base(self.player)}'
+
+ patch = FinalFantasyTacticsIIProcedurePatch(player=self.player, player_name=self.player_name)
+ patch.write_file("patch_file.json", json.dumps(patch_dict).encode("UTF-8"))
+ rom_path = os.path.join(
+ output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}" f"{patch.patch_file_ending}"
+ )
+ patch.write(rom_path)
+
+ def create_location_dict(self):
+ locations = self.get_locations()
+ location_dict = dict()
+ for location in locations:
+ if location.name not in locations_with_text:
+ continue
+ item_locations = [location.name]
+ if location.name in linked_reward_names.keys():
+ for linked_location in linked_reward_names[location.name]:
+ item_locations.append(linked_location)
+ item_strings = []
+ is_any_progression: bool = False
+ for location_name in item_locations:
+ ap_location = self.get_location(location_name)
+ item = ap_location.item
+ if item.classification & ItemClassification.progression:
+ is_any_progression = True
+ classification = ItemClassification.progression
+ elif item.classification & ItemClassification.useful:
+ classification = ItemClassification.useful
+ elif item.classification & ItemClassification.trap:
+ classification = ItemClassification.trap
+ else:
+ classification = ItemClassification.filler
+ player = item.player
+ if player == self.player:
+ text = create_text_for_own_item(item.name, classification)
+ else:
+ other_game = self.multiworld.game[player]
+ is_fft = other_game == "Final Fantasy Tactics Ivalice Island"
+ text = create_text_for_offworld_item(self.multiworld.player_name[player],
+ item.name,
+ classification,
+ is_fft)
+ item_strings.append(text)
+ if len(item_strings) > 1:
+ if len(item_strings) == 2:
+ item_strings[-1] = " and " + item_strings[-1]
+ else:
+ for index, string in enumerate(item_strings[0:-1]):
+ item_strings[index] = string + ", "
+ item_strings[-1] = "and " + item_strings[-1]
+ terminator = "!" if is_any_progression else "."
+ final_item_string = f"{self.player_name}'s party found " + "".join(item_strings) + terminator
+ location_dict[location.name] = final_item_string
+ return location_dict
+
+ def modify_multidata(self, multidata: dict):
+ import base64
+ rom_name_text = f'FFTII{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:9}'
+ rom_name_text = rom_name_text[:20]
+ rom_name = bytearray(rom_name_text, 'utf-8')
+ rom_name.extend([0] * (20 - len(rom_name)))
+ new_name = base64.b64encode(bytes(rom_name)).decode()
+ multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
+
+ def get_filler_item_name(self) -> str:
+ if self.filler_items is None:
+ self.filler_items = ["Potion"]
+ return self.random.choice(self.filler_items)
+
+ def fill_slot_data(self) -> Dict[str, Any]:
+ return_dict = self.options.as_dict(
+ "bonus_gil_item_size",
+ "jp_boon_size",
+ "sidequest_battles",
+ "rare_battles",
+ "final_battles",
+ "poach_locations",
+ "job_unlocks",
+ "logical_difficulty"
+ )
+ return_dict["zodiac_stones_required"] = self.zodiac_stones_required
+ return return_dict
+
+
+class FinalFantasyTacticsIIItem(Item):
+ game = "Final Fantasy Tactics Ivalice Island"
diff --git a/worlds/fftii/archipelago.json b/worlds/fftii/archipelago.json
new file mode 100644
index 000000000000..19c59a618039
--- /dev/null
+++ b/worlds/fftii/archipelago.json
@@ -0,0 +1 @@
+{"game": "Final Fantasy Tactics Ivalice Island", "world_version": "0.1.0", "authors": ["Cerabow", "Toast", "Jumza", "Rosalie"], "compatible_version": 7, "version": 7}
\ No newline at end of file
diff --git a/worlds/fftii/data/__init__.py b/worlds/fftii/data/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/worlds/fftii/data/items.py b/worlds/fftii/data/items.py
new file mode 100644
index 000000000000..871cfeb8bbd5
--- /dev/null
+++ b/worlds/fftii/data/items.py
@@ -0,0 +1,217 @@
+class ItemData:
+ name: str
+ game_id: int
+
+ def __init__(self, name: str, game_id: int):
+ self.name = name
+ self.game_id = game_id
+
+ def __repr__(self):
+ return f"{self.name} ({hex(self.game_id)})"
+
+all_item_data = []
+
+gear_item_names = [
+ "Dagger", "Mythril Knife", "Blind Knife", "Mage Masher", "Platina Dagger", "Main Gauche", "orichalcum",
+ "Assassin Dagger", "Air Knife", "Zorlin Shape",
+
+ "Hidden Knife", "Ninja Knife", "Short Edge", "Ninja Edge", "Spell Edge", "Sasuke Knife", "Iga Knife", "Koga Knife",
+
+ "Broad Sword", "Long Sword", "Iron Sword", "Mythril Sword", "Blood Sword", "Coral Sword", "Ancient Sword",
+ "Sleep Sword", "Platinum Sword", "Diamond Sword", "Ice Brand", "Rune Blade", "Nagrarock", "Materia Blade",
+
+ "Defender", "Save the Queen", "Excalibur", "Ragnarok", "Chaos Blade",
+
+ "Asura Knife", "Koutetsu Knife", "Bizzen Boat", "Murasame", "Heaven's Cloud", "Kiyomori", "Muramasa",
+ "Kikuichimoji", "Masamune", "Chirijiraden",
+
+ "Battle Axe", "Giant Axe", "Slasher",
+
+ "Rod", "Thunder Rod", "Flame Rod", "Ice Rod", "Poison Rod", "Wizard Rod", "Dragon Rod", "Faith Rod",
+
+ "Oak Staff", "White Staff", "Healing Staff", "Rainbow Staff", "Wizard Staff", "Gold Staff", "Mace of Zeus",
+ "Sage Staff",
+
+ "Flail", "Flame Whip", "Morning Star", "Scorpion Tail",
+
+ "Romanda Gun", "Mythril Gun", "Stone Gun", "Blaze Gun", "Glacier Gun", "Blast Gun",
+
+ "Bow Gun", "Night Killer", "Cross Bow", "Poison Bow", "Hunting Bow", "Gastrafitis",
+
+ "Long Bow", "Silver Bow", "Ice Bow", "Lightning Bow", "Windslash Bow", "Mythril Bow", "Ultimus Bow", "Yoichi Bow",
+ "Perseus Bow",
+
+ "Ramia Harp", "Bloody Strings", "Fairy Harp",
+
+ "Battle Dict", "Monster Dict", "Papyrus Plate", "Madlemgen",
+
+ "Javelin", "Spear", "Mythril Spear", "Partisan", "Oberisk", "Holy Lance", "Dragon Whisker", "Javelin 2",
+
+ "Cypress Rod", "Battle Bamboo", "Musk Rod", "Iron Fan", "Gokuu Rod", "Ivory Rod", "Octagon Rod", "Whale Whisker",
+
+ "C Bag", "FS Bag", "P Bag", "H Bag",
+
+ "Persia", "Cashmere", "Ryozan Silk",
+
+ "Shuriken", "Magic Shuriken", "Yagyu Darkness", "Fire Ball", "Water Ball", "Lightning Ball",
+
+ "Escutcheon", "Buckler", "Bronze Shield", "Round Shield", "Mythril Shield", "Gold Shield", "Ice Shield",
+ "Flame Shield", "Aegis Shield", "Diamond Shield", "Platina Shield", "Crystal Shield", "Genji Shield",
+ "Kaiser Plate", "Venetian Shield", "Escutcheon 2",
+
+ "Leather Helmet", "Bronze Helmet", "Iron Helmet", "Barbuta", "Mythril Helmet", "Gold Helmet", "Cross Helmet",
+ "Diamond Helmet", "Platina Helmet", "Circlet", "Crystal Helmet", "Genji Helmet", "Grand Helmet",
+
+ "Leather Hat", "Feather Hat", "Red Hood", "Headgear", "Triangle Hat", "Green Beret", "Twist Headband",
+ "Holy Miter", "Black Hood", "Golden Hairpin", "Flash Hat", "Thief Hat",
+
+ "Cachusha", "Barette", "Ribbon",
+
+ "Leather Armor", "Linen Cuirass", "Bronze Armor", "Chain Mail", "Mythril Armor", "Plate Mail", "Gold Armor",
+ "Diamond Armor", "Platina Armor", "Carabini Mail", "Crystal Mail", "Genji Armor", "Reflect Mail", "Maximillian",
+
+ "Clothes", "Leather Outfit", "Leather Vest", "Chain Vest", "Mythril Vest", "Adaman Vest", "Wizard Outfit",
+ "Brigadine", "Judo Outfit", "Power Sleeve", "Earth Clothes", "Secret Clothes", "Black Costume", "Rubber Costume",
+
+ "Linen Robe", "Silk Robe", "Wizard Robe", "Chameleon Robe", "White Robe", "Black Robe", "Light Robe",
+ "Robe of Lords",
+
+ "Battle Boots", "Spike Shoes", "Germinas Boots", "Rubber Shoes", "Feather Boots", "Sprint Shoes", "Red Shoes",
+
+ "Power Wrist", "Genji Gauntlet", "Magic Gauntlet", "Bracer",
+
+ "Reflect Ring", "Defense Ring", "Magic Ring", "Cursed Ring", "Angel Ring",
+
+ "Diamond Armlet", "Jade Armlet", "108 Gems", "N-Kai Armlet", "Defense Armlet",
+
+ "Small Mantle", "Leather Mantle", "Wizard Mantle", "Elf Mantle", "Dracula Mantle", "Feather Mantle",
+ "Vanish Mantle",
+
+ "Chantage", "Cherche", "Setiemson", "Salty Rage",
+
+ "Potion", "Hi-Potion", "X-Potion", "Ether", "Hi-Ether", "Elixir", "Antidote", "Eye Drop", "Echo Grass",
+ "Maiden's Kiss", "Soft", "Holy Water", "Remedy", "Phoenix Down"
+]
+
+
+for index, item_name in enumerate(gear_item_names, start=1):
+ all_item_data.append(ItemData(item_name, index))
+
+item_data_lookup = {item.name: item for item in all_item_data}
+
+zodiac_stone_names = [
+ "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra",
+ "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces", "Serpentarius",
+]
+
+world_map_pass_names = [
+ "Gallione Pass", "Lesalia Pass", "Fovoham Pass", "Lionel Pass", "Zeltennia Pass", "Limberry Pass", "Murond Pass"
+]
+
+job_names = [
+ "Squire", "Chemist", "Knight", "Archer", "Monk", "Thief", "Lancer", "Geomancer", "Samurai", "Ninja", "Dancer",
+ "Priest", "Wizard", "Oracle", "Time Mage", "Mediator", "Summoner", "Calculator", "Bard", "Mime"
+]
+
+earned_job_names = [job for job in job_names if job != "Squire"]
+
+shop_levels = []
+
+for i in range(14):
+ shop_levels.append("Progressive Shop Level")
+
+major_item_names = [
+ *zodiac_stone_names, *world_map_pass_names, *job_names, "Progressive Shop Level", "Progressive Ramza Job Form"
+]
+
+nonbalanced_major_item_names = [*job_names, "Progressive Shop Level"]
+
+ramza_job_levels = [
+ "Progressive Ramza Job Form", "Progressive Ramza Job Form"
+]
+
+
+special_character_names = [
+ "Boco", "Rad", "Alicia", "Lavian", "Agrias", "Mustadio", "Rafa",
+ "Malak", "Beowulf", "Reis (Dragon)", "Reis (Human)", "Orlandu",
+ "Worker 8", "Cloud", "Meliadoul", "Byblos"
+]
+
+rare_item_names = [
+ "Zorlin Shape", "Sasuke Knife", "Iga Knife", "Koga Knife", "Nagrarock", "Materia Blade", "Defender",
+ "Save the Queen", "Excalibur", "Ragnarok", "Chaos Blade", "Masamune", "Chirijiraden", "Faith Rod", "Healing Staff",
+ "Mace of Zeus", "Sage Staff", "Scorpion Tail", "Stone Gun", "Blaze Gun", "Glacier Gun", "Blast Gun",
+ "Ultimus Bow", "Yoichi Bow", "Perseus Bow", "Fairy Harp", "Madlemgen", "Holy Lance", "Dragon Whisker",
+ "Javelin 2", "Ivory Rod", "Whale Whisker", "FS Bag", "Ryozan Silk", "Genji Shield", "Kaiser Plate",
+ "Venetian Shield", "Escutcheon 2", "Grand Helmet", "Genji Helmet", "Cachusha", "Barette", "Ribbon",
+ "Genji Armor", "Maximillian", "Secret Clothes", "Rubber Costume", "Robe of Lords", "Genji Gauntlet",
+ "Cursed Ring", "Vanish Mantle", "Chantage", "Cherche", "Salty Rage", "Setiemson"
+]
+
+gil_item_names = [
+ "Bonus Gil: l i t t l e m o n e y",
+ "Bonus Gil: m o r e m o n e y",
+ "Bonus Gil: l o t s o f m o n e y"
+]
+
+gil_item_names_weighted = [
+ "Bonus Gil: l i t t l e m o n e y",
+ "Bonus Gil: l i t t l e m o n e y",
+ "Bonus Gil: l i t t l e m o n e y",
+ "Bonus Gil: l i t t l e m o n e y",
+ "Bonus Gil: m o r e m o n e y",
+ "Bonus Gil: m o r e m o n e y",
+ "Bonus Gil: m o r e m o n e y",
+ "Bonus Gil: l o t s o f m o n e y",
+ "Bonus Gil: l o t s o f m o n e y"
+]
+
+gil_item_sizes = [
+ {
+ "Bonus Gil: l i t t l e m o n e y": 1000,
+ "Bonus Gil: m o r e m o n e y": 5000,
+ "Bonus Gil: l o t s o f m o n e y": 20000
+ },
+ {
+ "Bonus Gil: l i t t l e m o n e y": 2000,
+ "Bonus Gil: m o r e m o n e y": 10000,
+ "Bonus Gil: l o t s o f m o n e y": 40000
+ },
+ {
+ "Bonus Gil: l i t t l e m o n e y": 4000,
+ "Bonus Gil: m o r e m o n e y": 20000,
+ "Bonus Gil: l o t s o f m o n e y": 80000
+ }
+]
+
+jp_item_names = [
+ "Small JP Boon", "Medium JP Boon", "Large JP Boon"
+]
+
+jp_item_names_weighted = [
+ "Small JP Boon", "Small JP Boon", "Small JP Boon", "Small JP Boon",
+ "Medium JP Boon", "Medium JP Boon", "Medium JP Boon",
+ "Large JP Boon", "Large JP Boon",
+]
+
+jp_item_sizes = [
+ {"Small JP Boon": 25, "Medium JP Boon": 50, "Large JP Boon": 125},
+ {"Small JP Boon": 50, "Medium JP Boon": 100, "Large JP Boon": 250},
+ {"Small JP Boon": 100, "Medium JP Boon": 200, "Large JP Boon": 500}
+]
+
+useful_item_names = [
+ *rare_item_names, *special_character_names, *jp_item_names
+]
+
+shop_item_names = [
+ item for item in gear_item_names if item not in useful_item_names
+]
+
+filler_item_names = [
+ *shop_item_names, *gil_item_names, *jp_item_names
+]
+
+all_item_names = [
+ *major_item_names, *special_character_names, *gear_item_names, *gil_item_names, *jp_item_names
+]
diff --git a/worlds/fftii/data/locations.py b/worlds/fftii/data/locations.py
new file mode 100644
index 000000000000..e0c1555d37b5
--- /dev/null
+++ b/worlds/fftii/data/locations.py
@@ -0,0 +1,387 @@
+from .logic.FFTLocation import LocationNames
+from .logic.FFTRegion import FFTRegion
+from .logic.Monsters import monster_locations
+from .logic.regions.Fovoham import fovoham_regions
+from .logic.regions.Gallione import gallione_regions
+from .logic.regions.Jobs import jobs_regions
+from .logic.regions.Lesalia import lesalia_regions
+from .logic.regions.Limberry import limberry_regions
+from .logic.regions.Lionel import lionel_regions
+from .logic.regions.Murond import murond_regions
+from .logic.regions.Zeltennia import zeltennia_regions
+
+world_map_regions: list[FFTRegion] = [
+ *gallione_regions,
+ *fovoham_regions,
+ *lesalia_regions,
+ *lionel_regions,
+ *zeltennia_regions,
+ *limberry_regions,
+ *murond_regions
+]
+
+menu_regions: list[FFTRegion] = [
+ *jobs_regions
+]
+
+all_regions: list[FFTRegion] = [
+ *world_map_regions, *menu_regions
+]
+
+story_battle_locations: list[LocationNames] = [
+ LocationNames.GARILAND_STORY,
+ LocationNames.MANDALIA_STORY,
+ LocationNames.IGROS_STORY,
+ LocationNames.SWEEGY_STORY,
+ LocationNames.DORTER_1_STORY,
+ LocationNames.DORTER_2_STORY,
+ LocationNames.THIEVES_FORT_STORY,
+ LocationNames.LENALIA_STORY,
+ LocationNames.ZEAKDEN_STORY,
+ LocationNames.GROG_STORY,
+ LocationNames.YARDOW_STORY,
+ LocationNames.YUGUO_STORY,
+ LocationNames.RIOVANES_1_STORY,
+ LocationNames.RIOVANES_2_STORY,
+ LocationNames.RIOVANES_3_STORY,
+ LocationNames.FOVOHAM_STORY,
+ LocationNames.ARAGUAY_STORY,
+ LocationNames.ZIREKILE_STORY,
+ LocationNames.ZEKLAUS_STORY,
+ LocationNames.LESALIA_STORY,
+ LocationNames.GOLAND_STORY,
+ LocationNames.ZALAND_STORY,
+ LocationNames.BARIAUS_HILL_STORY,
+ LocationNames.LIONEL_1_STORY,
+ LocationNames.LIONEL_2_STORY,
+ LocationNames.ZIGOLIS_STORY,
+ LocationNames.GOLGORAND_STORY,
+ LocationNames.BARIAUS_VALLEY_STORY,
+ LocationNames.DOGUOLA_STORY,
+ LocationNames.BERVENIA_CITY_STORY,
+ LocationNames.FINATH_STORY,
+ LocationNames.ZELTENNIA_STORY,
+ LocationNames.GERMINAS_STORY,
+ LocationNames.BETHLA_NORTH_STORY,
+ LocationNames.BETHLA_SOUTH_STORY,
+ LocationNames.BETHLA_SLUICE_STORY,
+ LocationNames.BED_STORY,
+ LocationNames.LIMBERRY_1_STORY,
+ LocationNames.LIMBERRY_2_STORY,
+ LocationNames.LIMBERRY_3_STORY,
+ LocationNames.POESKAS_STORY,
+ LocationNames.MUROND_TEMPLE_1_STORY,
+ LocationNames.MUROND_TEMPLE_2_STORY,
+ LocationNames.MUROND_TEMPLE_3_STORY,
+ LocationNames.UBS_1_STORY,
+ LocationNames.UBS_2_STORY,
+ LocationNames.UBS_3_STORY,
+ LocationNames.UBS_4_STORY,
+ LocationNames.UBS_5_STORY,
+ LocationNames.GOUG_STORY,
+ LocationNames.MUROND_DEATH_CITY_STORY,
+ LocationNames.PRECINCTS_STORY,
+ LocationNames.AIRSHIPS_1_STORY,
+ LocationNames.AIRSHIPS_2_STORY
+]
+
+story_character_recruit_locations: list[LocationNames] = [
+ LocationNames.RAD_RECRUIT,
+ LocationNames.ALICIA_RECRUIT,
+ LocationNames.LAVIAN_RECRUIT,
+ LocationNames.BOCO_RECRUIT,
+ LocationNames.MUSTADIO_RECRUIT,
+ LocationNames.AGRIAS_RECRUIT,
+ LocationNames.RAFA_RECRUIT,
+ LocationNames.MALAK_RECRUIT,
+ LocationNames.ORLANDU_RECRUIT,
+ LocationNames.MELIADOUL_RECRUIT
+]
+
+character_recruit_locations: list[LocationNames] = [
+ LocationNames.RAD_RECRUIT,
+ LocationNames.ALICIA_RECRUIT,
+ LocationNames.LAVIAN_RECRUIT,
+ LocationNames.RAFA_RECRUIT,
+ LocationNames.MALAK_RECRUIT,
+ LocationNames.BOCO_RECRUIT,
+ LocationNames.BEOWULF_RECRUIT,
+ LocationNames.WORKER_8_RECRUIT,
+ LocationNames.AGRIAS_RECRUIT,
+ LocationNames.REIS_DRAGON_RECRUIT,
+ LocationNames.REIS_HUMAN_RECRUIT,
+ LocationNames.CLOUD_RECRUIT,
+ LocationNames.ORLANDU_RECRUIT,
+ LocationNames.MELIADOUL_RECRUIT,
+ LocationNames.MUSTADIO_RECRUIT,
+ LocationNames.BYBLOS_RECRUIT
+]
+
+sidequest_battles: list[LocationNames] = [
+ LocationNames.GOLAND_1_SIDEQUEST,
+ LocationNames.GOLAND_2_SIDEQUEST,
+ LocationNames.GOLAND_3_SIDEQUEST,
+ LocationNames.GOLAND_4_SIDEQUEST,
+ LocationNames.NELVESKA_SIDEQUEST,
+ LocationNames.ZARGHIDAS_SIDEQUEST,
+ LocationNames.NOGIAS_SIDEQUEST,
+ LocationNames.TERMINATE_SIDEQUEST,
+ LocationNames.DELTA_SIDEQUEST,
+ LocationNames.VALKYRIES_SIDEQUEST,
+ LocationNames.MLAPAN_SIDEQUEST,
+ LocationNames.TIGER_SIDEQUEST,
+ LocationNames.BRIDGE_SIDEQUEST,
+ LocationNames.VOYAGE_SIDEQUEST,
+ LocationNames.HORROR_SIDEQUEST,
+ LocationNames.END_SIDEQUEST,
+]
+
+dd_locations: list[LocationNames] = [
+ LocationNames.NOGIAS_SIDEQUEST,
+ LocationNames.TERMINATE_SIDEQUEST,
+ LocationNames.DELTA_SIDEQUEST,
+ LocationNames.VALKYRIES_SIDEQUEST,
+ LocationNames.MLAPAN_SIDEQUEST,
+ LocationNames.TIGER_SIDEQUEST,
+ LocationNames.BRIDGE_SIDEQUEST,
+ LocationNames.VOYAGE_SIDEQUEST,
+ LocationNames.HORROR_SIDEQUEST,
+ LocationNames.END_SIDEQUEST,
+]
+
+dd_location_names: list[str] = [location.value for location in dd_locations]
+
+sidequest_battle_locations: list[LocationNames] = [
+ *sidequest_battles,
+ LocationNames.BEOWULF_RECRUIT,
+ LocationNames.WORKER_8_RECRUIT,
+ LocationNames.REIS_DRAGON_RECRUIT,
+ LocationNames.REIS_HUMAN_RECRUIT,
+ LocationNames.CLOUD_RECRUIT,
+ LocationNames.BYBLOS_RECRUIT
+]
+
+rare_battle_locations: list[LocationNames] = [
+ LocationNames.MANDALIA_RARE,
+ LocationNames.SWEEGY_RARE,
+ LocationNames.LENALIA_RARE,
+ LocationNames.GROG_RARE,
+ LocationNames.YUGUO_RARE,
+ LocationNames.FOVOHAM_RARE,
+ LocationNames.ARAGUAY_RARE,
+ LocationNames.ZIREKILE_RARE,
+ LocationNames.ZEKLAUS_RARE,
+ LocationNames.BERVENIA_VOLCANO_RARE,
+ LocationNames.DOGUOLA_RARE,
+ LocationNames.BARIAUS_HILL_RARE,
+ LocationNames.ZIGOLIS_RARE,
+ LocationNames.BARIAUS_VALLEY_RARE,
+ LocationNames.FINATH_RARE,
+ LocationNames.GERMINAS_RARE,
+ LocationNames.BED_RARE,
+ LocationNames.DOLBODAR_RARE,
+ LocationNames.POESKAS_RARE
+]
+
+rare_battle_location_names: list[str] = [location.value for location in rare_battle_locations]
+
+job_unlock_locations: list[LocationNames] = [
+ LocationNames.SQUIRE_UNLOCK,
+ LocationNames.CHEMIST_UNLOCK,
+ LocationNames.KNIGHT_UNLOCK,
+ LocationNames.ARCHER_UNLOCK,
+ LocationNames.THIEF_UNLOCK,
+ LocationNames.MONK_UNLOCK,
+ LocationNames.PRIEST_UNLOCK,
+ LocationNames.WIZARD_UNLOCK,
+ LocationNames.TIME_MAGE_UNLOCK,
+ LocationNames.SUMMONER_UNLOCK,
+ LocationNames.ORACLE_UNLOCK,
+ LocationNames.MEDIATOR_UNLOCK,
+ LocationNames.GEOMANCER_UNLOCK,
+ LocationNames.LANCER_UNLOCK,
+ LocationNames.SAMURAI_UNLOCK,
+ LocationNames.NINJA_UNLOCK,
+ LocationNames.CALCULATOR_UNLOCK,
+ LocationNames.BARD_UNLOCK,
+ LocationNames.DANCER_UNLOCK,
+ LocationNames.MIME_UNLOCK
+]
+
+shop_unlock_locations: list[LocationNames] = [
+ LocationNames.MANDALIA_SHOP,
+ LocationNames.LENALIA_SHOP,
+ LocationNames.ZEAKDEN_SHOP,
+ LocationNames.YARDOW_SHOP,
+ LocationNames.RIOVANES_SHOP,
+ LocationNames.ZIREKILE_SHOP,
+ LocationNames.ZEKLAUS_SHOP,
+ LocationNames.LESALIA_SHOP,
+ LocationNames.BARIAUS_HILL_SHOP,
+ LocationNames.LIONEL_SHOP,
+ LocationNames.BARIAUS_VALLEY_SHOP,
+ LocationNames.BETHLA_SHOP,
+ LocationNames.LIMBERRY_SHOP,
+ LocationNames.ORBONNE_SHOP
+]
+
+ramza_job_unlock_locations: list[LocationNames] = [
+ LocationNames.RAMZA_CHAPTER_2_UNLOCK,
+ LocationNames.RAMZA_CHAPTER_4_UNLOCK
+]
+
+default_murond_fights: list[LocationNames] = [
+ LocationNames.UBS_4_STORY,
+ LocationNames.UBS_5_STORY,
+ LocationNames.MUROND_DEATH_CITY_STORY,
+ LocationNames.PRECINCTS_STORY,
+ LocationNames.AIRSHIPS_1_STORY
+]
+
+story_zodiac_stone_locations: list[LocationNames] = [
+ LocationNames.IGROS_STORY, # Capricorn
+ LocationNames.RIOVANES_3_STORY, # Pisces
+ LocationNames.RIOVANES_2_STORY, # Aries
+ LocationNames.GOUG_STORY, # Taurus
+ LocationNames.LIMBERRY_2_STORY, # Gemini
+ # "Graveyard of Airships 1 Story Battle", # Leo
+ # "Graveyard of Airships 2 Story Battle", # Virgo
+ LocationNames.BETHLA_SLUICE_STORY, # Libra
+ LocationNames.LIONEL_2_STORY, # Scorpio
+ LocationNames.LIMBERRY_3_STORY, # Sagittarius
+]
+
+altima_only_story_zodiac_stone_locations: list[LocationNames] = [
+ LocationNames.AIRSHIPS_1_STORY
+]
+
+sidequest_zodiac_stone_locations: list[LocationNames] = [
+ LocationNames.GOLAND_4_SIDEQUEST,
+ LocationNames.NELVESKA_SIDEQUEST,
+ LocationNames.END_SIDEQUEST,
+]
+
+linked_rewards: dict[LocationNames, list[LocationNames]] = {
+ LocationNames.MANDALIA_STORY: [
+ LocationNames.MANDALIA_SHOP
+ ],
+ LocationNames.LENALIA_STORY: [
+ LocationNames.LENALIA_SHOP,
+ ],
+ LocationNames.ZEAKDEN_STORY: [
+ LocationNames.ZEAKDEN_SHOP,
+ LocationNames.RAMZA_CHAPTER_2_UNLOCK,
+ LocationNames.RAD_RECRUIT,
+ LocationNames.ALICIA_RECRUIT,
+ LocationNames.LAVIAN_RECRUIT
+ ],
+ LocationNames.YARDOW_STORY: [
+ LocationNames.YARDOW_SHOP
+ ],
+ LocationNames.RIOVANES_3_STORY: [
+ LocationNames.RIOVANES_SHOP,
+ LocationNames.RAFA_RECRUIT,
+ LocationNames.MALAK_RECRUIT,
+ LocationNames.RAMZA_CHAPTER_4_UNLOCK
+ ],
+ LocationNames.ZIREKILE_STORY: [
+ LocationNames.ZIREKILE_SHOP
+ ],
+ LocationNames.ZEKLAUS_STORY: [
+ LocationNames.ZEKLAUS_SHOP
+ ],
+ LocationNames.ARAGUAY_STORY: [
+ LocationNames.BOCO_RECRUIT
+ ],
+ LocationNames.GOLAND_4_SIDEQUEST: [
+ LocationNames.BEOWULF_RECRUIT,
+ LocationNames.REIS_DRAGON_RECRUIT,
+ LocationNames.WORKER_8_RECRUIT
+ ],
+ LocationNames.LESALIA_STORY: [
+ LocationNames.LESALIA_SHOP
+ ],
+ LocationNames.BARIAUS_HILL_STORY: [
+ LocationNames.BARIAUS_HILL_SHOP
+ ],
+ LocationNames.LIONEL_2_STORY: [
+ LocationNames.LIONEL_SHOP
+ ],
+ LocationNames.BARIAUS_VALLEY_STORY: [
+ LocationNames.BARIAUS_VALLEY_SHOP,
+ LocationNames.AGRIAS_RECRUIT
+ ],
+ LocationNames.NELVESKA_SIDEQUEST: [
+ LocationNames.REIS_HUMAN_RECRUIT
+ ],
+ LocationNames.ZARGHIDAS_SIDEQUEST: [
+ LocationNames.CLOUD_RECRUIT
+ ],
+ LocationNames.BETHLA_SLUICE_STORY: [
+ LocationNames.BETHLA_SHOP,
+ LocationNames.ORLANDU_RECRUIT
+ ],
+ LocationNames.LIMBERRY_3_STORY: [
+ LocationNames.LIMBERRY_SHOP,
+ LocationNames.MELIADOUL_RECRUIT
+ ],
+ LocationNames.UBS_1_STORY: [
+ LocationNames.ORBONNE_SHOP
+ ],
+ LocationNames.GOUG_STORY: [
+ LocationNames.MUSTADIO_RECRUIT
+ ],
+ LocationNames.END_SIDEQUEST: [
+ LocationNames.BYBLOS_RECRUIT
+ ]
+}
+
+linked_reward_names: dict[str, list[str]] = {}
+for location, value in linked_rewards.items():
+ new_list = []
+ for linked_location in value:
+ new_list.append(linked_location.value)
+ linked_reward_names[location.value] = new_list
+
+
+locations_with_text: list[str] = []
+
+for location in story_battle_locations:
+ locations_with_text.append(location.value)
+
+for location in sidequest_battles:
+ locations_with_text.append(location.value)
+
+for location in rare_battle_locations:
+ locations_with_text.append(location.value)
+
+monster_location_names: list[str] = [f"Poach {monster.monster_name.value}" for monster in monster_locations]
+
+location_sort_list: list[LocationNames] = [
+ *story_battle_locations,
+ *shop_unlock_locations,
+ *ramza_job_unlock_locations,
+ *character_recruit_locations,
+ *sidequest_battles,
+ *rare_battle_locations,
+ *job_unlock_locations
+]
+
+location_sort_list_names: list[str] = [
+ location.value for location in location_sort_list
+]
+
+location_groups = {
+ "Story Battles": [
+ location.value for location in [
+ *story_battle_locations,
+ *shop_unlock_locations,
+ *ramza_job_unlock_locations,
+ *story_character_recruit_locations
+ ]
+ ],
+ "Sidequest Battles": [location.value for location in sidequest_battles],
+ "Rare Battles": rare_battle_location_names,
+ "Poaches": monster_location_names,
+ "Job Unlocks": [location.value for location in job_unlock_locations]
+}
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/Connection.py b/worlds/fftii/data/logic/Connection.py
new file mode 100644
index 000000000000..ca9d4334113b
--- /dev/null
+++ b/worlds/fftii/data/logic/Connection.py
@@ -0,0 +1,16 @@
+from typing import TYPE_CHECKING
+
+from .Requirement import Requirement
+
+if TYPE_CHECKING:
+ from .FFTRegion import FFTRegion
+
+class Connection:
+ destination: "FFTRegion"
+ requirements: list[Requirement]
+
+ def __init__(self, destination, requirements = None):
+ if requirements is None:
+ requirements = list()
+ self.destination = destination
+ self.requirements = requirements
diff --git a/worlds/fftii/data/logic/FFTLocation.py b/worlds/fftii/data/logic/FFTLocation.py
new file mode 100644
index 000000000000..3e2160aa8b69
--- /dev/null
+++ b/worlds/fftii/data/logic/FFTLocation.py
@@ -0,0 +1,189 @@
+from enum import Enum
+
+from .Requirement import Requirement
+from ...Options import FinalFantasyTacticsIIOptions
+
+
+class FFTLocation:
+ name: str
+ requirements: list[Requirement]
+ battle_level: int
+
+ def __init__(self, name: "LocationNames", requirements: list[Requirement] = None, battle_level: int = None):
+ if requirements is None:
+ requirements = list()
+ self.name = name.value
+ self.requirements = requirements
+ assert battle_level is not None, self.name
+ self.battle_level = battle_level
+
+ def __repr__(self):
+ return f"{self.__class__} -- {self.name}"
+
+ def check_enabled(self, options: FinalFantasyTacticsIIOptions):
+ return True
+
+class SidequestLocation(FFTLocation):
+ def check_enabled(self, options: FinalFantasyTacticsIIOptions):
+ return options.sidequest_battles
+
+class RareBattleLocation(FFTLocation):
+ def check_enabled(self, options: FinalFantasyTacticsIIOptions):
+ return options.rare_battles
+
+class VanillaFinalFightsLocation(FFTLocation):
+ def check_enabled(self, options: FinalFantasyTacticsIIOptions):
+ return options.final_battles == options.final_battles.option_vanilla
+
+class AltimaOnlyFinalFightLocation(FFTLocation):
+ def check_enabled(self, options: FinalFantasyTacticsIIOptions):
+ return options.final_battles == options.final_battles.option_altima_only
+
+class LocationNames(Enum):
+ GARILAND_STORY = "Gariland Magic City Story Battle"
+ MANDALIA_STORY = "Mandalia Plains Story Battle"
+ IGROS_STORY = "Igros Castle Story Battle"
+ SWEEGY_STORY = "Sweegy Woods Story Battle"
+ DORTER_1_STORY = "Dorter Slums Story Battle"
+ DORTER_2_STORY = "Dorter City Story Battle"
+ THIEVES_FORT_STORY = "Thieves' Fort Story Battle"
+ LENALIA_STORY = "Lenalia Plateau Story Battle"
+ ZEAKDEN_STORY = "Fort Zeakden Story Battle"
+ GROG_STORY = "Grog Hill Story Battle"
+ YARDOW_STORY = "Yardow Fort City Story Battle"
+ YUGUO_STORY = "Yuguo Woods Story Battle"
+ RIOVANES_1_STORY = "Gate of Riovanes Story Battle"
+ RIOVANES_2_STORY = "Inside Riovanes Castle Story Battle"
+ RIOVANES_3_STORY = "Roof of Riovanes Castle Story Battle"
+ FOVOHAM_STORY = "Fovoham Plains Story Battle"
+ ARAGUAY_STORY = "Araguay Woods Story Battle"
+ ZIREKILE_STORY = "Zirekile Falls Story Battle"
+ ZEKLAUS_STORY = "Zeklaus Desert Story Battle"
+ LESALIA_STORY = "Back Gate of Lesalia Castle Story Battle"
+ GOLAND_STORY = "Goland Coal City Story Battle"
+ DOGUOLA_STORY = "Doguola Pass Story Battle"
+ ZALAND_STORY = "Zaland Fort City Story Battle"
+ BARIAUS_HILL_STORY = "Bariaus Hill Story Battle"
+ LIONEL_1_STORY = "Gate of Lionel Castle Story Battle"
+ LIONEL_2_STORY = "Inside of Lionel Castle Story Battle"
+ ZIGOLIS_STORY = "Zigolis Swamp Story Battle"
+ GOLGORAND_STORY = "Golgorand Execution Site Story Battle"
+ BARIAUS_VALLEY_STORY = "Bariaus Valley Story Battle"
+ BERVENIA_CITY_STORY = "Bervenia Free City Story Battle"
+ FINATH_STORY = "Finath River Story Battle"
+ ZELTENNIA_STORY = "Zeltennia Castle Story Battle"
+ GERMINAS_STORY = "Germinas Peak Story Battle"
+ BETHLA_NORTH_STORY = "Bethla Garrison North Wall Story Battle"
+ BETHLA_SOUTH_STORY = "Bethla Garrison South Wall Story Battle"
+ BETHLA_SLUICE_STORY = "Bethla Garrison Sluice Story Battle"
+ BED_STORY = "Bed Desert Story Battle"
+ LIMBERRY_1_STORY = "Limberry Castle Gates Story Battle"
+ LIMBERRY_2_STORY = "Inside Limberry Castle Story Battle"
+ LIMBERRY_3_STORY = "Limberry Castle Cemetary Story Battle"
+ POESKAS_STORY = "Poeskas Lake Story Battle"
+ MUROND_TEMPLE_1_STORY = "St. Murond Temple Story Battle"
+ MUROND_TEMPLE_2_STORY = "St. Murond Temple Hall Story Battle"
+ MUROND_TEMPLE_3_STORY = "Chapel of St. Murond Temple Story Battle"
+ UBS_1_STORY = "Underground Book Storage 1 Story Battle"
+ UBS_2_STORY = "Underground Book Storage 2 Story Battle"
+ UBS_3_STORY = "Underground Book Storage 3 Story Battle"
+ UBS_4_STORY = "Underground Book Storage 4 Story Battle"
+ UBS_5_STORY = "Underground Book Storage 5 Story Battle"
+ GOUG_STORY = "Slums of Goug Story Battle"
+ MUROND_DEATH_CITY_STORY = "Murond Death City Story Battle"
+ PRECINCTS_STORY = "Lost Sacred Precincts Story Battle"
+ AIRSHIPS_1_STORY = "Graveyard of Airships 1 Story Battle"
+ AIRSHIPS_2_STORY = "Graveyard of Airships 2 Story Battle"
+
+ GOLAND_1_SIDEQUEST = "Goland Colliery Third Floor Sidequest Battle"
+ GOLAND_2_SIDEQUEST = "Goland Colliery Second Floor Sidequest Battle"
+ GOLAND_3_SIDEQUEST = "Goland Colliery First Floor Sidequest Battle"
+ GOLAND_4_SIDEQUEST = "Goland Underground Passage Sidequest Battle"
+ NELVESKA_SIDEQUEST = "Nelveska Temple Sidequest Battle"
+ ZARGHIDAS_SIDEQUEST = "Zarghidas Trade City Sidequest Battle"
+ NOGIAS_SIDEQUEST = "NOGIAS Sidequest Battle"
+ TERMINATE_SIDEQUEST = "TERMINATE Sidequest Battle"
+ DELTA_SIDEQUEST = "DELTA Sidequest Battle"
+ VALKYRIES_SIDEQUEST = "VALKYRIES Sidequest Battle"
+ MLAPAN_SIDEQUEST = "MLAPAN Sidequest Battle"
+ TIGER_SIDEQUEST = "TIGER Sidequest Battle"
+ BRIDGE_SIDEQUEST = "BRIDGE Sidequest Battle"
+ VOYAGE_SIDEQUEST = "VOYAGE Sidequest Battle"
+ HORROR_SIDEQUEST = "HORROR Sidequest Battle"
+ END_SIDEQUEST = "END Sidequest Battle"
+
+ MANDALIA_RARE = "Mandalia Plains Rare Battle"
+ SWEEGY_RARE = "Sweegy Woods Rare Battle"
+ LENALIA_RARE = "Lenalia Plateau Rare Battle"
+ GROG_RARE = "Grog Hill Rare Battle"
+ YUGUO_RARE = "Yuguo Woods Rare Battle"
+ FOVOHAM_RARE = "Fovoham Plains Rare Battle"
+ ARAGUAY_RARE = "Araguay Woods Rare Battle"
+ ZIREKILE_RARE = "Zirekile Falls Rare Battle"
+ ZEKLAUS_RARE = "Zeklaus Desert Rare Battle"
+ BERVENIA_VOLCANO_RARE = "Bervenia Volvano Rare Battle"
+ DOGUOLA_RARE = "Doguola Pass Rare Battle"
+ BARIAUS_HILL_RARE = "Bariaus Hill Rare Battle"
+ ZIGOLIS_RARE = "Zigolis Swamp Rare Battle"
+ BARIAUS_VALLEY_RARE = "Bariaus Valley Rare Battle"
+ FINATH_RARE = "Finath River Rare Battle"
+ GERMINAS_RARE = "Germinas Peak Rare Battle"
+ BED_RARE = "Bed Desert Rare Battle"
+ DOLBODAR_RARE = "Dolbodar Swamp Rare Battle"
+ POESKAS_RARE = "Poeskas Lake Rare Battle"
+
+ RAD_RECRUIT = "Recruit Rad"
+ ALICIA_RECRUIT = "Recruit Alicia"
+ LAVIAN_RECRUIT = "Recruit Lavian"
+ RAFA_RECRUIT = "Recruit Rafa"
+ MALAK_RECRUIT = "Recruit Malak"
+ BOCO_RECRUIT = "Recruit Boco"
+ BEOWULF_RECRUIT = "Recruit Beowulf"
+ WORKER_8_RECRUIT = "Recruit Worker 8"
+ AGRIAS_RECRUIT = "Recruit Agrias"
+ REIS_DRAGON_RECRUIT = "Recruit Reis (Dragon)"
+ REIS_HUMAN_RECRUIT = "Recruit Reis (Human)"
+ CLOUD_RECRUIT = "Recruit Cloud"
+ ORLANDU_RECRUIT = "Recruit Orlandu"
+ MELIADOUL_RECRUIT = "Recruit Meliadoul"
+ MUSTADIO_RECRUIT = "Recruit Mustadio"
+ BYBLOS_RECRUIT = "Recruit Byblos"
+
+ SQUIRE_UNLOCK = "Squire Unlock"
+ CHEMIST_UNLOCK = "Chemist Unlock"
+ KNIGHT_UNLOCK = "Knight Unlock"
+ ARCHER_UNLOCK = "Archer Unlock"
+ THIEF_UNLOCK = "Thief Unlock"
+ MONK_UNLOCK = "Monk Unlock"
+ PRIEST_UNLOCK = "Priest Unlock"
+ WIZARD_UNLOCK = "Wizard Unlock"
+ TIME_MAGE_UNLOCK = "Time Mage Unlock"
+ SUMMONER_UNLOCK = "Summoner Unlock"
+ ORACLE_UNLOCK = "Oracle Unlock"
+ MEDIATOR_UNLOCK = "Mediator Unlock"
+ GEOMANCER_UNLOCK = "Geomancer Unlock"
+ LANCER_UNLOCK = "Lancer Unlock"
+ SAMURAI_UNLOCK = "Samurai Unlock"
+ NINJA_UNLOCK = "Ninja Unlock"
+ CALCULATOR_UNLOCK = "Calculator Unlock"
+ BARD_UNLOCK = "Bard Unlock"
+ DANCER_UNLOCK = "Dancer Unlock"
+ MIME_UNLOCK = "Mime Unlock"
+
+ MANDALIA_SHOP = "Mandalia Plains Shop Unlock"
+ LENALIA_SHOP = "Lenalia Plateau Shop Unlock"
+ ZEAKDEN_SHOP = "Fort Zeakden Shop Unlock"
+ YARDOW_SHOP = "Yardow Fort City Shop Unlock"
+ RIOVANES_SHOP = "Riovanes Castle Shop Unlock"
+ ZIREKILE_SHOP = "Zirekile Falls Shop Unlock"
+ ZEKLAUS_SHOP = "Zeklaus Desert Shop Unlock"
+ LESALIA_SHOP = "Lesalia Imperial Capital Shop Unlock"
+ BARIAUS_HILL_SHOP = "Bariaus Hill Shop Unlock"
+ LIONEL_SHOP = "Lionel Castle Shop Unlock"
+ BARIAUS_VALLEY_SHOP = "Bariaus Valley Shop Unlock"
+ BETHLA_SHOP = "Bethla Garrison Shop Unlock"
+ LIMBERRY_SHOP = "Limberry Castle Shop Unlock"
+ ORBONNE_SHOP = "Orbonne Monastery Shop Unlock"
+
+ RAMZA_CHAPTER_2_UNLOCK = "Chapter 2 Ramza Squire Job Unlock"
+ RAMZA_CHAPTER_4_UNLOCK = "Chapter 4 Ramza Squire Job Unlock"
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/FFTRegion.py b/worlds/fftii/data/logic/FFTRegion.py
new file mode 100644
index 000000000000..969f9dadf04e
--- /dev/null
+++ b/worlds/fftii/data/logic/FFTRegion.py
@@ -0,0 +1,14 @@
+from .Connection import Connection
+from .FFTLocation import FFTLocation
+
+
+class FFTRegion:
+ name: str
+ connections: list[Connection] = []
+ locations: list[FFTLocation] = []
+
+ def __repr__(self):
+ return self.name
+
+ def __str__(self):
+ return self.__repr__()
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/JobUnlocks.py b/worlds/fftii/data/logic/JobUnlocks.py
new file mode 100644
index 000000000000..319ae63da5f6
--- /dev/null
+++ b/worlds/fftii/data/logic/JobUnlocks.py
@@ -0,0 +1,22 @@
+unlock_dict = {
+ "Squire": {},
+ "Chemist": {},
+ "Knight": {"Squire": 2},
+ "Archer": {"Squire": 2},
+ "Monk": {"Knight": 2},
+ "Thief": {"Archer": 2},
+ "Lancer": {"Thief": 3},
+ "Geomancer": {"Monk": 3},
+ "Samurai": {"Knight": 3, "Monk": 4, "Lancer": 2},
+ "Ninja": {"Archer": 3, "Thief": 4, "Geomancer": 2},
+ "Dancer": {"Lancer": 4, "Geomancer": 4},
+ "Priest": {"Chemist": 2},
+ "Wizard": {"Chemist": 2},
+ "Oracle": {"Priest": 2},
+ "Mediator": {"Oracle": 2},
+ "Time Mage": {"Wizard": 2},
+ "Summoner": {"Time Mage": 2},
+ "Calculator": {"Priest": 4, "Wizard": 4, "Oracle": 3, "Time Mage": 3},
+ "Bard": {"Mediator": 4, "Summoner": 4},
+ "Mime": {"Squire": 8, "Chemist": 8, "Lancer": 4, "Geomancer": 4, "Mediator": 4, "Summoner": 4}
+}
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/Monsters.py b/worlds/fftii/data/logic/Monsters.py
new file mode 100644
index 000000000000..79e537f6205b
--- /dev/null
+++ b/worlds/fftii/data/logic/Monsters.py
@@ -0,0 +1,518 @@
+from enum import Enum
+
+from .FFTRegion import FFTRegion
+from .regions import Mandalia, Grog, Zirekile, BariausHill, Finath, Dolbodar, Fovoham, Bethla, \
+ Zaland, BerveniaVolcano, Riovanes, Lenalia, Lesalia, Araguay, BariausValley, Sweegy, Yuguo, Bed, BerveniaCity, \
+ Germinas, Zeklaus, Doguola, Poeskas, Zigolis, Dorter, Goug, Zeakden, Nelveska, DeepDungeon, Goland, Zeltennia
+
+
+class MonsterNames(Enum):
+ YELLOW_CHOCOBO = "Yellow Chocobo"
+ BLACK_CHOCOBO = "Black Chocobo"
+ RED_CHOCOBO = "Red Chocobo"
+
+ GOBLIN = "Goblin"
+ BLACK_GOBLIN = "Black Goblin"
+ GOBBLEDEGUCK = "Gobbledeguck"
+
+ RED_PANTHER = "Red Panther"
+ CUAR = "Cuar"
+ VAMPIRE = "Vampire"
+
+ BOMB = "Bomb"
+ GRENADE = "Grenade"
+ EXPLOSIVE = "Explosive"
+
+ SKELETON = "Skeleton"
+ BONE_SNATCH = "Bone Snatch"
+ LIVING_BONE = "Living Bone"
+
+ GHOUL = "Ghoul"
+ GUST = "Gust"
+ REVNANT = "Revnant"
+
+ FLOATIBALL = "Floatiball"
+ AHRIMAN = "Ahriman"
+ PLAGUE = "Plague"
+
+ PISCO_DEMON = "Pisco Demon"
+ SQUIDLARKIN = "Squidlarkin"
+ MINDFLARE = "Mindflare"
+
+ JURAVIS = "Juravis"
+ STEEL_HAWK = "Steel Hawk"
+ COCATORIS = "Cocatoris"
+
+ BULL_DEMON = "Bull Demon"
+ MINITAURUS = "Minitaurus"
+ SACRED = "Sacred"
+
+ MORBOL = "Morbol"
+ OCHU = "Ochu"
+ GREAT_MORBOL = "Great Morbol"
+
+ WOODMAN = "Woodman"
+ TRENT = "Trent"
+ TAIJU = "Taiju"
+
+ DRAGON = "Dragon"
+ BLUE_DRAGON = "Blue Dragon"
+ RED_DRAGON = "Red Dragon"
+
+ BEHEMOTH = "Behemoth"
+ KING_BEHEMOTH = "King Behemoth"
+ DARK_BEHEMOTH = "Dark Behemoth"
+
+ HYUDRA = "Hyudra"
+ HYDRA = "Hydra"
+ TIAMAT = "Tiamat"
+
+ URIBO = "Uribo"
+ PORKY = "Porky"
+ WILDBOW = "Wildbow"
+
+class MonsterFamilies(Enum):
+ CHOCOBO = "Chocobo"
+ GOBLIN = "Goblin"
+ PANTHER = "Panther"
+ BOMB = "Bomb"
+ SKELETON = "Skeleton"
+ GHOST = "Ghost"
+ AHRIMAN = "Ahriman"
+ MINDFLAYER = "Mindflayer"
+ BIRD = "Bird"
+ MINOTAUR = "Minotaur"
+ MALBORO = "Malboro"
+ TREANT = "Treant"
+ DRAGON = "Dragon"
+ BEHEMOTH = "Behemoth"
+ HYDRA = "Hydra"
+ PIG = "Pig"
+
+monster_families: dict[MonsterFamilies, list[MonsterNames]] = {
+ MonsterFamilies.CHOCOBO: [MonsterNames.YELLOW_CHOCOBO, MonsterNames.BLACK_CHOCOBO, MonsterNames.RED_CHOCOBO],
+ MonsterFamilies.GOBLIN: [MonsterNames.GOBLIN, MonsterNames.BLACK_GOBLIN, MonsterNames.GOBBLEDEGUCK],
+ MonsterFamilies.PANTHER: [MonsterNames.RED_PANTHER, MonsterNames.CUAR, MonsterNames.VAMPIRE],
+ MonsterFamilies.BOMB: [MonsterNames.BOMB, MonsterNames.GRENADE, MonsterNames.EXPLOSIVE],
+ MonsterFamilies.SKELETON: [MonsterNames.SKELETON, MonsterNames.BONE_SNATCH, MonsterNames.LIVING_BONE],
+ MonsterFamilies.GHOST: [MonsterNames.GHOUL, MonsterNames.GUST, MonsterNames.REVNANT],
+ MonsterFamilies.AHRIMAN: [MonsterNames.FLOATIBALL, MonsterNames.AHRIMAN, MonsterNames.PLAGUE],
+ MonsterFamilies.MINDFLAYER: [MonsterNames.PISCO_DEMON, MonsterNames.SQUIDLARKIN, MonsterNames.MINDFLARE],
+ MonsterFamilies.BIRD: [MonsterNames.JURAVIS, MonsterNames.STEEL_HAWK, MonsterNames.COCATORIS],
+ MonsterFamilies.MINOTAUR: [MonsterNames.BULL_DEMON, MonsterNames.MINITAURUS, MonsterNames.SACRED],
+ MonsterFamilies.MALBORO: [MonsterNames.MORBOL, MonsterNames.OCHU, MonsterNames.GREAT_MORBOL],
+ MonsterFamilies.TREANT: [MonsterNames.WOODMAN, MonsterNames.TRENT, MonsterNames.TAIJU],
+ MonsterFamilies.DRAGON: [MonsterNames.DRAGON, MonsterNames.BLUE_DRAGON, MonsterNames.RED_DRAGON],
+ MonsterFamilies.BEHEMOTH: [MonsterNames.BEHEMOTH, MonsterNames.KING_BEHEMOTH, MonsterNames.DARK_BEHEMOTH],
+ MonsterFamilies.HYDRA: [MonsterNames.HYUDRA, MonsterNames.HYDRA, MonsterNames.TIAMAT],
+ MonsterFamilies.PIG: [MonsterNames.URIBO, MonsterNames.PORKY, MonsterNames.WILDBOW]
+}
+
+monster_family_lookup: dict[MonsterNames, MonsterFamilies] = {}
+
+for family, monsters in monster_families.items():
+ for monster in monsters:
+ monster_family_lookup[monster] = family
+
+
+class RegionAccessRequirement:
+ access_regions: list[type[FFTRegion]]
+ battle_level: int
+ sidequest: bool
+
+ def __init__(self, access_regions: list[type[FFTRegion]], battle_level: int, sidequest: bool = False):
+ self.access_regions = access_regions
+ self.battle_level = battle_level
+ self.sidequest = sidequest
+
+class MonsterRegion:
+ monster_name: MonsterNames
+ gallione_locations: list[RegionAccessRequirement] = []
+ fovoham_locations: list[RegionAccessRequirement] = []
+ lesalia_locations: list[RegionAccessRequirement] = []
+ lionel_locations: list[RegionAccessRequirement] = []
+ zeltennia_locations: list[RegionAccessRequirement] = []
+ limberry_locations: list[RegionAccessRequirement] = []
+ murond_locations: list[RegionAccessRequirement] = []
+ compiled_requirements: list[RegionAccessRequirement] = []
+
+ def __init__(self, name: MonsterNames):
+ self.monster_name = name
+
+yellow_chocobo = MonsterRegion(MonsterNames.YELLOW_CHOCOBO)
+yellow_chocobo.gallione_locations = [RegionAccessRequirement([Mandalia], 0)]
+yellow_chocobo.fovoham_locations = [RegionAccessRequirement([Grog], 0)]
+yellow_chocobo.lesalia_locations = [RegionAccessRequirement([Zirekile], 0)]
+yellow_chocobo.lionel_locations = [RegionAccessRequirement([BariausHill], 0)]
+yellow_chocobo.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+yellow_chocobo.limberry_locations = [RegionAccessRequirement([Dolbodar], 9)]
+
+black_chocobo = MonsterRegion(MonsterNames.BLACK_CHOCOBO)
+black_chocobo.gallione_locations = [RegionAccessRequirement([Mandalia], 5)]
+black_chocobo.fovoham_locations = [RegionAccessRequirement([Fovoham], 0)]
+black_chocobo.lesalia_locations = [
+ RegionAccessRequirement([Zirekile], 5),
+ RegionAccessRequirement([Zirekile, Zaland], 0),
+ RegionAccessRequirement([Zirekile, Bethla], 2),
+ RegionAccessRequirement([BerveniaVolcano, Riovanes], 9)
+]
+black_chocobo.lionel_locations = [RegionAccessRequirement([BariausHill], 0)]
+black_chocobo.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+black_chocobo.limberry_locations = [RegionAccessRequirement([Dolbodar], 9)]
+
+red_chocobo = MonsterRegion(MonsterNames.RED_CHOCOBO)
+red_chocobo.gallione_locations = [RegionAccessRequirement([Lenalia], 5)]
+red_chocobo.fovoham_locations = [RegionAccessRequirement([Grog, Lesalia], 2)]
+red_chocobo.lesalia_locations = [
+ RegionAccessRequirement([Zirekile, Bethla], 5),
+ RegionAccessRequirement([Zirekile, Zaland], 5),
+ RegionAccessRequirement([BerveniaVolcano, Riovanes], 9)
+]
+red_chocobo.lionel_locations = [RegionAccessRequirement([BariausHill], 0)]
+red_chocobo.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+red_chocobo.limberry_locations = [RegionAccessRequirement([Dolbodar], 9)]
+
+goblin = MonsterRegion(MonsterNames.GOBLIN)
+goblin.gallione_locations = [RegionAccessRequirement([Mandalia], 0)]
+goblin.fovoham_locations = [
+ RegionAccessRequirement([Fovoham, Lenalia], 0),
+ RegionAccessRequirement([Grog, Lesalia], 5)
+]
+goblin.lesalia_locations = [RegionAccessRequirement([Araguay], 0)]
+goblin.lionel_locations = [RegionAccessRequirement([BariausValley], 0)]
+goblin.zeltennia_locations = [RegionAccessRequirement([Finath], 9)]
+goblin.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+black_goblin = MonsterRegion(MonsterNames.BLACK_GOBLIN)
+black_goblin.gallione_locations = [RegionAccessRequirement([Sweegy], 0)]
+black_goblin.fovoham_locations = [RegionAccessRequirement([Yuguo], 2)]
+black_goblin.lesalia_locations = [RegionAccessRequirement([Araguay], 0)]
+black_goblin.lionel_locations = [RegionAccessRequirement([BariausValley], 0)]
+black_goblin.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+gobbledeguck = MonsterRegion(MonsterNames.GOBBLEDEGUCK)
+gobbledeguck.gallione_locations = [RegionAccessRequirement([Mandalia], 5)]
+gobbledeguck.fovoham_locations = [RegionAccessRequirement([Yuguo], 5)]
+gobbledeguck.lesalia_locations = [RegionAccessRequirement([Araguay], 8)]
+gobbledeguck.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+gobbledeguck.limberry_locations = [RegionAccessRequirement([Dolbodar], 5)]
+
+red_panther = MonsterRegion(MonsterNames.RED_PANTHER)
+red_panther.gallione_locations = [RegionAccessRequirement([Mandalia], 0)]
+red_panther.fovoham_locations = [RegionAccessRequirement([Grog], 0)]
+red_panther.lesalia_locations = [RegionAccessRequirement([Araguay], 0)]
+red_panther.lionel_locations = [RegionAccessRequirement([BariausValley], 0)]
+red_panther.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+red_panther.limberry_locations = [RegionAccessRequirement([Bed], 5)]
+
+cuar = MonsterRegion(MonsterNames.CUAR)
+cuar.gallione_locations = [
+ RegionAccessRequirement([Sweegy], 5),
+ RegionAccessRequirement([Lenalia, Fovoham], 2),
+]
+cuar.fovoham_locations = [RegionAccessRequirement([Grog], 2)]
+cuar.lesalia_locations = [RegionAccessRequirement([Araguay], 0)]
+cuar.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+cuar.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+cuar.limberry_locations = [RegionAccessRequirement([Bed, BerveniaCity], 0)]
+
+vampire = MonsterRegion(MonsterNames.VAMPIRE)
+vampire.gallione_locations = [RegionAccessRequirement([Sweegy], 5)]
+vampire.fovoham_locations = [RegionAccessRequirement([Yuguo], 5)]
+vampire.lesalia_locations = [RegionAccessRequirement([Araguay], 5)]
+vampire.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+vampire.zeltennia_locations = [RegionAccessRequirement([Germinas], 5)]
+vampire.limberry_locations = [RegionAccessRequirement([Bed, BerveniaCity], 2)]
+
+bomb = MonsterRegion(MonsterNames.BOMB)
+bomb.gallione_locations = [RegionAccessRequirement([Sweegy], 0)]
+bomb.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+bomb.lesalia_locations = [RegionAccessRequirement([Zeklaus], 0)]
+bomb.lionel_locations = [RegionAccessRequirement([BariausHill], 0)]
+bomb.zeltennia_locations = [RegionAccessRequirement([Doguola], 5)]
+bomb.limberry_locations = [RegionAccessRequirement([Poeskas], 0)]
+
+grenade = MonsterRegion(MonsterNames.GRENADE)
+grenade.gallione_locations = [RegionAccessRequirement([Mandalia], 5)]
+grenade.fovoham_locations = [RegionAccessRequirement([Grog], 0)]
+grenade.lesalia_locations = [RegionAccessRequirement([Zeklaus], 0)]
+grenade.lionel_locations = [RegionAccessRequirement([BariausHill], 5)]
+grenade.zeltennia_locations = [RegionAccessRequirement([Doguola, Grog], 5)]
+grenade.limberry_locations = [RegionAccessRequirement([Poeskas], 0)]
+
+explosive = MonsterRegion(MonsterNames.EXPLOSIVE)
+explosive.gallione_locations = [RegionAccessRequirement([Sweegy], 8)]
+explosive.fovoham_locations = [RegionAccessRequirement([Grog, Zeltennia], 5)]
+explosive.lesalia_locations = [
+ RegionAccessRequirement([BerveniaVolcano], 5),
+ RegionAccessRequirement([BerveniaVolcano, Riovanes], 2)
+]
+explosive.lionel_locations = [RegionAccessRequirement([BariausHill], 5)]
+explosive.zeltennia_locations = [RegionAccessRequirement([Doguola, Grog], 5)]
+explosive.limberry_locations = [RegionAccessRequirement([Poeskas], 0)]
+
+skeleton = MonsterRegion(MonsterNames.SKELETON)
+skeleton.gallione_locations = [RegionAccessRequirement([Sweegy], 0)]
+skeleton.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+skeleton.lesalia_locations = [RegionAccessRequirement([Zeklaus, Dorter], 0)]
+skeleton.lionel_locations = [RegionAccessRequirement([Zigolis], 0)]
+skeleton.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+bone_snatch = MonsterRegion(MonsterNames.BONE_SNATCH)
+bone_snatch.gallione_locations = [RegionAccessRequirement([Sweegy], 5)]
+bone_snatch.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+bone_snatch.lesalia_locations = [RegionAccessRequirement([Araguay], 0)]
+bone_snatch.lionel_locations = [RegionAccessRequirement([Zigolis], 0)]
+bone_snatch.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+living_bone = MonsterRegion(MonsterNames.LIVING_BONE)
+living_bone.gallione_locations = [RegionAccessRequirement([Sweegy], 5)]
+living_bone.lesalia_locations = [RegionAccessRequirement([BerveniaVolcano], 5)]
+living_bone.lionel_locations = [RegionAccessRequirement([Zigolis, Goug], 5)]
+living_bone.limberry_locations = [RegionAccessRequirement([Dolbodar], 2)]
+
+ghoul = MonsterRegion(MonsterNames.GHOUL)
+ghoul.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+ghoul.lesalia_locations = [RegionAccessRequirement([Araguay, Dorter], 0)]
+ghoul.lionel_locations = [RegionAccessRequirement([Zigolis], 0)]
+ghoul.limberry_locations = [RegionAccessRequirement([Poeskas], 0)]
+
+gust = MonsterRegion(MonsterNames.GUST)
+gust.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+gust.lesalia_locations = [RegionAccessRequirement([Araguay, Dorter], 2)]
+gust.lionel_locations = [RegionAccessRequirement([Zigolis], 2)]
+gust.limberry_locations = [RegionAccessRequirement([Poeskas], 0)]
+
+revnant = MonsterRegion(MonsterNames.REVNANT)
+revnant.fovoham_locations = [RegionAccessRequirement([Yuguo], 5)]
+revnant.lesalia_locations = [RegionAccessRequirement([Araguay, Dorter], 5)]
+revnant.lionel_locations = [RegionAccessRequirement([Zigolis], 5)]
+revnant.limberry_locations = [RegionAccessRequirement([Poeskas], 2)]
+
+floatiball = MonsterRegion(MonsterNames.FLOATIBALL)
+floatiball.fovoham_locations = [RegionAccessRequirement([Fovoham], 0)]
+floatiball.lesalia_locations = [RegionAccessRequirement([Zirekile], 0)]
+floatiball.lionel_locations = [RegionAccessRequirement([Zigolis], 0)]
+floatiball.limberry_locations = [RegionAccessRequirement([Bed], 0)]
+
+ahriman = MonsterRegion(MonsterNames.AHRIMAN)
+ahriman.fovoham_locations = [RegionAccessRequirement([Fovoham], 0)]
+ahriman.lesalia_locations = [
+ RegionAccessRequirement([Zirekile, Bethla], 2),
+ RegionAccessRequirement([BerveniaVolcano, Riovanes], 0)
+]
+ahriman.lionel_locations = [RegionAccessRequirement([Zigolis, Goug], 2)]
+ahriman.limberry_locations = [RegionAccessRequirement([Bed], 0)]
+
+plague = MonsterRegion(MonsterNames.PLAGUE)
+plague.fovoham_locations = [RegionAccessRequirement([Fovoham], 8)]
+plague.lesalia_locations = [
+ RegionAccessRequirement([BerveniaVolcano, Riovanes], 5),
+ RegionAccessRequirement([Goland], 8, sidequest=True)
+]
+plague.lionel_locations = [RegionAccessRequirement([BariausValley], 9)]
+plague.zeltennia_locations = [RegionAccessRequirement([Germinas], 5)]
+plague.limberry_locations = [RegionAccessRequirement([Bed, BerveniaCity], 5)]
+
+pisco_demon = MonsterRegion(MonsterNames.PISCO_DEMON)
+pisco_demon.gallione_locations = [
+ RegionAccessRequirement([Lenalia], 2),
+ RegionAccessRequirement([Lenalia, Fovoham], 0)
+]
+pisco_demon.fovoham_locations = [RegionAccessRequirement([Fovoham], 0)]
+pisco_demon.lesalia_locations = [RegionAccessRequirement([Zirekile], 0)]
+pisco_demon.lionel_locations = [RegionAccessRequirement([BariausValley], 0)]
+pisco_demon.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+pisco_demon.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+squidlarkin = MonsterRegion(MonsterNames.SQUIDLARKIN)
+squidlarkin.gallione_locations = [RegionAccessRequirement([Lenalia], 2)]
+squidlarkin.fovoham_locations = [RegionAccessRequirement([Fovoham], 0)]
+squidlarkin.lesalia_locations = [RegionAccessRequirement([Zirekile], 0)]
+squidlarkin.lionel_locations = [RegionAccessRequirement([BariausValley], 0)]
+squidlarkin.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+squidlarkin.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+mindflare = MonsterRegion(MonsterNames.MINDFLARE)
+mindflare.fovoham_locations = [RegionAccessRequirement([Fovoham], 2)]
+mindflare.lesalia_locations = [RegionAccessRequirement([Zirekile, Zaland], 2)]
+mindflare.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+mindflare.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+mindflare.limberry_locations = [RegionAccessRequirement([Dolbodar], 5)]
+
+juravis = MonsterRegion(MonsterNames.JURAVIS)
+juravis.fovoham_locations = [RegionAccessRequirement([Fovoham, Zeakden], 0)]
+juravis.lesalia_locations = [RegionAccessRequirement([BerveniaVolcano], 0)]
+juravis.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+juravis.zeltennia_locations = [RegionAccessRequirement([Germinas], 0)]
+
+steel_hawk = MonsterRegion(MonsterNames.STEEL_HAWK)
+steel_hawk.fovoham_locations = [RegionAccessRequirement([Fovoham, Zeakden], 2)]
+steel_hawk.lesalia_locations = [RegionAccessRequirement([Zeklaus], 0)]
+steel_hawk.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+steel_hawk.zeltennia_locations = [RegionAccessRequirement([Germinas], 0)]
+steel_hawk.limberry_locations = [RegionAccessRequirement([Poeskas], 2)]
+
+cocatoris = MonsterRegion(MonsterNames.COCATORIS)
+cocatoris.fovoham_locations = [RegionAccessRequirement([Fovoham], 0)]
+cocatoris.lesalia_locations = [RegionAccessRequirement([Zeklaus], 5)]
+cocatoris.lionel_locations = [RegionAccessRequirement([BariausValley], 5)]
+cocatoris.limberry_locations = [RegionAccessRequirement([Poeskas, Germinas], 2)]
+
+bull_demon = MonsterRegion(MonsterNames.BULL_DEMON)
+bull_demon.gallione_locations = [RegionAccessRequirement([Sweegy], 0)]
+bull_demon.fovoham_locations = [
+ RegionAccessRequirement([Fovoham], 5),
+ RegionAccessRequirement([Fovoham, Zeakden], 0),
+]
+bull_demon.lesalia_locations = [RegionAccessRequirement([Zeklaus], 0)]
+bull_demon.lionel_locations = [RegionAccessRequirement([BariausHill], 0)]
+bull_demon.zeltennia_locations = [RegionAccessRequirement([Doguola], 0)]
+bull_demon.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+minitaurus = MonsterRegion(MonsterNames.MINITAURUS)
+minitaurus.fovoham_locations = [RegionAccessRequirement([Fovoham], 5)]
+minitaurus.lesalia_locations = [RegionAccessRequirement([Zeklaus], 5)]
+minitaurus.lionel_locations = [RegionAccessRequirement([BariausValley], 2)]
+minitaurus.zeltennia_locations = [RegionAccessRequirement([Germinas], 0)]
+minitaurus.limberry_locations = [RegionAccessRequirement([Poeskas], 2)]
+
+sacred = MonsterRegion(MonsterNames.SACRED)
+sacred.fovoham_locations = [
+ RegionAccessRequirement([Fovoham], 8),
+ RegionAccessRequirement([Fovoham, Lenalia], 5)
+]
+sacred.lesalia_locations = [RegionAccessRequirement([Zeklaus], 5)]
+sacred.lionel_locations = [RegionAccessRequirement([BariausHill], 5)]
+sacred.limberry_locations = [RegionAccessRequirement([Dolbodar], 9)]
+
+morbol = MonsterRegion(MonsterNames.MORBOL)
+morbol.gallione_locations = [RegionAccessRequirement([Mandalia], 5)]
+morbol.fovoham_locations = [RegionAccessRequirement([Fovoham], 2)]
+morbol.lesalia_locations = [RegionAccessRequirement([Araguay], 2)]
+morbol.lionel_locations = [RegionAccessRequirement([Zigolis], 0)]
+morbol.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+morbol.limberry_locations = [RegionAccessRequirement([Dolbodar], 0)]
+
+ochu = MonsterRegion(MonsterNames.OCHU)
+ochu.gallione_locations = [RegionAccessRequirement([Lenalia], 5)]
+ochu.lesalia_locations = [RegionAccessRequirement([Araguay], 5)]
+ochu.lionel_locations = [RegionAccessRequirement([Zigolis], 5)]
+ochu.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+
+great_morbol = MonsterRegion(MonsterNames.GREAT_MORBOL)
+great_morbol.zeltennia_locations = [RegionAccessRequirement([Finath], 8)]
+
+woodman = MonsterRegion(MonsterNames.WOODMAN)
+woodman.gallione_locations = [RegionAccessRequirement([Sweegy], 5)]
+woodman.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+woodman.lesalia_locations = [RegionAccessRequirement([Araguay], 5)]
+woodman.zeltennia_locations = [RegionAccessRequirement([Doguola], 2)]
+
+trent = MonsterRegion(MonsterNames.TRENT)
+trent.gallione_locations = [RegionAccessRequirement([Sweegy], 5)]
+trent.fovoham_locations = [RegionAccessRequirement([Yuguo], 0)]
+trent.lesalia_locations = [RegionAccessRequirement([Araguay], 2)]
+trent.zeltennia_locations = [RegionAccessRequirement([Finath], 2)]
+
+taiju = MonsterRegion(MonsterNames.TAIJU)
+taiju.fovoham_locations = [RegionAccessRequirement([Yuguo], 5)]
+taiju.lesalia_locations = [RegionAccessRequirement([Araguay], 5)]
+taiju.zeltennia_locations = [RegionAccessRequirement([Finath], 8)]
+taiju.murond_locations = [RegionAccessRequirement([DeepDungeon], 10, sidequest=True)]
+
+dragon = MonsterRegion(MonsterNames.DRAGON)
+dragon.gallione_locations = [RegionAccessRequirement([Lenalia], 0)]
+dragon.lesalia_locations = [RegionAccessRequirement([Zeklaus], 5)]
+dragon.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+dragon.zeltennia_locations = [RegionAccessRequirement([Germinas], 0)]
+dragon.limberry_locations = [RegionAccessRequirement([Bed], 2)]
+
+blue_dragon = MonsterRegion(MonsterNames.BLUE_DRAGON)
+blue_dragon.gallione_locations = [RegionAccessRequirement([Mandalia], 9)]
+blue_dragon.fovoham_locations = [RegionAccessRequirement([Grog], 0)]
+blue_dragon.lionel_locations = [RegionAccessRequirement([BariausValley], 5)]
+blue_dragon.zeltennia_locations = [RegionAccessRequirement([Finath], 8)]
+blue_dragon.limberry_locations = [
+ RegionAccessRequirement([Bed], 8),
+ RegionAccessRequirement([Bed, BerveniaCity], 5)
+]
+
+red_dragon = MonsterRegion(MonsterNames.RED_DRAGON)
+red_dragon.gallione_locations = [RegionAccessRequirement([Mandalia], 9)]
+red_dragon.lesalia_locations = [RegionAccessRequirement([Zeklaus], 5)]
+red_dragon.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+red_dragon.zeltennia_locations = [RegionAccessRequirement([Finath], 0)]
+
+behemoth = MonsterRegion(MonsterNames.BEHEMOTH)
+behemoth.lesalia_locations = [RegionAccessRequirement([BerveniaVolcano], 2)]
+behemoth.lionel_locations = [RegionAccessRequirement([BariausValley], 5)]
+behemoth.zeltennia_locations = [RegionAccessRequirement([Doguola], 0)]
+behemoth.limberry_locations = [RegionAccessRequirement([Poeskas], 0)]
+
+king_behemoth = MonsterRegion(MonsterNames.KING_BEHEMOTH)
+king_behemoth.lesalia_locations = [RegionAccessRequirement([BerveniaVolcano, Riovanes], 9)]
+king_behemoth.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+king_behemoth.limberry_locations = [
+ RegionAccessRequirement([Poeskas, Germinas], 0),
+ RegionAccessRequirement([Bed], 5)
+]
+
+dark_behemoth = MonsterRegion(MonsterNames.DARK_BEHEMOTH)
+dark_behemoth.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+dark_behemoth.limberry_locations = [RegionAccessRequirement([Poeskas], 8)]
+
+hyudra = MonsterRegion(MonsterNames.HYUDRA)
+hyudra.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+hyudra.zeltennia_locations = [RegionAccessRequirement([Nelveska], 12, sidequest=True)]
+
+hydra = MonsterRegion(MonsterNames.HYDRA)
+hydra.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+
+tiamat = MonsterRegion(MonsterNames.TIAMAT)
+tiamat.lionel_locations = [RegionAccessRequirement([BariausHill], 9)]
+
+uribo = MonsterRegion(MonsterNames.URIBO)
+uribo.lesalia_locations = [RegionAccessRequirement([Goland], 8, sidequest=True)]
+uribo.lionel_locations = [RegionAccessRequirement([Zigolis], 6)]
+uribo.zeltennia_locations = [RegionAccessRequirement([Finath], 12)]
+uribo.limberry_locations = [RegionAccessRequirement([Dolbodar], 2)]
+
+porky = MonsterRegion(MonsterNames.PORKY)
+porky.limberry_locations = [RegionAccessRequirement([Dolbodar], 9)]
+
+wildbow = MonsterRegion(MonsterNames.WILDBOW)
+
+monster_locations = [
+ yellow_chocobo, black_chocobo, red_chocobo,
+ goblin, black_goblin, gobbledeguck,
+ red_panther, cuar, vampire,
+ bomb, grenade, explosive,
+ skeleton, bone_snatch, living_bone,
+ ghoul, gust, revnant,
+ floatiball, ahriman, plague,
+ pisco_demon, squidlarkin, mindflare,
+ juravis, steel_hawk, cocatoris,
+ bull_demon, minitaurus, sacred,
+ morbol, ochu, great_morbol,
+ woodman, trent, taiju,
+ dragon, blue_dragon, red_dragon,
+ behemoth, king_behemoth, dark_behemoth,
+ hyudra, hydra, tiamat,
+ uribo, porky, wildbow
+]
+
+monster_locations_lookup: dict[str, MonsterRegion] = {
+ monster.monster_name.value: monster for monster in monster_locations
+}
+
+for monster in monster_locations:
+ monster.compiled_requirements = [
+ *monster.gallione_locations,
+ *monster.fovoham_locations,
+ *monster.lesalia_locations,
+ *monster.lionel_locations,
+ *monster.zeltennia_locations,
+ *monster.limberry_locations,
+ *monster.murond_locations
+ ]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/Requirement.py b/worlds/fftii/data/logic/Requirement.py
new file mode 100644
index 000000000000..254c2baef735
--- /dev/null
+++ b/worlds/fftii/data/logic/Requirement.py
@@ -0,0 +1,44 @@
+import typing
+from typing import TYPE_CHECKING, Self
+
+if TYPE_CHECKING:
+ from ... import FinalFantasyTacticsOptions
+
+
+class Requirement:
+ """
+ Defines a set of requirements for a Connection or Location.
+ """
+ items_needed: list[str] = []
+ other_requirements: list[Self] = []
+ name: str = __name__
+
+ def __init__(self, items_needed, other_requirements = None):
+ """
+ Creates a new Requirement object. The parameters are unpacked into a series of OR requirements where everything
+ in ``items_required`` and one of the entries in ``other_requirements``
+ must be met for the Requirement to be passed.
+
+ :param items_needed: A list of items that are all required to be had.
+ :param other_requirements: A list of Requirement objects.
+ If not empty, one of these must be fulfilled in addition to the ``items_needed``.
+ """
+ if other_requirements is None:
+ other_requirements = list()
+ self.items_needed = items_needed
+ self.other_requirements = other_requirements
+ self.name: str = self.__class__.__name__
+
+ def __repr__(self):
+ return_string = f"{self.name}\n"
+ return_string += f"ItemsNeeded: [{', '.join(self.items_needed)}]\n"
+ return_string += (f"OtherRequirements: "
+ f"[{', '.join([requirement.name for requirement in self.other_requirements])}]\n")
+ return return_string
+
+ def __str__(self):
+ return self.__repr__()
+
+ @staticmethod
+ def check_option_enabled(options: "FinalFantasyTacticsOptions") -> bool:
+ return True
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/Requirements.py b/worlds/fftii/data/logic/Requirements.py
new file mode 100644
index 000000000000..f0d0b0562bce
--- /dev/null
+++ b/worlds/fftii/data/logic/Requirements.py
@@ -0,0 +1,33 @@
+from .Requirement import Requirement
+
+class HasGallionePass(Requirement):
+ name = "Has Gallione Pass"
+ items_needed = ["Gallione Pass"]
+
+class HasFovohamPass(Requirement):
+ name = "Has Fovoham Pass"
+ items_needed = ["Fovoham Pass"]
+
+class HasLesaliaPass(Requirement):
+ name = "Has Lesalia Pass"
+ items_needed = ["Lesalia Pass"]
+
+class HasLionelPass(Requirement):
+ name = "Has Lionel Pass"
+ items_needed = ["Lionel Pass"]
+
+class HasZeltenniaPass(Requirement):
+ name = "Has Zeltennia Pass"
+ items_needed = ["Zeltennia Pass"]
+
+class HasLimberryPass(Requirement):
+ name = "Has Limberry Pass"
+ items_needed = ["Limberry Pass"]
+
+class HasMurondPass(Requirement):
+ name = "Has Murond Pass"
+ items_needed = ["Murond Pass"]
+
+class HasZodiacStones(Requirement):
+ name = "Has Zodiac Stones"
+ items_needed = ["Zodiac Stones"]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/__init__.py b/worlds/fftii/data/logic/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/worlds/fftii/data/logic/regions/Fovoham.py b/worlds/fftii/data/logic/regions/Fovoham.py
new file mode 100644
index 000000000000..f7f3eca153ef
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Fovoham.py
@@ -0,0 +1,21 @@
+from ..FFTRegion import FFTRegion
+
+
+class Grog(FFTRegion):
+ name = "Grog"
+
+class Yardow(FFTRegion):
+ name = "Yardow"
+
+class Yuguo(FFTRegion):
+ name = "Yuguo"
+
+class Riovanes(FFTRegion):
+ name = "Riovanes"
+
+class Fovoham(FFTRegion):
+ name = "Fovoham"
+
+fovoham_regions = [
+ Grog, Yardow, Yuguo, Riovanes, Fovoham
+]
diff --git a/worlds/fftii/data/logic/regions/Gallione.py b/worlds/fftii/data/logic/regions/Gallione.py
new file mode 100644
index 000000000000..d453a6dbb6ae
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Gallione.py
@@ -0,0 +1,31 @@
+from ..FFTRegion import FFTRegion
+
+
+class Gariland(FFTRegion):
+ name = "Gariland"
+
+class Mandalia(FFTRegion):
+ name = "Mandalia"
+
+class Igros(FFTRegion):
+ name = "Igros"
+
+class Sweegy(FFTRegion):
+ name = "Sweegy"
+
+class Dorter(FFTRegion):
+ name = "Dorter"
+
+class ThievesFort(FFTRegion):
+ name = "Thieves' Fort"
+
+class Lenalia(FFTRegion):
+ name = "Lenalia"
+
+class Zeakden(FFTRegion):
+ name = "Zeakden"
+
+
+gallione_regions = [
+ Gariland, Mandalia, Igros, Sweegy, Dorter, ThievesFort, Lenalia, Zeakden
+]
diff --git a/worlds/fftii/data/logic/regions/Jobs.py b/worlds/fftii/data/logic/regions/Jobs.py
new file mode 100644
index 000000000000..b6fe89f83eca
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Jobs.py
@@ -0,0 +1,67 @@
+from ..FFTRegion import FFTRegion
+
+
+class Squire(FFTRegion):
+ name = "Squire"
+
+class Chemist(FFTRegion):
+ name = "Chemist"
+
+class Knight(FFTRegion):
+ name = "Knight"
+
+class Archer(FFTRegion):
+ name = "Archer"
+
+class Priest(FFTRegion):
+ name = "Priest"
+
+class Wizard(FFTRegion):
+ name = "Wizard"
+
+class Thief(FFTRegion):
+ name = "Thief"
+
+class Monk(FFTRegion):
+ name = "Monk"
+
+class TimeMage(FFTRegion):
+ name = "Time Mage"
+
+class Summoner(FFTRegion):
+ name = "Summoner"
+
+class Oracle(FFTRegion):
+ name = "Oracle"
+
+class Mediator(FFTRegion):
+ name = "Mediator"
+
+class Geomancer(FFTRegion):
+ name = "Geomancer"
+
+class Lancer(FFTRegion):
+ name = "Lancer"
+
+class Samurai(FFTRegion):
+ name = "Samurai"
+
+class Ninja(FFTRegion):
+ name = "Ninja"
+
+class Calculator(FFTRegion):
+ name = "Calculator"
+
+class Bard(FFTRegion):
+ name = "Bard"
+
+class Dancer(FFTRegion):
+ name = "Dancer"
+
+class Mime(FFTRegion):
+ name = "Mime"
+
+jobs_regions = [
+ Squire, Chemist, Knight, Archer, Thief, Monk, Priest, Wizard, TimeMage, Summoner, Oracle, Mediator,
+ Geomancer, Lancer, Samurai, Ninja, Calculator, Bard, Dancer, Mime
+]
diff --git a/worlds/fftii/data/logic/regions/Lesalia.py b/worlds/fftii/data/logic/regions/Lesalia.py
new file mode 100644
index 000000000000..8dac74561655
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Lesalia.py
@@ -0,0 +1,24 @@
+from ..FFTRegion import FFTRegion
+
+
+class Araguay(FFTRegion):
+ name = "Araguay"
+
+class Zirekile(FFTRegion):
+ name = "Zirekile"
+
+class Zeklaus(FFTRegion):
+ name = "Zeklaus"
+
+class BerveniaVolcano(FFTRegion):
+ name = "Bervenia Volcano"
+
+class Lesalia(FFTRegion):
+ name = "Lesalia"
+
+class Goland(FFTRegion):
+ name = "Goland"
+
+lesalia_regions = [
+ Araguay, Zirekile, Zeklaus, BerveniaVolcano, Lesalia, Goland
+]
diff --git a/worlds/fftii/data/logic/regions/Limberry.py b/worlds/fftii/data/logic/regions/Limberry.py
new file mode 100644
index 000000000000..a3f94e621e92
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Limberry.py
@@ -0,0 +1,21 @@
+from ..FFTRegion import FFTRegion
+
+
+class Bethla(FFTRegion):
+ name = "Bethla"
+
+class Bed(FFTRegion):
+ name = "Bed"
+
+class Dolbodar(FFTRegion):
+ name = "Dolbodar"
+
+class Limberry(FFTRegion):
+ name = "Limberry"
+
+class Poeskas(FFTRegion):
+ name = "Poeskas"
+
+limberry_regions = [
+ Bethla, Bed, Dolbodar, Limberry, Poeskas
+]
diff --git a/worlds/fftii/data/logic/regions/Lionel.py b/worlds/fftii/data/logic/regions/Lionel.py
new file mode 100644
index 000000000000..c033796ce4f3
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Lionel.py
@@ -0,0 +1,27 @@
+from ..FFTRegion import FFTRegion
+
+
+class Zaland(FFTRegion):
+ name = "Zaland"
+
+class BariausHill(FFTRegion):
+ name = "Bariaus Hill"
+
+class Lionel(FFTRegion):
+ name = "Lionel"
+
+class Zigolis(FFTRegion):
+ name = "Zigolis"
+
+class Golgorand(FFTRegion):
+ name = "Golgorand"
+
+class BariausValley(FFTRegion):
+ name = "Bariaus Valley"
+
+class Warjilis(FFTRegion):
+ name = "Warjilis"
+
+lionel_regions = [
+ Zaland, BariausHill, Lionel, Zigolis, Golgorand, BariausValley, Warjilis
+]
diff --git a/worlds/fftii/data/logic/regions/Murond.py b/worlds/fftii/data/logic/regions/Murond.py
new file mode 100644
index 000000000000..de4e94132783
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Murond.py
@@ -0,0 +1,21 @@
+from ..FFTRegion import FFTRegion
+
+
+class Murond(FFTRegion):
+ name = "Murond"
+
+class Orbonne(FFTRegion):
+ name = "Orbonne"
+
+class Goug(FFTRegion):
+ name = "Goug"
+
+class DeepDungeon(FFTRegion):
+ name = "Deep Dungeon"
+
+class MurondDeathCity(FFTRegion):
+ name = "Murond Death City"
+
+murond_regions = [
+ Murond, Orbonne, Goug, DeepDungeon, MurondDeathCity
+]
diff --git a/worlds/fftii/data/logic/regions/Zeltennia.py b/worlds/fftii/data/logic/regions/Zeltennia.py
new file mode 100644
index 000000000000..7a57b336a979
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/Zeltennia.py
@@ -0,0 +1,28 @@
+from ..FFTRegion import FFTRegion
+
+
+class BerveniaCity(FFTRegion):
+ name = "Bervenia City"
+
+class Finath(FFTRegion):
+ name = "Finath"
+
+class Zeltennia(FFTRegion):
+ name = "Zeltennia"
+
+class Nelveska(FFTRegion):
+ name = "Nelveska"
+
+class Zarghidas(FFTRegion):
+ name = "Zarghidas"
+
+class Germinas(FFTRegion):
+ name = "Germinas"
+
+class Doguola(FFTRegion):
+ name = "Doguola"
+
+
+zeltennia_regions = [
+ BerveniaCity, Finath, Zeltennia, Nelveska, Zarghidas, Germinas, Doguola
+]
diff --git a/worlds/fftii/data/logic/regions/__init__.py b/worlds/fftii/data/logic/regions/__init__.py
new file mode 100644
index 000000000000..e6099803733c
--- /dev/null
+++ b/worlds/fftii/data/logic/regions/__init__.py
@@ -0,0 +1,7 @@
+from .Fovoham import *
+from .Gallione import *
+from .Lesalia import *
+from .Limberry import *
+from .Lionel import *
+from .Murond import *
+from .Zeltennia import *
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/Fovoham.py b/worlds/fftii/data/logic/topologies/Fovoham.py
new file mode 100644
index 000000000000..653c4e6e138b
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Fovoham.py
@@ -0,0 +1,66 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames, RareBattleLocation
+
+from ..regions.Fovoham import *
+from ..regions.Gallione import Zeakden, Lenalia
+from ..regions.Lesalia import Lesalia, BerveniaVolcano
+from ..regions.Zeltennia import Doguola
+
+Grog.connections = [
+ Connection(Yardow),
+ Connection(Lesalia, [HasLesaliaPass]),
+ Connection(Doguola, [HasZeltenniaPass])
+]
+
+Grog.locations = [
+ FFTLocation(LocationNames.GROG_STORY, battle_level=10),
+ RareBattleLocation(LocationNames.GROG_RARE, battle_level=8)
+]
+
+Yardow.connections = [
+ Connection(Grog),
+ Connection(Yuguo)
+]
+
+Yardow.locations = [
+ FFTLocation(LocationNames.YARDOW_STORY, battle_level=10),
+ FFTLocation(LocationNames.YARDOW_SHOP, battle_level=10)
+]
+
+Yuguo.connections = [
+ Connection(Yardow),
+ Connection(Riovanes)
+]
+
+Yuguo.locations = [
+ FFTLocation(LocationNames.YUGUO_STORY, battle_level=11),
+ RareBattleLocation(LocationNames.YUGUO_RARE, battle_level=8)
+]
+
+Riovanes.connections = [
+ Connection(Fovoham),
+ Connection(Yuguo),
+ Connection(BerveniaVolcano, [HasLesaliaPass]),
+]
+
+Riovanes.locations = [
+ FFTLocation(LocationNames.RIOVANES_1_STORY, battle_level=11),
+ FFTLocation(LocationNames.RIOVANES_2_STORY, battle_level=11),
+ FFTLocation(LocationNames.RIOVANES_3_STORY, battle_level=11),
+ FFTLocation(LocationNames.RIOVANES_SHOP, battle_level=11),
+ FFTLocation(LocationNames.RAMZA_CHAPTER_4_UNLOCK, battle_level=11),
+ FFTLocation(LocationNames.RAFA_RECRUIT, battle_level=11),
+ FFTLocation(LocationNames.MALAK_RECRUIT, battle_level=11)
+]
+
+Fovoham.connections = [
+ Connection(Riovanes),
+ Connection(Zeakden, [HasGallionePass]),
+ Connection(Lenalia, [HasGallionePass])
+]
+
+Fovoham.locations = [
+ FFTLocation(LocationNames.FOVOHAM_STORY, battle_level=1),
+ RareBattleLocation(LocationNames.FOVOHAM_RARE, battle_level=8)
+]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/Gallione.py b/worlds/fftii/data/logic/topologies/Gallione.py
new file mode 100644
index 000000000000..c5a9f471101c
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Gallione.py
@@ -0,0 +1,96 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, RareBattleLocation, LocationNames
+
+from ..regions.Gallione import *
+from ..regions.Fovoham import Fovoham
+from ..regions.Lesalia import Zeklaus, Araguay
+from ..regions.Murond import Murond, Orbonne
+
+Gariland.connections = [
+ Connection(Mandalia),
+ Connection(Sweegy),
+ Connection(Lenalia),
+ Connection(Murond, [HasMurondPass])
+]
+
+Gariland.locations = [
+ FFTLocation(LocationNames.GARILAND_STORY, battle_level=0)
+]
+
+Mandalia.connections = [
+ Connection(Gariland),
+ Connection(ThievesFort),
+ Connection(Igros)
+]
+
+Mandalia.locations = [
+ FFTLocation(LocationNames.MANDALIA_STORY, battle_level=0),
+ FFTLocation(LocationNames.MANDALIA_SHOP, battle_level=0),
+ RareBattleLocation(LocationNames.MANDALIA_RARE, battle_level=8)
+]
+
+Igros.connections = [
+ Connection(Mandalia),
+ Connection(Zeakden)
+]
+
+Igros.locations = [
+ FFTLocation(LocationNames.IGROS_STORY, battle_level=14)
+]
+
+Sweegy.connections = [
+ Connection(Gariland),
+ Connection(Dorter)
+]
+
+Sweegy.locations = [
+ FFTLocation(LocationNames.SWEEGY_STORY, battle_level=1),
+ RareBattleLocation(LocationNames.SWEEGY_RARE, battle_level=8)
+]
+
+Dorter.connections = [
+ Connection(Sweegy),
+ Connection(Zeklaus, [HasLesaliaPass]),
+ Connection(Araguay, [HasLesaliaPass]),
+ Connection(Orbonne, [HasMurondPass])
+]
+
+Dorter.locations = [
+ FFTLocation(LocationNames.DORTER_1_STORY, battle_level=1),
+ FFTLocation(LocationNames.DORTER_2_STORY, battle_level=4)
+]
+
+ThievesFort.connections = [
+ Connection(Mandalia)
+]
+
+ThievesFort.locations = [
+ FFTLocation(LocationNames.THIEVES_FORT_STORY, battle_level=2)
+]
+
+Lenalia.connections = [
+ Connection(Gariland),
+ Connection(Fovoham, [HasFovohamPass])
+]
+
+Lenalia.locations = [
+ FFTLocation(LocationNames.LENALIA_STORY, battle_level=2),
+ FFTLocation(LocationNames.LENALIA_SHOP, battle_level=2),
+ RareBattleLocation(LocationNames.LENALIA_RARE, battle_level=8)
+
+]
+
+Zeakden.connections = [
+ Connection(Igros),
+ Connection(Fovoham, [HasFovohamPass])
+]
+
+Zeakden.locations = [
+ FFTLocation(LocationNames.ZEAKDEN_STORY, battle_level=3),
+ FFTLocation(LocationNames.ZEAKDEN_SHOP, battle_level=3),
+ FFTLocation(LocationNames.RAMZA_CHAPTER_2_UNLOCK, battle_level=3),
+ FFTLocation(LocationNames.RAD_RECRUIT, battle_level=3),
+ FFTLocation(LocationNames.ALICIA_RECRUIT, battle_level=3),
+ FFTLocation(LocationNames.LAVIAN_RECRUIT, battle_level=3)
+]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/Jobs.py b/worlds/fftii/data/logic/topologies/Jobs.py
new file mode 100644
index 000000000000..7c0d6baa943a
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Jobs.py
@@ -0,0 +1,86 @@
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames
+
+from ..regions.Jobs import *
+
+Squire.locations = [
+ FFTLocation(LocationNames.SQUIRE_UNLOCK, [], battle_level=0)
+]
+
+Chemist.locations = [
+ FFTLocation(LocationNames.CHEMIST_UNLOCK, [], battle_level=0)
+]
+
+Knight.locations = [
+ FFTLocation(LocationNames.KNIGHT_UNLOCK, [Requirement(["Squire"])], battle_level=0)
+]
+
+Archer.locations = [
+ FFTLocation(LocationNames.ARCHER_UNLOCK, [Requirement(["Squire"])], battle_level=0)
+]
+
+Monk.locations = [
+ FFTLocation(LocationNames.MONK_UNLOCK, [Requirement(["Knight"])], battle_level=0)
+]
+
+Thief.locations = [
+ FFTLocation(LocationNames.THIEF_UNLOCK, [Requirement(["Archer"])], battle_level=0)
+]
+
+Lancer.locations = [
+ FFTLocation(LocationNames.LANCER_UNLOCK, [Requirement(["Thief"])], battle_level=0)
+]
+
+Geomancer.locations = [
+ FFTLocation(LocationNames.GEOMANCER_UNLOCK, [Requirement(["Monk"])], battle_level=0)
+]
+
+Samurai.locations = [
+ FFTLocation(LocationNames.SAMURAI_UNLOCK, [Requirement(["Knight", "Monk", "Lancer"])], battle_level=0)
+]
+
+Ninja.locations = [
+ FFTLocation(LocationNames.NINJA_UNLOCK, [Requirement(["Archer", "Thief", "Geomancer"])], battle_level=0)
+]
+
+Dancer.locations = [
+ FFTLocation(LocationNames.DANCER_UNLOCK, [Requirement(["Lancer", "Geomancer"])], battle_level=0)
+]
+
+Priest.locations = [
+ FFTLocation(LocationNames.PRIEST_UNLOCK, [Requirement(["Chemist"])], battle_level=0)
+]
+
+Wizard.locations = [
+ FFTLocation(LocationNames.WIZARD_UNLOCK, [Requirement(["Chemist"])], battle_level=0)
+]
+
+Oracle.locations = [
+ FFTLocation(LocationNames.ORACLE_UNLOCK, [Requirement(["Priest"])], battle_level=0)
+]
+
+TimeMage.locations = [
+ FFTLocation(LocationNames.TIME_MAGE_UNLOCK, [Requirement(["Wizard"])], battle_level=0)
+]
+
+Mediator.locations = [
+ FFTLocation(LocationNames.MEDIATOR_UNLOCK, [Requirement(["Oracle"])], battle_level=0)
+]
+
+Summoner.locations = [
+ FFTLocation(LocationNames.SUMMONER_UNLOCK, [Requirement(["Time Mage"])], battle_level=0)
+]
+
+Calculator.locations = [
+ FFTLocation(LocationNames.CALCULATOR_UNLOCK, [Requirement(["Priest", "Wizard", "Oracle", "Time Mage"])], battle_level=0)
+]
+
+Bard.locations = [
+ FFTLocation(LocationNames.BARD_UNLOCK, [Requirement(["Mediator", "Summoner"])], battle_level=0)
+]
+
+Mime.locations = [
+ FFTLocation(LocationNames.MIME_UNLOCK, [
+ Requirement(["Squire", "Chemist", "Lancer", "Geomancer", "Mediator", "Summoner"])
+ ], battle_level=0)
+]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/Lesalia.py b/worlds/fftii/data/logic/topologies/Lesalia.py
new file mode 100644
index 000000000000..170c7654369d
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Lesalia.py
@@ -0,0 +1,79 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames, RareBattleLocation, SidequestLocation
+
+from ..regions.Lesalia import *
+from ..regions.Gallione import Dorter
+from ..regions.Fovoham import Grog, Riovanes
+from ..regions.Limberry import Bethla
+from ..regions.Lionel import Zaland
+
+Araguay.connections = [
+ Connection(Dorter, [HasGallionePass]),
+ Connection(Zirekile)
+]
+
+Araguay.locations = [
+ FFTLocation(LocationNames.ARAGUAY_STORY, battle_level=4),
+ FFTLocation(LocationNames.BOCO_RECRUIT, battle_level=4),
+ RareBattleLocation(LocationNames.ARAGUAY_RARE, battle_level=8)
+]
+
+Zeklaus.connections = [
+ Connection(Dorter, [HasGallionePass]),
+ Connection(Goland),
+ Connection(BerveniaVolcano)
+]
+
+Zeklaus.locations = [
+ FFTLocation(LocationNames.ZEKLAUS_STORY, battle_level=1),
+ FFTLocation(LocationNames.ZEKLAUS_SHOP, battle_level=1),
+ RareBattleLocation(LocationNames.ZEKLAUS_RARE, battle_level=8)
+]
+
+BerveniaVolcano.connections = [
+ Connection(Zeklaus),
+ Connection(Riovanes, [HasFovohamPass])
+]
+
+BerveniaVolcano.locations = [
+ RareBattleLocation(LocationNames.BERVENIA_VOLCANO_RARE, battle_level=8)
+]
+
+Zirekile.connections = [
+ Connection(Araguay),
+ Connection(Zaland, [HasLionelPass]),
+ Connection(Bethla, [HasLimberryPass])
+]
+
+Zirekile.locations = [
+ FFTLocation(LocationNames.ZIREKILE_STORY, battle_level=4),
+ FFTLocation(LocationNames.ZIREKILE_SHOP, battle_level=4),
+ RareBattleLocation(LocationNames.ZIREKILE_RARE, battle_level=8)
+]
+
+Goland.connections = [
+ Connection(Zeklaus),
+ Connection(Lesalia)
+]
+
+Goland.locations = [
+ FFTLocation(LocationNames.GOLAND_STORY, battle_level=8),
+ SidequestLocation(LocationNames.GOLAND_1_SIDEQUEST, battle_level=8),
+ SidequestLocation(LocationNames.GOLAND_2_SIDEQUEST, battle_level=8),
+ SidequestLocation(LocationNames.GOLAND_3_SIDEQUEST, battle_level=8),
+ SidequestLocation(LocationNames.GOLAND_4_SIDEQUEST, battle_level=8),
+ SidequestLocation(LocationNames.BEOWULF_RECRUIT, battle_level=8),
+ SidequestLocation(LocationNames.REIS_DRAGON_RECRUIT, battle_level=8),
+ SidequestLocation(LocationNames.WORKER_8_RECRUIT, battle_level=8)
+]
+
+Lesalia.connections = [
+ Connection(Goland),
+ Connection(Grog, [HasFovohamPass])
+]
+
+Lesalia.locations = [
+ FFTLocation(LocationNames.LESALIA_STORY, battle_level=8),
+ FFTLocation(LocationNames.LESALIA_SHOP, battle_level=8)
+]
diff --git a/worlds/fftii/data/logic/topologies/Limberry.py b/worlds/fftii/data/logic/topologies/Limberry.py
new file mode 100644
index 000000000000..93cce14538de
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Limberry.py
@@ -0,0 +1,63 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames, RareBattleLocation
+
+from ..regions.Limberry import *
+from ..regions.Zeltennia import BerveniaCity, Germinas
+from ..regions.Lesalia import Zirekile
+
+Bed.connections = [
+ Connection(BerveniaCity, [HasZeltenniaPass]),
+ Connection(Bethla)
+]
+
+Bed.locations = [
+ FFTLocation(LocationNames.BED_STORY, battle_level=12),
+ RareBattleLocation(LocationNames.BED_RARE, battle_level=8)
+]
+
+Bethla.connections = [
+ Connection(Bed),
+ Connection(Dolbodar),
+ Connection(Zirekile, [HasLesaliaPass])
+]
+
+Bethla.locations = [
+ FFTLocation(LocationNames.BETHLA_NORTH_STORY, battle_level=12),
+ FFTLocation(LocationNames.BETHLA_SOUTH_STORY, battle_level=12),
+ FFTLocation(LocationNames.BETHLA_SLUICE_STORY, battle_level=12),
+ FFTLocation(LocationNames.BETHLA_SHOP, battle_level=12),
+ FFTLocation(LocationNames.ORLANDU_RECRUIT, battle_level=12)
+]
+
+Dolbodar.connections = [
+ Connection(Bethla),
+ Connection(Limberry)
+]
+
+Dolbodar.locations = [
+ RareBattleLocation(LocationNames.DOLBODAR_RARE, battle_level=8)
+]
+
+Limberry.connections = [
+ Connection(Dolbodar),
+ Connection(Poeskas)
+]
+
+Limberry.locations = [
+ FFTLocation(LocationNames.LIMBERRY_1_STORY, battle_level=13),
+ FFTLocation(LocationNames.LIMBERRY_2_STORY, battle_level=13),
+ FFTLocation(LocationNames.LIMBERRY_3_STORY, battle_level=13),
+ FFTLocation(LocationNames.LIMBERRY_SHOP, battle_level=13),
+ FFTLocation(LocationNames.MELIADOUL_RECRUIT, battle_level=13)
+]
+
+Poeskas.connections = [
+ Connection(Germinas, [HasZeltenniaPass]),
+ Connection(Limberry)
+]
+
+Poeskas.locations = [
+ FFTLocation(LocationNames.POESKAS_STORY, battle_level=13),
+ RareBattleLocation(LocationNames.POESKAS_RARE, battle_level=8)
+]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/Lionel.py b/worlds/fftii/data/logic/topologies/Lionel.py
new file mode 100644
index 000000000000..6ccf2e16cd9b
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Lionel.py
@@ -0,0 +1,75 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames, RareBattleLocation
+
+from ..regions.Lionel import *
+from ..regions.Lesalia import Zirekile
+from ..regions.Murond import Goug, DeepDungeon
+
+Zaland.connections = [
+ Connection(Zirekile, [HasLesaliaPass]),
+ Connection(BariausHill)
+]
+
+Zaland.locations = [
+ FFTLocation(LocationNames.ZALAND_STORY, battle_level=5)
+]
+
+BariausHill.connections = [
+ Connection(Zaland),
+ Connection(Lionel)
+]
+
+BariausHill.locations = [
+ FFTLocation(LocationNames.BARIAUS_HILL_STORY, battle_level=5),
+ FFTLocation(LocationNames.BARIAUS_HILL_SHOP, battle_level=5),
+ RareBattleLocation(LocationNames.BARIAUS_HILL_RARE, battle_level=8)
+]
+
+Lionel.connections = [
+ Connection(BariausHill),
+ Connection(Zigolis),
+ Connection(BariausValley)
+]
+
+Lionel.locations = [
+ FFTLocation(LocationNames.LIONEL_1_STORY, battle_level=7),
+ FFTLocation(LocationNames.LIONEL_2_STORY, battle_level=7),
+ FFTLocation(LocationNames.LIONEL_SHOP, battle_level=7)
+]
+
+BariausValley.connections = [
+ Connection(Lionel),
+ Connection(Golgorand),
+ Connection(Warjilis)
+]
+
+BariausValley.locations = [
+ FFTLocation(LocationNames.BARIAUS_VALLEY_STORY, battle_level=6),
+ FFTLocation(LocationNames.BARIAUS_VALLEY_SHOP, battle_level=6),
+ FFTLocation(LocationNames.AGRIAS_RECRUIT, battle_level=6),
+ RareBattleLocation(LocationNames.BARIAUS_VALLEY_RARE, battle_level=8)
+]
+
+Golgorand.connections = [
+ Connection(BariausValley)
+]
+
+Golgorand.locations = [
+ FFTLocation(LocationNames.GOLGORAND_STORY, battle_level=7)
+]
+
+Warjilis.connections = [
+ Connection(BariausValley),
+ Connection(DeepDungeon, [HasMurondPass])
+]
+
+Zigolis.connections = [
+ Connection(Lionel),
+ Connection(Goug, [HasMurondPass])
+]
+
+Zigolis.locations = [
+ FFTLocation(LocationNames.ZIGOLIS_STORY, battle_level=6),
+ RareBattleLocation(LocationNames.ZIGOLIS_RARE, battle_level=8)
+]
diff --git a/worlds/fftii/data/logic/topologies/Murond.py b/worlds/fftii/data/logic/topologies/Murond.py
new file mode 100644
index 000000000000..eb06a3abfc97
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Murond.py
@@ -0,0 +1,77 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames, SidequestLocation, VanillaFinalFightsLocation, \
+ AltimaOnlyFinalFightLocation
+
+from ..regions.Murond import *
+from ..regions.Gallione import Gariland, Dorter
+from ..regions.Lionel import Zigolis, Warjilis
+
+Murond.connections = [
+ Connection(Gariland, [HasGallionePass]),
+ Connection(Goug)
+]
+
+Murond.locations = [
+ FFTLocation(LocationNames.MUROND_TEMPLE_1_STORY, battle_level=14),
+ FFTLocation(LocationNames.MUROND_TEMPLE_2_STORY, battle_level=14),
+ FFTLocation(LocationNames.MUROND_TEMPLE_3_STORY, battle_level=14)
+]
+
+Goug.connections = [
+ Connection(Murond),
+ Connection(Zigolis, [HasLionelPass])
+]
+
+Goug.locations = [
+ FFTLocation(LocationNames.GOUG_STORY, battle_level=6),
+ FFTLocation(LocationNames.MUSTADIO_RECRUIT, battle_level=6)
+]
+
+Orbonne.connections = [
+ Connection(Dorter, [HasGallionePass])
+]
+
+Orbonne.locations = [
+ FFTLocation(LocationNames.UBS_1_STORY, battle_level=9),
+ FFTLocation(LocationNames.UBS_2_STORY, battle_level=9),
+ FFTLocation(LocationNames.UBS_3_STORY, battle_level=9),
+ FFTLocation(LocationNames.ORBONNE_SHOP, battle_level=9),
+ AltimaOnlyFinalFightLocation(LocationNames.UBS_4_STORY, battle_level=14),
+ AltimaOnlyFinalFightLocation(LocationNames.UBS_5_STORY, battle_level=14),
+ AltimaOnlyFinalFightLocation(LocationNames.MUROND_DEATH_CITY_STORY, battle_level=14),
+ AltimaOnlyFinalFightLocation(LocationNames.PRECINCTS_STORY, battle_level=14),
+ AltimaOnlyFinalFightLocation(LocationNames.AIRSHIPS_1_STORY, battle_level=14)
+]
+
+DeepDungeon.connections = [
+ Connection(Warjilis, [HasLionelPass]),
+ Connection(MurondDeathCity, [HasZodiacStones])
+]
+
+DeepDungeon.locations = [
+ SidequestLocation(LocationNames.NOGIAS_SIDEQUEST, battle_level=10),
+ SidequestLocation(LocationNames.TERMINATE_SIDEQUEST, battle_level=10),
+ SidequestLocation(LocationNames.DELTA_SIDEQUEST, battle_level=10),
+ SidequestLocation(LocationNames.VALKYRIES_SIDEQUEST, battle_level=10),
+ SidequestLocation(LocationNames.MLAPAN_SIDEQUEST, battle_level=12),
+ SidequestLocation(LocationNames.TIGER_SIDEQUEST, battle_level=12),
+ SidequestLocation(LocationNames.BRIDGE_SIDEQUEST, battle_level=12),
+ SidequestLocation(LocationNames.VOYAGE_SIDEQUEST, battle_level=12),
+ SidequestLocation(LocationNames.HORROR_SIDEQUEST, battle_level=12),
+ SidequestLocation(LocationNames.END_SIDEQUEST, battle_level=14),
+ SidequestLocation(LocationNames.BYBLOS_RECRUIT, battle_level=14),
+]
+
+MurondDeathCity.connections = [
+ Connection(DeepDungeon)
+]
+
+MurondDeathCity.locations = [
+ VanillaFinalFightsLocation(LocationNames.UBS_4_STORY, battle_level=14),
+ VanillaFinalFightsLocation(LocationNames.UBS_5_STORY, battle_level=14),
+ VanillaFinalFightsLocation(LocationNames.MUROND_DEATH_CITY_STORY, battle_level=14),
+ VanillaFinalFightsLocation(LocationNames.PRECINCTS_STORY, battle_level=14),
+ VanillaFinalFightsLocation(LocationNames.AIRSHIPS_1_STORY, battle_level=14),
+ FFTLocation(LocationNames.AIRSHIPS_2_STORY, battle_level=14)
+]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/Zeltennia.py b/worlds/fftii/data/logic/topologies/Zeltennia.py
new file mode 100644
index 000000000000..187562a95454
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/Zeltennia.py
@@ -0,0 +1,76 @@
+from ..Connection import Connection
+from ..Requirements import *
+from ..FFTLocation import FFTLocation, LocationNames, RareBattleLocation, SidequestLocation
+
+from ..regions.Zeltennia import *
+from ..regions.Fovoham import Grog
+from ..regions.Limberry import Bed, Poeskas
+
+BerveniaCity.connections = [
+ Connection(Doguola, [HasLesaliaPass]),
+ Connection(Bed, [HasLimberryPass]),
+ Connection(Finath)
+]
+
+BerveniaCity.locations = [
+ FFTLocation(LocationNames.BERVENIA_CITY_STORY, battle_level=12)
+]
+
+Finath.connections = [
+ Connection(BerveniaCity),
+ Connection(Zeltennia)
+]
+
+Finath.locations = [
+ FFTLocation(LocationNames.FINATH_STORY, battle_level=12),
+ RareBattleLocation(LocationNames.FINATH_RARE, battle_level=8)
+]
+
+Zeltennia.connections = [
+ Connection(Finath),
+ Connection(Zarghidas),
+ Connection(Nelveska)
+]
+
+Zeltennia.locations = [
+ FFTLocation(LocationNames.ZELTENNIA_STORY, battle_level=12)
+]
+
+Zarghidas.connections = [
+ Connection(Zeltennia),
+ Connection(Germinas)
+]
+
+Zarghidas.locations = [
+ SidequestLocation(LocationNames.ZARGHIDAS_SIDEQUEST, battle_level=14),
+ SidequestLocation(LocationNames.CLOUD_RECRUIT, battle_level=14)
+]
+
+Germinas.connections = [
+ Connection(Zarghidas),
+ Connection(Poeskas, [HasLimberryPass])
+]
+
+Germinas.locations = [
+ FFTLocation(LocationNames.GERMINAS_STORY, battle_level=13),
+ RareBattleLocation(LocationNames.GERMINAS_RARE, battle_level=8)
+]
+
+Nelveska.connections = [
+ Connection(Zeltennia)
+]
+
+Nelveska.locations = [
+ SidequestLocation(LocationNames.NELVESKA_SIDEQUEST, battle_level=12),
+ SidequestLocation(LocationNames.REIS_HUMAN_RECRUIT, battle_level=12)
+]
+
+Doguola.connections = [
+ Connection(Grog),
+ Connection(BerveniaCity)
+]
+
+Doguola.locations = [
+ FFTLocation(LocationNames.DOGUOLA_STORY, battle_level=12),
+ RareBattleLocation(LocationNames.DOGUOLA_RARE, battle_level=8)
+]
\ No newline at end of file
diff --git a/worlds/fftii/data/logic/topologies/__init__.py b/worlds/fftii/data/logic/topologies/__init__.py
new file mode 100644
index 000000000000..c1530f2f841f
--- /dev/null
+++ b/worlds/fftii/data/logic/topologies/__init__.py
@@ -0,0 +1 @@
+from . import Gallione, Fovoham, Lesalia, Lionel, Zeltennia, Limberry, Murond, Jobs
\ No newline at end of file
diff --git a/worlds/fftii/data/memory.py b/worlds/fftii/data/memory.py
new file mode 100644
index 000000000000..f7b132dbd47d
--- /dev/null
+++ b/worlds/fftii/data/memory.py
@@ -0,0 +1,485 @@
+from .logic.FFTLocation import LocationNames
+
+cd_name_location = 0x9304
+cd_name = "SCUS_942.21"
+
+world_bin_start = 0x0BD00408
+
+rom_name_location = 0x00028CC0
+volume_name_location = 0x00009340
+seed_hash_location = 0x0BD6A136
+
+rom_name_location_in_ram = 0x027058
+rom_name_length = 20
+seed_hash_location_in_ram = 0x13C2AE
+seed_hash_length = 2
+
+seed_hash_in_memory_card = 0x0578CE
+
+items_received_low = 0x578CE
+items_received_high = 0x578CF
+items_obtained_size = 2
+
+world_loaded_address = 0x0E0228
+world_loaded_length = 4
+world_loaded_value = [0x27, 0xBD, 0xFF, 0xE8]
+
+inventory_start_address = 0x0596E0
+shop_progression_address = 0x0578D8
+
+war_funds_address = 0x0577CC
+war_funds_length = 4
+
+zodiac_stones_1_address = 0x057974
+zodiac_stones_2_address = 0x057975
+
+# These could have been with the event flags but I wrote them first so here they are in this format.
+stones_lookup = {
+ "Aries": (zodiac_stones_1_address, 3),
+ "Taurus": (zodiac_stones_1_address, 4),
+ "Gemini": (zodiac_stones_1_address, 5),
+ "Cancer": (zodiac_stones_1_address, 6),
+ "Leo": (zodiac_stones_1_address, 7),
+ "Virgo": (zodiac_stones_2_address, 0),
+ "Libra": (zodiac_stones_2_address, 1),
+ "Scorpio": (zodiac_stones_2_address, 2),
+ "Sagittarius": (zodiac_stones_2_address, 3),
+ "Capricorn": (zodiac_stones_2_address, 4),
+ "Aquarius": (zodiac_stones_2_address, 5),
+ "Pisces": (zodiac_stones_2_address, 6),
+ "Serpentarius": (zodiac_stones_2_address, 7),
+}
+
+unit_stats_address = 0x057F74
+unit_stat_size = 0x100
+unit_count = 16
+unit_stats_length = unit_stat_size * unit_count
+party_id_offset = 0x01
+jp_offset = 0x6E
+job_amount = 20
+
+temp_unit_stats_address = 0x1C8638
+temp_unit_stat_size = 0x128
+temp_unit_count = 16
+temp_unit_stats_length = temp_unit_stat_size * temp_unit_count
+temp_party_id_offset = 0x01
+temp_jp_offset = 0xBE
+temp_job_amount = 20
+
+event_flags_location = 0x05791C
+event_flags_length = 0x224
+
+
+
+story_addresses = {
+ "Gariland Magic City Story Battle": 0x94,
+ "Mandalia Plains Story Battle": 0x95,
+ "Igros Castle Story Battle": 0x96,
+ "Sweegy Woods Story Battle": 0x97,
+ "Dorter Slums Story Battle": 0x98,
+ "Dorter City Story Battle": 0x99,
+ "Thieves' Fort Story Battle": 0x9A,
+ "Lenalia Plateau Story Battle": 0x9B,
+ "Fort Zeakden Story Battle": 0x9C,
+ "Grog Hill Story Battle": 0x9D,
+ "Yardow Fort City Story Battle": 0x9E,
+ "Yuguo Woods Story Battle": 0x9F,
+ "Gate of Riovanes Story Battle": 0xA0,
+ "Inside Riovanes Castle Story Battle": 0xA1,
+ "Roof of Riovanes Castle Story Battle": 0xA2,
+ "Fovoham Plains Story Battle": 0xA3,
+ "Araguay Woods Story Battle": 0xA4,
+ "Zirekile Falls Story Battle": 0xA5,
+ "Zeklaus Desert Story Battle": 0xA6,
+ "Back Gate of Lesalia Castle Story Battle": 0xA7,
+ "Goland Coal City Story Battle": 0xA8,
+ "Doguola Pass Story Battle": 0xA9,
+ "Zaland Fort City Story Battle": 0xAA,
+ "Bariaus Hill Story Battle": 0xAB,
+ "Gate of Lionel Castle Story Battle": 0xAC,
+ "Inside of Lionel Castle Story Battle": 0xAD,
+ "Zigolis Swamp Story Battle": 0xAE,
+ "Golgorand Execution Site Story Battle": 0xAF,
+ "Bariaus Valley Story Battle": 0xB0,
+ "Bervenia Free City Story Battle": 0xB1,
+ "Finath River Story Battle": 0xB2,
+ "Zeltennia Castle Story Battle": 0xB3,
+ "Germinas Peak Story Battle": 0xB4,
+ "Bethla Garrison North Wall Story Battle": 0xB5,
+ "Bethla Garrison South Wall Story Battle": 0xB6,
+ "Bethla Garrison Sluice Story Battle": 0xB7,
+ "Bed Desert Story Battle": 0xB8,
+ "Limberry Castle Gates Story Battle": 0xB9,
+ "Inside Limberry Castle Story Battle": 0xBa,
+ "Limberry Castle Cemetary Story Battle": 0xBB,
+ "Poeskas Lake Story Battle": 0xBC,
+ "St. Murond Temple Story Battle": 0xBD,
+ "St. Murond Temple Hall Story Battle": 0xBE,
+ "Chapel of St. Murond Temple Story Battle": 0xBF,
+ "Underground Book Storage 1 Story Battle": 0xC0,
+ "Underground Book Storage 2 Story Battle": 0xC1,
+ "Underground Book Storage 3 Story Battle": 0xC2,
+ "Underground Book Storage 4 Story Battle": 0xC3,
+ "Underground Book Storage 5 Story Battle": 0xC4,
+ "Slums of Goug Story Battle": 0xC5,
+ "Murond Death City Story Battle": 0xC6,
+ "Lost Sacred Precincts Story Battle": 0xC7,
+ "Graveyard of Airships 1 Story Battle": 0xC8,
+ "Graveyard of Airships 2 Story Battle": 0xC9
+}
+
+sidequest_addresses = {
+ "Goland Colliery Third Floor Sidequest Battle": 0xDC,
+ "Goland Colliery Second Floor Sidequest Battle": 0xDD,
+ "Goland Colliery First Floor Sidequest Battle": 0xDE,
+ "Goland Underground Passage Sidequest Battle": 0xDF,
+ "Nelveska Temple Sidequest Battle": 0xE0,
+ "Zarghidas Trade City Sidequest Battle": 0xE1,
+ "NOGIAS Sidequest Battle": 0xE2,
+ "TERMINATE Sidequest Battle": 0xE3,
+ "DELTA Sidequest Battle": 0xE4,
+ "VALKYRIES Sidequest Battle": 0xE5,
+ "MLAPAN Sidequest Battle": 0xE6,
+ "TIGER Sidequest Battle": 0xE7,
+ "BRIDGE Sidequest Battle": 0xE8,
+ "VOYAGE Sidequest Battle": 0xE9,
+ "HORROR Sidequest Battle": 0xEA,
+ "END Sidequest Battle": 0xEB,
+}
+
+rare_battle_addresses = {
+ "Mandalia Plains Rare Battle": 0x11C,
+ "Sweegy Woods Rare Battle": 0xCA,
+ "Lenalia Plateau Rare Battle": 0xCB,
+ "Grog Hill Rare Battle": 0xCC,
+ "Yuguo Woods Rare Battle": 0xCD,
+ "Fovoham Plains Rare Battle": 0xCe,
+ "Araguay Woods Rare Battle": 0xCF,
+ "Zirekile Falls Rare Battle": 0xD0,
+ "Zeklaus Desert Rare Battle": 0xD1,
+ "Bervenia Volvano Rare Battle": 0xD2,
+ "Doguola Pass Rare Battle": 0xD3,
+ "Bariaus Hill Rare Battle": 0xD4,
+ "Zigolis Swamp Rare Battle": 0xD5,
+ "Bariaus Valley Rare Battle": 0xD6,
+ "Finath River Rare Battle": 0xD7,
+ "Germinas Peak Rare Battle": 0xD8,
+ "Bed Desert Rare Battle": 0xD9,
+ "Dolbodar Swamp Rare Battle": 0xDA,
+ "Poeskas Lake Rare Battle": 0xDB
+}
+
+# shouldn't need these but noting just in case
+shop_unlock_addresses = {
+ "Mandalia Plains Shop Unlock": 0xEC,
+ "Lenalia Plateau Shop Unlock": 0xED,
+ "Fort Zeakden Shop Unlock": 0xEE,
+ "Yardow Fort City Shop Unlock": 0xEF,
+ "Riovanes Castle Shop Unlock": 0xF0,
+ "Zirekile Falls Shop Unlock": 0xF1,
+ "Zeklaus Desert Shop Unlock": 0xF2,
+ "Lesalia Imperial Capital Shop Unlock": 0xF3,
+ "Bariaus Hill Shop Unlock": 0xF4,
+ "Lionel Castle Shop Unlock": 0xF5,
+ "Bariaus Valley Shop Unlock": 0xF6,
+ "Bethla Garrison Shop Unlock": 0xF7,
+ "Limberry Castle Shop Unlock": 0xF8,
+ "Orbonne Monastery Shop Unlock": 0xF9
+}
+
+# These are set when the character is received
+character_recruit_addresses = {
+ "Rafa": 0xFA,
+ "Malak": 0xFB,
+ "Boco": 0xFC,
+ "Beowulf": 0xFD,
+ "Worker 8": 0xFE,
+ "Agrias": 0xFF,
+ "Reis (Dragon)": 0x100,
+ "Reis (Human)": 0x101,
+ "Cloud": 0x102,
+ "Orlandu": 0x103,
+ "Meliadoul": 0x104,
+ "Mustadio": 0x105,
+ "Byblos": 0x106,
+ "Rad": 0x107,
+ "Lavian": 0x108,
+ "Alicia": 0x109
+}
+
+# These are set when the unlock is received.
+ramza_job_unlock_addresses = {
+ "Chapter 2 Ramza Squire Job Unlock": 0x11A,
+ "Chapter 4 Ramza Squire Job Unlock": 0x11B
+}
+
+# These are set during patching
+yaml_options = {
+ "RareBattles": (0x00348931, 0x04),
+ "Sidequests": (0x00348931, 0x02),
+ "FinalBattles": (0x00348931, 0x01),
+ "EXPMultiplier": 0x35565C,
+ "JPMultiplier": 0x355670
+}
+
+# This is written to the client to unlock jobs
+available_jobs_addresses = {
+ "Time Mage": 0x190,
+ "Wizard": 0x191,
+ "Priest": 0x192,
+ "Monk": 0x193,
+ "Archer": 0x194,
+ "Knight": 0x195,
+ "Chemist": 0x196,
+ "Squire": 0x197,
+ "Ninja": 0x198,
+ "Samurai": 0x199,
+ "Lancer": 0x19A,
+ "Geomancer": 0x19B,
+ "Oracle": 0x19C,
+ "Mediator": 0x19D,
+ "Thief": 0x19E,
+ "Summoner": 0x19F,
+ "Mime": 0x1A4,
+ "Dancer": 0x1A5,
+ "Bard": 0x1A6,
+ "Calculator": 0x1A7
+}
+
+locations_to_read = {
+ **story_addresses, **sidequest_addresses, **rare_battle_addresses
+}
+
+# We offset a bunch of flags by 0x80 because the bitflag offset starts later than the flag bits do
+# as the first set of flags correspond to words, not bits...but we want the flag numbers to match our notes
+
+for location, flag in locations_to_read.items():
+ locations_to_read[location] = flag - 0x80
+
+for location, flag in available_jobs_addresses.items():
+ available_jobs_addresses[location] = flag - 0x80
+
+for location, flag in character_recruit_addresses.items():
+ character_recruit_addresses[location] = flag - 0x80
+
+for location, flag in ramza_job_unlock_addresses.items():
+ ramza_job_unlock_addresses[location] = flag - 0x80
+
+all_main_bits = {
+ **story_addresses, **sidequest_addresses, **shop_unlock_addresses, **character_recruit_addresses,
+ **ramza_job_unlock_addresses, **available_jobs_addresses
+}
+
+for location, flag in all_main_bits.items():
+ all_main_bits[location] = flag - 0x80
+
+poaching_flags_location = 0x0578C4
+poaching_flags_length = 8
+
+poaching_addresses = {
+ "Poach Yellow Chocobo": 0x00,
+ "Poach Black Chocobo": 0x01,
+ "Poach Red Chocobo": 0x02,
+ "Poach Goblin": 0x03,
+ "Poach Black Goblin": 0x04,
+ "Poach Gobbledeguck": 0x05,
+ "Poach Bomb": 0x06,
+ "Poach Grenade": 0x07,
+ "Poach Explosive": 0x08,
+ "Poach Red Panther": 0x09,
+ "Poach Cuar": 0x0A,
+ "Poach Vampire": 0x0B,
+ "Poach Pisco Demon": 0x0C,
+ "Poach Squidlarkin": 0x0D,
+ "Poach Mindflare": 0x0E,
+ "Poach Skeleton": 0x0F,
+ "Poach Bone Snatch": 0x10,
+ "Poach Living Bone": 0x11,
+ "Poach Ghoul": 0x12,
+ "Poach Gust": 0x13,
+ "Poach Revnant": 0x14,
+ "Poach Floatiball": 0x15,
+ "Poach Ahriman": 0x16,
+ "Poach Plague": 0x17,
+ "Poach Juravis": 0x18,
+ "Poach Steel Hawk": 0x19,
+ "Poach Cocatoris": 0x1A,
+ "Poach Uribo": 0x1B,
+ "Poach Porky": 0x1C,
+ "Poach Wildbow": 0x1D,
+ "Poach Woodman": 0x1E,
+ "Poach Trent": 0x1F,
+ "Poach Taiju": 0x20,
+ "Poach Bull Demon": 0x21,
+ "Poach Minitaurus": 0x22,
+ "Poach Sacred": 0x23,
+ "Poach Morbol": 0x24,
+ "Poach Ochu": 0x25,
+ "Poach Great Morbol": 0x26,
+ "Poach Behemoth": 0x27,
+ "Poach King Behemoth": 0x28,
+ "Poach Dark Behemoth": 0x29,
+ "Poach Dragon": 0x2A,
+ "Poach Blue Dragon": 0x2B,
+ "Poach Red Dragon": 0x2C,
+ "Poach Hyudra": 0x2D,
+ "Poach Hydra": 0x2E,
+ "Poach Tiamat": 0x2F,
+}
+
+job_level_offset = 0x64
+job_level_order = [
+ "Squire",
+ "Chemist",
+ "Knight",
+ "Archer",
+ "Monk",
+ "Priest",
+ "Wizard",
+ "Time Mage",
+ "Summoner",
+ "Thief",
+ "Mediator",
+ "Oracle",
+ "Geomancer",
+ "Lancer",
+ "Samurai",
+ "Ninja",
+ "Calculator",
+ "Bard",
+ "Dancer",
+ "Mime"
+]
+
+pass_paths = {
+ "Gallione Pass": {
+ "Fovoham Pass": [
+ 0x231 - 0x80, # Lenalia <> Fovoham
+ 0x232 - 0x80 # Fovoham <> Zeakden
+ ],
+ "Lesalia Pass": [
+ 0x236 - 0x80, # Dorter <> Zeklaus
+ 0x240 - 0x80 # Dorter <> Araguay
+ ],
+ "Murond Pass": [
+ 0x23F - 0x80, # Dorter <> Orbonne
+ 0x25A - 0x80 # Gariland <> Murond Temple
+ ]
+ },
+ "Fovoham Pass": {
+ "Lesalia Pass": [
+ 0x234 - 0x80, # Riovanes <> Bervenia Volcano
+ 0x23C - 0x80 # Grog <> Lesalia
+ ],
+ "Zeltennia Pass": [
+ 0x246 - 0x80, # Grog <> Doguola
+ ]
+ },
+ "Lesalia Pass": {
+ "Lionel Pass": [
+ 0x250 - 0x80 # Zirekile <> Zaland
+ ],
+ "Limberry Pass": [
+ 0x242 - 0x80 # Zirekile <> Bethla
+ ]
+ },
+ "Lionel Pass": {
+ "Murond Pass": [
+ 0x258 - 0x80, # Goug <> Zigolis
+ 0x255 - 0x80 # Warjilis <> Deep Dungeon
+ ]
+ },
+ "Zeltennia Pass": {
+ "Limberry Pass": [
+ 0x244 - 0x80, # Bervenia City <> Bed
+ 0x24C - 0x80 # Germinas <> Poeskas
+ ]
+ },
+}
+
+finale_path = 0x25B - 0x80 # Formerly Goug <> Warjilis, now Deep Dungeon <> Murond Death City
+game_started_flag_address = 0x18E - 0x80
+
+
+victory_text_offsets = {
+ LocationNames.GARILAND_STORY.value: 0x86D5A0,
+ LocationNames.MANDALIA_STORY.value: [
+ 0x88439F, 0x886A26
+ ],
+ LocationNames.IGROS_STORY.value: 0xC44725,
+ LocationNames.SWEEGY_STORY.value: 0x89B335,
+ LocationNames.DORTER_1_STORY.value: 0x8A45F8,
+ LocationNames.DORTER_2_STORY.value: 0x965739,
+ LocationNames.THIEVES_FORT_STORY.value: 0x8C48B3,
+ LocationNames.LENALIA_STORY.value: 0x8EBADE,
+ LocationNames.ZEAKDEN_STORY.value: 0x91BD3A,
+
+ LocationNames.GROG_STORY.value: 0xAA24B9,
+ LocationNames.YARDOW_STORY.value: 0xAB95A0,
+ LocationNames.YUGUO_STORY.value: 0xAC4BF2,
+ LocationNames.RIOVANES_1_STORY.value: 0xAD7348,
+ LocationNames.RIOVANES_2_STORY.value: 0xAE7335,
+ LocationNames.RIOVANES_3_STORY.value: 0xAF5168,
+ LocationNames.FOVOHAM_STORY.value: 0x8FE378,
+
+ LocationNames.ARAGUAY_STORY.value: 0x9757C8,
+ LocationNames.ZIREKILE_STORY.value: 0x98A0F8,
+ LocationNames.ZEKLAUS_STORY.value: 0x8AFDF8,
+ LocationNames.LESALIA_STORY.value: 0xA5FAF0,
+ LocationNames.GOLAND_STORY.value: 0xA2D238,
+
+ LocationNames.ZALAND_STORY.value: 0x99EC0A,
+ LocationNames.BARIAUS_HILL_STORY.value: 0x9AEE89,
+ LocationNames.LIONEL_1_STORY.value: 0xA11976,
+ LocationNames.LIONEL_2_STORY.value: 0xA1ADD8,
+ LocationNames.ZIGOLIS_STORY.value: 0x9C82E0,
+ LocationNames.GOLGORAND_STORY.value: 0xA01828,
+ LocationNames.BARIAUS_VALLEY_STORY.value: 0x9ECF1C,
+
+ LocationNames.DOGUOLA_STORY.value: 0xB50D78,
+ LocationNames.BERVENIA_CITY_STORY.value: 0xB5EBAA,
+ LocationNames.FINATH_STORY.value: 0xB65838,
+ LocationNames.ZELTENNIA_STORY.value: 0xB759A8,
+ LocationNames.GERMINAS_STORY.value: 0xBB8338,
+
+ LocationNames.BETHLA_NORTH_STORY.value: 0xB95C31,
+ LocationNames.BETHLA_SOUTH_STORY.value: 0xB8EDF1,
+ LocationNames.BETHLA_SLUICE_STORY.value: [
+ 0xBA3894, 0xBA8214
+ ],
+ LocationNames.BED_STORY.value: 0xB8364D,
+ LocationNames.LIMBERRY_1_STORY.value: 0xBCAAE3,
+ LocationNames.LIMBERRY_2_STORY.value: 0xBDAC70,
+ LocationNames.LIMBERRY_3_STORY.value: 0xBE6275,
+ LocationNames.POESKAS_STORY.value: 0xBBF178,
+
+ LocationNames.MUROND_TEMPLE_1_STORY.value: 0xC4FCE3,
+ LocationNames.MUROND_TEMPLE_2_STORY.value: 0xC5B4C6,
+ LocationNames.MUROND_TEMPLE_3_STORY.value: 0xC66E1C,
+ LocationNames.UBS_1_STORY.value: 0xA8920B,
+ LocationNames.UBS_2_STORY.value: 0xA6FC46,
+ LocationNames.UBS_3_STORY.value: 0xA7B599,
+ LocationNames.UBS_4_STORY.value: 0xB09C48,
+ LocationNames.UBS_5_STORY.value: 0xB12F5E,
+ LocationNames.GOUG_STORY.value: 0x9D3922,
+ LocationNames.MUROND_DEATH_CITY_STORY.value: 0xB209FB,
+ LocationNames.PRECINCTS_STORY.value: 0xB29E56,
+ LocationNames.AIRSHIPS_1_STORY.value: 0xB331FD,
+ LocationNames.AIRSHIPS_2_STORY.value: 0xB3C2D6,
+
+ LocationNames.GOLAND_1_SIDEQUEST.value: 0xC89378,
+ LocationNames.GOLAND_2_SIDEQUEST.value: 0xC901B8,
+ LocationNames.GOLAND_3_SIDEQUEST.value: 0xC96FF8,
+ LocationNames.GOLAND_4_SIDEQUEST.value: 0xCA27B8,
+ LocationNames.NELVESKA_SIDEQUEST.value: 0xCB05DA,
+ LocationNames.ZARGHIDAS_SIDEQUEST.value: 0xA4FC25,
+
+ LocationNames.END_SIDEQUEST.value: 0x9553FB,
+
+}
+
+rare_battles_offset = 0x871478
+dd_battles_offset = 0x929978
+
+total_jp_boon_gained = 0x057930
+total_jp_boon_gained_length = 2
\ No newline at end of file
diff --git a/worlds/fftii/data/text.py b/worlds/fftii/data/text.py
new file mode 100644
index 000000000000..1bd92c4b530b
--- /dev/null
+++ b/worlds/fftii/data/text.py
@@ -0,0 +1,252 @@
+from docutils.io import InputError
+
+from BaseClasses import ItemClassification
+from .items import zodiac_stone_names
+
+
+class TextData:
+ character: str
+ id: int
+ width: int
+
+ def __init__(self, character: str, id: int, width: int = 6):
+ self.character = character
+ self.id = id
+ self.width = width
+
+
+all_characters: list[TextData] = [
+ TextData("0", 0x00),
+ TextData("1", 0x01),
+ TextData("2", 0x02),
+ TextData("3", 0x03),
+ TextData("4", 0x04),
+ TextData("5", 0x05),
+ TextData("6", 0x06),
+ TextData("7", 0x07),
+ TextData("8", 0x08),
+ TextData("9", 0x09),
+
+ TextData("A", 0x0A),
+ TextData("B", 0x0B),
+ TextData("C", 0x0C),
+ TextData("D", 0x0D),
+ TextData("E", 0x0E),
+ TextData("F", 0x0F),
+ TextData("G", 0x10),
+ TextData("H", 0x11),
+ TextData("I", 0x12, 4),
+ TextData("J", 0x13),
+ TextData("K", 0x14),
+ TextData("L", 0x15),
+ TextData("M", 0x16),
+ TextData("N", 0x17),
+ TextData("O", 0x18),
+ TextData("P", 0x19),
+ TextData("Q", 0x1A),
+ TextData("R", 0x1B),
+ TextData("S", 0x1C),
+ TextData("T", 0x1D),
+ TextData("U", 0x1E),
+ TextData("V", 0x1F),
+ TextData("W", 0x20),
+ TextData("X", 0x21),
+ TextData("Y", 0x22),
+ TextData("Z", 0x23),
+
+ TextData("a", 0x24, 4),
+ TextData("b", 0x25, 4),
+ TextData("c", 0x26, 4),
+ TextData("d", 0x27, 4),
+ TextData("e", 0x28, 4),
+ TextData("f", 0x29, 4),
+ TextData("g", 0x2A, 4),
+ TextData("h", 0x2B, 4),
+ TextData("i", 0x2C, 2),
+ TextData("j", 0x2D, 4),
+ TextData("k", 0x2E, 4),
+ TextData("l", 0x2F, 2),
+ TextData("m", 0x30, 6),
+ TextData("n", 0x31, 4),
+ TextData("o", 0x32, 4),
+ TextData("p", 0x33, 4),
+ TextData("q", 0x34, 4),
+ TextData("r", 0x35, 4),
+ TextData("s", 0x36, 4),
+ TextData("t", 0x37, 4),
+ TextData("u", 0x38, 4),
+ TextData("v", 0x39, 4),
+ TextData("w", 0x3A, 6),
+ TextData("x", 0x3B, 4),
+ TextData("y", 0x3C, 4),
+ TextData("z", 0x3D, 4),
+
+ TextData("!", 0x3E, 4),
+ TextData("?", 0x40),
+ #TextData("+", 0x42),
+ TextData("/", 0x44),
+ TextData(":", 0x46, 4),
+ TextData(".", 0x5F, 4),
+ TextData("(", 0x8D, 4),
+ TextData(")", 0x8E, 4),
+ TextData("\"", 0x91, 4),
+ TextData("'", 0x93, 4),
+ TextData("♪", 0xB2, 10),
+ TextData("*", 0xB5, 10),
+ TextData(",", 0xDA74, 4),
+ #TextData(".", 0xD11C, 2),
+
+ TextData("-", 0xD11D, 2),
+ TextData("+", 0xD11E, 2),
+ TextData("×", 0xD11F, 2),
+ TextData("÷", 0xD120, 2),
+
+ TextData("&", 0xD9B7),
+ TextData("%", 0xD9B8),
+
+ TextData("{Aries}", 0xDA00, 10),
+ TextData("{Taurus}", 0xDA01, 10),
+ TextData("{Gemini}", 0xDA02, 10),
+ TextData("{Cancer}", 0xDA03, 10),
+ TextData("{Leo}", 0xDA04, 10),
+ TextData("{Virgo}", 0xDA05, 10),
+ TextData("{Libra}", 0xDA06, 10),
+ TextData("{Scorpio}", 0xDA07, 10),
+ TextData("{Sagittarius}", 0xDA08, 10),
+ TextData("{Capricorn}", 0xDA09, 10),
+ TextData("{Aquarius}", 0xDA0A, 10),
+ TextData("{Pisces}", 0xDA0B, 10),
+ TextData("{Serpentarius}", 0xDA0C, 10),
+
+ TextData("=", 0xDA0C, 6),
+ TextData("$", 0xDA0C, 6),
+ TextData("¥", 0xDA0C, 6),
+
+ TextData(" ", 0xDA73, 2),
+ TextData("{unitname}", 0xE1),
+ TextData("{Newline}", 0xF8),
+ TextData("{End}", 0xFE),
+ TextData("{ColorNormal}", 0xE300, 0),
+ TextData("{ColorSpecial1}", 0xE304, 0),
+ TextData("{ColorSpecial2}", 0xE308, 0),
+ TextData("{ColorSpecial3}", 0xE3FF, 0)
+]
+
+text_data_lookup = {
+ text_data.character: text_data for text_data in all_characters
+}
+
+max_text_width = 180
+
+def create_text_for_offworld_item(player_name: str, item_name: str, classification: ItemClassification, is_fft_item: bool):
+ if is_fft_item and item_name in zodiac_stone_names:
+ item_string = f"{{{item_name}}}" + f"{item_name}"
+ else:
+ item_string = f"{item_name}"
+ if classification == ItemClassification.progression:
+ item_string = "{ColorSpecial2}" + f"{item_string}" + "{ColorNormal}"
+ elif classification == ItemClassification.useful:
+ item_string = f"{item_string}"
+ elif classification == ItemClassification.trap:
+ item_string = "{ColorSpecial1}" + f"{item_string}" + "{ColorNormal}"
+ else:
+ item_string = "{ColorSpecial1}" + f"{item_string}" + "{ColorNormal}"
+ return f"{player_name}'s {item_string}"
+
+def create_text_for_own_item(item_name: str, classification: ItemClassification):
+ if item_name in zodiac_stone_names:
+ item_string = f"{{{item_name}}}" + f"{item_name}"
+ else:
+ item_string = f"{item_name}"
+ if classification == ItemClassification.progression:
+ item_string = "{ColorSpecial2}" + f"{item_string}" + "{ColorNormal}"
+ elif classification == ItemClassification.useful:
+ item_string = f"{item_string}"
+ elif classification == ItemClassification.trap:
+ item_string = "{ColorSpecial1}" + f"{item_string}" + "{ColorNormal}"
+ else:
+ item_string = "{ColorSpecial1}" + f"{item_string}" + "{ColorNormal}"
+ return f"their own {item_string}"
+
+def split_text_into_lines(location_text: str) -> tuple[list[str], list[list[int]]]:
+ lines: list[str] = []
+ byte_lines: list[list[int]] = []
+ current_line_width = 0
+ index = 0
+ current_line = ""
+ current_bytes = []
+ last_space_index = 0
+ while index < len(location_text):
+ character = location_text[index]
+ if character not in ["{", "}"]:
+ data_key = character
+ index += 1
+ elif character == "{":
+ start = index
+ end = location_text.index("}", start)
+ data_key = location_text[start:end + 1]
+ index = end + 1
+ else:
+ print(character)
+ print(index)
+ raise InputError("We shouldn't be processing this.")
+ if data_key not in text_data_lookup.keys():
+ data_key = "?"
+ data = text_data_lookup[data_key]
+ if current_line_width + data.width > max_text_width:
+ last_space = current_line.rfind(" ")
+ last_space_byte = -1
+ for byte_index, value in enumerate(current_bytes):
+ if value == 0x73:
+ last_space_byte = byte_index
+ if last_space == -1:
+ lines.append(current_line)
+ byte_lines.append(current_bytes)
+ current_line = data_key
+ current_bytes = [data.id]
+ current_line_width = 0
+ else:
+ lines.append(current_line[:last_space])
+ byte_lines.append(current_bytes[:last_space_byte - 1])
+ current_line = current_line[last_space + 1:] + data_key
+ current_bytes = current_bytes[last_space_byte + 1:]
+ if data.id > 0xFF:
+ current_bytes.append((data.id & 0xFF00) >> 8)
+ current_bytes.append(data.id & 0xFF)
+ else:
+ current_bytes.append(data.id)
+ current_line_width = 0
+ sub_index = 0
+ while sub_index < len(current_line):
+ sub_character = current_line[sub_index]
+ if sub_character not in ["{", "}"]:
+ sub_data_key = character
+ sub_index += 1
+ elif sub_character == "{":
+ sub_start = sub_index
+ sub_end = current_line.index("}", sub_start)
+ sub_data_key = current_line[sub_start:sub_end + 1]
+ sub_index = sub_end + 1
+ else:
+ print(character)
+ print(index)
+ raise InputError("We shouldn't be processing this.")
+ if sub_data_key not in text_data_lookup.keys():
+ sub_data_key = "?"
+ sub_data = text_data_lookup[sub_data_key]
+ current_line_width += sub_data.width
+ else:
+ current_line_width += data.width
+ current_line += data_key
+ if data.id > 0xFF:
+ current_bytes.append((data.id & 0xFF00) >> 8)
+ current_bytes.append(data.id & 0xFF)
+ else:
+ current_bytes.append(data.id)
+ if len(current_line) > 0:
+ lines.append(current_line)
+ byte_lines.append(current_bytes)
+ for line in byte_lines[:-1]:
+ line.append(text_data_lookup["{Newline}"].id)
+ byte_lines[-1].append(text_data_lookup["{End}"].id)
+ return lines, byte_lines
\ No newline at end of file
diff --git a/worlds/fftii/docs/en_Final Fantasy Tactics Ivalice Island.md b/worlds/fftii/docs/en_Final Fantasy Tactics Ivalice Island.md
new file mode 100644
index 000000000000..476311110218
--- /dev/null
+++ b/worlds/fftii/docs/en_Final Fantasy Tactics Ivalice Island.md
@@ -0,0 +1,50 @@
+# Final Fantasy Tactics Ivalice Island
+
+## What is this game?
+
+Final Fantasy Tactics Ivalice Island is an open world randomizer for Final Fantasy Tactics designed for Archipelago.
+
+## 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.
+
+## What does randomization do to this game?
+
+- The world map is no longer unlocked through linear story progression.
+- - Instead, regional passes must be acquired to unlock the locations in those regions
+- - The regions are Gallione (where you start by default), Fovoham, Lesalia, Lionel, Zeltennia, Limberry, and Murond.
+- The goal of the game is still to travel to Murond Death City and defeat Altima.
+- - However, in order to unlock the way there, a number of Zodiac Stones must be acquired first from the multiworld.
+- Character recruitments, shop inventory unlocks, jobs, and Ramza's unique Squire job are unlocked as multiworld items.
+
+## What items and locations get shuffled?
+
+### Items
+- Key progression items (world map passes and Zodiac Stones)
+- Unique character recruitments
+- Shop inventory unlocks
+- Ramza's unique Squire job chapter upgrades
+- All gear and consumable items
+- Items containing an amount of gil added straight to your War Funds.
+- Items containing an amount of JP added straight to every characer's jobs.
+- Optionally, enabling jobs besides Squire for your team can be in the item pool.
+
+### Locations
+- Every required story battle in the game is a location
+- Events that would normally reward a character, shop inventory unlock, or progress Ramza's job form are also locations.
+- Optionally, sidequest battles can be included in the location pool.
+- Optionally, the "rare" battle associated with each battleground can be in the location pool.
+- Optionally, meeting the requirements to unlock a new job can be in the location pool.
+- Optionally, poaching monsters with Secret Hunt can be added in the location pool
+
+## What items from Final Fantasy Tactics Ivalice Island can appear in other players' worlds?
+
+All items can appear in other players' worlds.
+
+## What does another world's item look like in Final Fantasy Tactics Ivalice Island?
+
+Items do not have a unique appearance in Final Fantasy Tactics Ivalice Island. Items received after battle events
+will be displayed in text in a custom event. Other items (job unlock rewards and poach rewards) will be shown
+in the client.
+
diff --git a/worlds/fftii/docs/faq.md b/worlds/fftii/docs/faq.md
new file mode 100644
index 000000000000..fa0c5f546fe1
--- /dev/null
+++ b/worlds/fftii/docs/faq.md
@@ -0,0 +1,58 @@
+# Final Fantasy Tactics Ivalice Island FAQ
+
+## What is this?
+
+Final Fantasy Tactics Ivalice Island is an open world randomizer for Final Fantasy Tactics for the PS1,
+designed from the ground up for Archipelago.
+
+## What does "open world" mean here?
+
+You begin the game in Gariland, just like in vanilla.
+However, you may proceed to any location in the Gallione region in whatever order you choose.
+Unlocking various Pass items for other regions will open up travel to those regions, providing more locations to visit.
+Story battles may be accessed in any order, with each one providing items upon completion.
+
+The goal of the game is to collect enough Zodiac Stones to unlock the way to Murond Death City and defeat Altima.
+
+## What are the differences from the vanilla game?
+
+- World map paths are unlocked through acquiring Pass items for each region
+(Gallione, Fovoham, Lesalia, Lionel, Zeltennia, Limberry, Murond).
+- The Zodiac Stones are required in order to access the final series of fights at Murond Death City.
+- Shop inventory is no longer tied to specific events in the story, and is instead added to as you collect specific items.
+- Ramza's unique Squire job no longer progresses with specific events, and is instead upgraded as you collect specific items.
+- Guests no longer join your party, and instead will have fixed levels and equipment in the battles they participate in.
+- Characters no longer join after specific battles in the story, and instead are found as items.
+- Random battle formations for each battlefield are unlocked in a uniform timing with shop inventory progress.
+- Optionally, the following changes can be done:
+- - You begin the game with just Squire unlocked, and must unlock the other jobs as items.
+- - The "rare" special battle in each battlefield can give an item reward.
+- - Poaching each monster type can give an additional item reward on top of the Fur Shop reward.
+- - Sidequest battles and recruitments can give an item reward.
+
+## What Quality of Life options have been added?
+
+- JP Boon items have been added, adding JP to your character's jobs to make acquiring abilities easier
+- JP gains have been multiplied across the board (configurable with an option) to make unlocking new jobs easier.
+
+## Where can the Zodiac Stones be on the Vanilla Stones setting?
+
+- Inside of Lionel Castle
+- Slums of Goug
+- Inside Riovanes Castle
+- Roof of Riovanes Castle
+- Bethla Garrison Sluice
+- Inside Limberry Castle
+- Limberry Castle Cemetary
+- Igros Castle
+- Graveyard of Airships 1 (only when Altima Only is enabled)
+- Goland Underground Passage (only when sidequests are enabled)
+- Nelveska Temple (only when sidequests are enabled)
+- END (only when sidequests are enabled)
+
+Because in vanilla the Limberry Castle Cemetary gives out two stones (one from Zalera and one from Meliadoul),
+Inside Limberry Castle was chosen as a possible location for a Zodiac Stone to maintain some parity with vanilla.
+
+## Can I play this single player?
+
+Yes, though a server connection is still required, as most items are given by the client.
\ No newline at end of file
diff --git a/worlds/fftii/docs/how_to_play.md b/worlds/fftii/docs/how_to_play.md
new file mode 100644
index 000000000000..ff63314762b3
--- /dev/null
+++ b/worlds/fftii/docs/how_to_play.md
@@ -0,0 +1,142 @@
+# Final Fantasy Tactics Ivalice Island Instruction Manual
+
+## Table of Contents
+- [Concepts](#concepts)
+- - [Introduction](#introduction)
+- - [Controls](#controls)
+- - [Goal](#goal)
+- - [Zodiac Stones](#zodiac-stones)
+- - [Passes](#passes)
+- - [Jobs](#jobs)
+- - [Shop Inventory](#shop-inventory)
+- - [Character Recruitment](#character-recruitments)
+- - [Ramza's Squire Job](#ramzas-squire-job)
+- [Options](#options)
+- - [Zodiac Stones](#zodiac-stones-1)
+- - [Final Battles](#final-battles)
+- - [Job Unlocks](#job-unlocks)
+- - [Rare Battles](#rare-battles)
+- - [Poach Locations](#poach-locations)
+- - [Logical Difficulty](#logical-difficulty)
+- - [Filler Item Weights](#filler-item-weights)
+
+## Concepts
+
+### Introduction
+_Final Fantasy Tactics Ivalice Island_ is an open world randomizer mod for Final Fantasy Tactics for the PS1, developed
+specifically for Archipelago. Your goal is to collect enough **Zodiac Stones** to open the way to Murond Death City and
+defeat Altima in the Graveyard of Airships. You will begin the game in Gallione, at Gariland. As you play, you will
+unlock various **Pass** items that will open the paths to the other regions of Ivalice. Completing story battles and other
+goals will unlock items and upgrades to aid you in your journey.
+
+### Controls
+On the world map, there are a few special controls used to interface with the changes made for Ivalice Island.
+
+- Press `R2` to load into a menu for your current location that will allow you to select an event battle at that location
+to engage in. For example, pressing R2 when at Dorter Trade City will load a menu allowing you to select the Dorter Slums
+battle from Chapter 1 or the Dorter City battle from Chapter 2.
+- Press `L2` to process any received character recruitments and Ramza job form upgrades.
+- When traveling on the world map, hold `O` to engage in a random battle at your destination.
+
+### Goal
+The objective of Ivalice Island is to collect enough **Zodiac Stones** to unlock the route to Murond Death City.
+Once enough are collected, the route to Murond Death City will appear connecting from the Deep Dungeon.
+
+### Zodiac Stones
+Depending on your options, up to thirteen Zodiac Stones can be in the item pool, and up to thirteen can be required to
+unlock Murond Death City. Aside from unlocking the route there, Zodiac Stones have no effect.
+
+### Passes
+In order to access locations beyond Gallione, Passes are required. For example, to access the region of Fovoham, the
+**Fovoham Pass** is required, and to access the region of Lesalia, the **Lesalia Pass** is required.
+
+Simply having a **Pass** isn't necessarily enough. For some regions, you will also need the **Pass** for adjoining
+regions. For example, traveling to Lionel requires the **Lionel Pass**, of course, but you will also need the
+**Lesalia Pass** or **Murond Pass** as those regions lie between Gallione and Lionel.
+
+A full list of what locations belong to what regions can be found in the [References document](./references.md).
+
+### Jobs
+If enabled in your options, jobs will be items you can acquire and meeting the requirements to unlock a job will be
+a location that provides an item. Once acquired, a job is unlocked for all units immediately. You will always begin
+the game with the Squire job unlocked.
+
+As Squire and Chemist have no requirements to unlock, you will receive the items at those locations immediately upon
+beginning the game.
+
+### Shop inventory
+At the start of the game, you begin with a limited shop inventory, just like in the vanilla game. Obtaining
+**Progressive Shop Unlock** items will unlock more items in the various stores of Ivalice.
+
+Additionally, receiving more **Progressive Shop Unlock** items will unlock more random battle formations at each
+battlefield.
+
+### Character Recruitments
+All sixteen unique characters (both forms of Reis are included) are included as items in the item pool. Characters may
+be dismissed, but if they are, they cannot be reacquired.
+
+### Ramza's Squire Job
+Ramza begins the game in his Chapter 1 Squire job. Obtaining **Progressive Ramza Job Form** items will progress his job to
+their Chapter 2 and Chapter 4 forms.
+
+### Filler Items
+Filler items not belonging to the above categories will be included. These can be normal shop items, rare unbuyable
+items, bonus gil items, and bonuses of spendable JP for all units in your roster.
+
+## Options
+
+### Zodiac Stones
+The **Zodiac Stones** required to reach Murond Death City can be customized. The amount in the pool and amount
+required can be adjusted between one and thirteen each. If the number required is more than the number in the pool,
+additional **Zodiac Stones** will be added to the pool to make up the difference.
+
+**Zodiac Stone** placement can also be customized.
+
+- On the *Vanilla Stones* setting, eight story locations and three sidequest locations (if enabled) that hold
+**Zodiac Stones** in the vanilla game will be eligible to have a **Zodiac Stone** (see the
+[References document](./references.md)). If the number of **Zodiac Stones** in the settings are greater than the
+number of locations available, the number of **Zodiac Stones** in the pool will be reduced to match.
+- On the *Anywhere Local* setting, **Zodiac Stones** can be on any location in your own world.
+- On the *Anywhere* settings, **Zodiac Stones** can be anywhere in the multiworld.
+
+### Final Battles
+The finale at Murond Death City can be customized. On the *Vanilla* setting, the six final battles of the game must
+be completed in order to clear the game: Underground Book Storage 4, Underground Book Storage 5, Murond Death City,
+Lost Sacred Precincts, Graveyard of Airships 1, Graveyard of Airships 2. On the *Altima Only* setting, only the
+Graveyard of Airships 2 fight against Altima must be completed. The other five fights are relocated to Orbonne
+Monastery.
+
+### Job Unlocks
+If enabled, jobs and job unlocks will be items and locations in the multiworld. You will begin the game with just the
+**Squire** job unlocked. Meeting the requirements for a unit to unlock a job will instead send a location check. For
+example, reaching Squire level 2 will send the items for **Knight Unlock** and **Archer Unlock**. Receiving a job
+from the multiworld will unlock that job for all units.
+
+If disabled, jobs will be unlocked as they normally are in the vanilla game.
+
+### Rare Battles
+If enabled, the rare battle formation for every battlefield will be a location check. While in the vanilla game these
+would appear randomly from one particular direction, in _Ivalice Island_ these are accessed from the same menu
+story battles are found in once eight **Progressive Shop Level** items have been acquired.
+
+If disabled, these battles will still be accessible but will not provide a multiworld item.
+
+### Poach Locations
+If enabled, poaching monsters with the Support ability Secret Hunt will be location checks. Depending on what Passes
+have been obtained, recruiting a monster with the Action ability Invitation or the Support ability Train and
+then hatching another of the same family can be required.
+
+If disabled, poaching monsters will not provide a multiworld item.
+
+Regardless of the setting chosen, a monster's normal Poach rewards will be available from Fur Shops.
+
+### Logical Difficulty
+This setting affects the placement of job items (when Job Unlocks are enabled) and **Progressive Shop Level** items.
+A certain number of each are logically required for battles as you progress through the game, ensuring you have a
+minimum amount of options before being required to take on a fight. Higher difficulties will logically require fewer
+**Progressive Shop Level** items and job items, with Extreme requiring none at all: player beware!
+
+### Filler Item Weights
+These options adjust the weighting of filler items in the multiworld item pool. Setting a category to a weight of zero
+will ensure it does not appear in the item pool, unless all categories are set to zero, in which case an even amount of
+each will be chosen.
\ No newline at end of file
diff --git a/worlds/fftii/docs/known_issues.md b/worlds/fftii/docs/known_issues.md
new file mode 100644
index 000000000000..e26b436f2c07
--- /dev/null
+++ b/worlds/fftii/docs/known_issues.md
@@ -0,0 +1,13 @@
+# Known Issues
+
+Anything in this list is a known issue and is either due to be fixed at a later date or won't be fixed
+due to the high difficulty to fix and low impact to the player.
+
+- When moving Ramza on the overworld, if your destination is more than sixteen spaces away, he won't move.
+- - This can be worked around by just moving him in multiple separate steps.
+- When learning abilities from crystals, what abilities you have access to learn are based off of
+vanilla job unlock requirements, not which jobs you've been sent through the multiworld.
+- Units in some battle events may behave strangely such as facing the wrong direction or displaying
+an incorrect sprite. These do not affect gameplay.
+- When receiving inventory items while in the formation screen, you have to re-sort your inventory or
+re-enter the formation screen for new items to be equippable.
\ No newline at end of file
diff --git a/worlds/fftii/docs/multiworld_en.md b/worlds/fftii/docs/multiworld_en.md
new file mode 100644
index 000000000000..2c522189b54d
--- /dev/null
+++ b/worlds/fftii/docs/multiworld_en.md
@@ -0,0 +1,49 @@
+# Setup Guide for Final Fantasy Tactics Ivalice Island Archipelago
+
+## Important
+
+As we are using Bizhawk, this guide is only applicable to Windows and Linux systems.
+
+## Required Software
+
+- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
+ - Detailed installation instructions for Bizhawk can be found at the above link.
+ - Windows users must run the prerequisite installer first, which can also be found at the above link.
+- The built-in Bizhawk client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
+- A US copy of Final Fantasy Tactics
+- - The MD5 hash will be "b156ba386436d20fd5ed8d37bab6b624"
+
+## 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 options by visiting the
+[Final Fantasy Tactics Ivalice Island Options Page](/games/Final Fantasy Tactics Ivalice Island/player-options)
+
+## Joining a MultiWorld Game
+
+### Obtain your PS1 patch file
+
+When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done,
+the host will provide you with either a link to download your data file, or with a zip file containing everyone's data
+files. Your data file should have a `.apfftii` extension.
+
+Double-click on your `.apfftii` file to start your client and start the ROM patch process.
+Once the process is finished, the client and the emulator will be started automatically (if you associated the extension
+to the emulator as recommended).
+
+### Connect to the Multiserver
+
+Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools"
+menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script.
+
+Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`.
+
+To connect the client to the multiserver simply put `:` on the textfield on top and press enter (if the
+server uses password, type in the bottom textfield `/connect : [password]`)
diff --git a/worlds/fftii/docs/references.md b/worlds/fftii/docs/references.md
new file mode 100644
index 000000000000..e8ae5f057f68
--- /dev/null
+++ b/worlds/fftii/docs/references.md
@@ -0,0 +1,399 @@
+# Table of Contents
+1. [Regions and Locations](#regions-and-locations)
+2. [Poach Locations](#poach-locations)
+3. [Location and Item Group Names](#location-and-item-group-names)
+
+# Regions and Locations
+
+## Gallione
+- Gariland Magic City
+- Mandalia Plains
+- Igros Castle
+- Sweegy Woods
+- Dorter Trade City
+- Thieves' Fort
+- Lenalia Plateau
+- Fort Zeakden
+
+## Fovoham
+- Fovoham Plains
+- Riovanes Castle
+- Yuguo Woods
+- Yardow Fort City
+- Grog Hill
+
+## Lesalia
+- Zeklaus Desert
+- Bervenia Volcano
+- Araguay Woods
+- Zirekile Falls
+- Goland Coal City
+- Lesalia Imperial Capital
+
+## Lionel
+- Zaland Fort City
+- Bariaus Hill
+- Lionel Castle
+- Bariaus Valley
+- Zigolis Swamp
+- Warjilis Trade City
+
+## Zeltennia
+- Doguola Pass
+- Bervenia Free City
+- Finath River
+- Zeltennia Castle
+- Nelveska Temple
+- Zarghidas Trade City
+- Germinas Peak
+
+## Limberry
+- Poeskas Lake
+- Limberry Castle
+- Dolbodar Swamp
+- Bed Desert
+- Bethla Garrison
+
+## Murond
+- Murond Holy Place
+- Goug Machine City
+- Orbonne Monastery
+- Deep Dungeon
+- Murond Death City
+
+# Poach Locations
+- [Preface](#preface)
+- [Chocobo Family](#chocobo-family)
+- [Goblin Family](#goblin-family)
+- [Bomb Family](#bomb-family)
+- [Panther Family](#panther-family)
+- [Pisco Demon Family](#pisco-demon-family)
+- [Skeleton Family](#skeleton-family)
+- [Ghoul Family](#ghoul-family)
+- [Floatiball Family](#floatiball-family)
+- [Juravis Family](#juravis-family)
+- [Woodman Family](#woodman-family)
+- [Bull Demon Family](#bull-demon-family)
+- [Morbol Family](#morbol-family)
+- [Behemoth Family](#behemoth-family)
+- [Dragon Family](#dragon-family)
+- [Hyudra Family](#hyudra-family)
+
+
+## Preface
+This reference lists every monster and a location they can be found. This list is not exhaustive; rather, it's the
+locations used for the internal logic of the APWorld on if a monster is accessible. The locations provided are
+intended to be those with the fewest requirements to reach, not necessarily the easiest fights or most common spawns.
+
+The number following the battlefield is the formation number for that direction. Formation 1 is available from the
+beginning of the game. Formation 2 requires two **Progressive Shop Levels** to be obtained, Formation 3 requires five,
+and Formation 4 as well as the Rare battle require eight.
+
+## Chocobo Family
+
+### Yellow Chocobo
+- Mandalia Plains 1 (from Gariland Magic City)
+
+### Black Chocobo
+- Mandalia Plains 3 (from Thieves' Fort)
+- Fovoham Plains 1 (from Riovanes Castle)
+- Zirekile Falls 2 (from Bethla Garrison)
+- Zirekile Falls 1 (from Zaland Fort City)
+
+### Red Chocobo
+- Lenalia Plateau 3 (from Gariland Magic City)
+- Grog Hill 2 (from Lesalia Imperial Capital)
+- Bariaus Hill 1 (from Lionel Castle)
+- Finath River 1 (from Bervenia Free City)
+
+
+## Goblin Family
+
+### Goblin
+- Mandalia Plains 1 (from Thieves' Fort)
+
+### Black Goblin
+- Sweegy Woods 1 (from Gariland Magic City)
+
+### Gobbledeguck
+- Mandalia Plains 3 (from Igros Castle)
+- Bariaus Valley 2 (from Warjilis Trade City)
+
+
+## Bomb Family
+
+### Bomb
+- Sweegy Woods 1 (from Gariland Magic City)
+
+### Grenade
+- Mandalia Plains 3 (from Thieves' Fort)
+- Grog Hill 1 (from Yardow Fort City)
+- Zeklaus Desert 1 (from Bervenia Volcano)
+
+### Explosive
+- Sweegy Woods 4 (from Gariland Magic City)
+- Grog Hill 3 (from Doguola Pass)
+- Bervenia Volcano 2 (from Riovanes Castle)
+- Bervenia Volcano 3 (from Zeklaus Desert)
+- Bariaus Hill 3 (from Lionel Castle)
+- Poeskas Lake 1 (from Limberry Castle)
+
+
+## Panther Family
+
+### Red Panther
+- Mandalia Plains 1 (from Igros Castle)
+
+### Cuar
+- Sweegy Woods 3 (from Gariland Magic City)
+- Lenalia Plateau 2 (from Fovoham Plains)
+- Araguay Woods 1 (from Zirekile Falls)
+- Bariaus Valley 2 (from Golgorand Execution Site)
+- Finath River 1 (from Bervenia Free City)
+
+### Vampire
+- Sweegy Woods 3 (from Gariland Magic City)
+- Barius Valley 2 (from Warjilis Trade City)
+- Bed Desert 2 (from Bervenia Free City)
+
+
+## Pisco Demon Family
+
+### Pisco Demon
+- Lenalia Plateau 2 (from Gariland Magic City)
+- Lenalia Plateau 1 (from Fovoham Plains)
+- Zirekile Falls 1 (from Araguay Woods)
+- Barius Valley 1 (from Warjilis Trade City)
+
+### Squidlarkin
+- Lenalia Plateau 2 (from Gariland Magic City)
+- Fovoham Plains 1 (from Riovanes Castle)
+- Zirekile Falls 1 (from Araguay Woods)
+- Barius Valley 1 (from Warjilis Trade City)
+
+### Mindflare
+- Fovoham Plains 2 (from Riovanes Castle)
+- Zirekile Falls 2 (from Zaland Fort City)
+- Barius Valley 2 (from Warjilis Trade City)
+- Finath River 1 (from Zeltennia Castle)
+
+
+## Skeleton Family
+
+### Skeleton
+- Sweegy Woods 1 (from Gariland Magic City)
+
+### Bone Snatch
+- Sweegy Woods 3 (from Gariland Magic City)
+- Yuguo Woods 1 (from Yardow Fort City)
+- Araguay Woods 1 (from Zirekile Falls)
+- Zigolis Swamp 1 (from Lionel Castle)
+
+### Living Bone
+- Sweegy Woods 3 (from Gariland Magic City)
+- Dolbodar Swmp 2 (from Limberry Castle)
+
+
+## Ghoul Family
+
+### Ghoul
+- Yuguo Woods 1 (from Riovanes Castle)
+- Araguay Woods 1 (from Dorter Trade City)
+- Zigolis Swamp 1 (from Lionel Castle)
+
+### Gust
+- Yuguo Woods 1 (from Riovanes Castle)
+- Araguay Woods 2 (from Dorter Trade City)
+- Zigolis Swamp 2 (from Lionel Castle)
+- Poeskas Lake 1 (from Limberry Castle)
+
+### Revnant
+- Yuguo Woods 3 (from Riovanes Castle)
+- Araguay Woods 3 (from Dorter Trade City)
+- Zigolis Swamp 3 (from Lionel Castle)
+
+
+## Floatiball Family
+
+### Floatiball
+- Fovoham Plains 1 (from Riovanes Castle)
+- Zirekile Falls 1 (from Araguay Woods)
+- Zigolis Swamp 1 (from Lionel Castle)
+
+### Ahriman
+- Fovoham Plains 1 (from Riovanes Castle)
+- Bed Desert 1 (from Bethla Garrison)
+- Zigolis Swamp 2 (from Goug Machine City)
+
+### Plague
+- Fovoham Plains 4 (from Riovanes Castle)
+- Bervenia Volcano 3 (from Riovanes Castle)
+- Bariaus Valley Rare
+- Germinas Peak 3 (from Zarghidas Trade City)
+
+
+## Juravis Family
+
+### Juravis
+- Fovoham Plains 1 (from Fort Zeakden)
+- Bervenia Volcano 1 (from Zeklaus Desert)
+- Barius Valley 2 (from Warjilis Trade City)
+
+### Steel Hawk
+- Fovoham Plains 2 (from Fort Zeakden)
+- Zeklaus Desert 1 (from Bervenia Volcano)
+- Bariaus Valley 2 (from Warjilis Trade City)
+- Germinas Peak 1 (from Zaghidas Trade City)
+
+### Cocatoris
+- Fovoham Plains 1 (from Riovanes Castle)
+- Zeklaus Desert 3 (from Bervenia Volcano)
+- Bariaus Valley 3 (from Warjilis Trade City)
+- Poeskas Lake 2 (from Germinas Peak)
+
+
+## Uribo Family
+
+### Uribo
+- Zigolis Swamp Story
+- Finath River Story
+- Dolbodar Swamp 2 (from Bethla Garrison)
+- Goland Colliery First Floor (when Sidequest Battles are enabled)
+
+### Porky
+- Dolbodar Swamp Rare
+
+### Wildbow
+- Egg only
+
+
+## Woodman Family
+
+### Woodman
+- Sweegy Woods 3 (from Gariland Magic City)
+- Yuguo Woods 1 (from Riovanes Castle)
+- Doguola Pass 2 (from Bervenia Free City)
+
+### Trent
+- Sweegy Woods 3 (from Dorter Trade City)
+- Yuguo Woods 1 (from Yardow Fort City)
+- Araguay Woods 2 (from Zirekile Falls)
+
+### Taiju
+- Yuguo Woods 3 (from Yardow Fort City)
+- Araguay Woods 3 (from Zirekile Falls)
+- MLAPAN (when Sidequest Battles are enabled)
+
+
+## Bull Demon Family
+
+### Bull Demon
+- Sweegy Woods 1 (from Gariland Magic City)
+
+### Minitaurus
+- Fovoham Plains 3 (from Riovanes Castle)
+- Zeklaus Desert 3 (from Dorter Trade City)
+- Bariaus Valley 2 (from Warjilis Trade City)
+- Germinas Peak 1 (from Zarghidas Trade City)
+- Poeskas Lake 2 (from Limberry Castle)
+
+### Sacred
+- Fovoham Plains 3 (from Lenalia Plateau)
+- Zeklaus Desert 3 (from Goland Coal City)
+- Bariaus Hill 3 (from Lionel Castle)
+
+
+## Morbol Family
+
+### Morbol
+- Mandalia Plains 3 (from Igros Castle)
+- Fovoham Plains 2 (from Riovanes Castle)
+- Araguay Woods 2 (from Zirekile Falls)
+- Zigolis Swamp 1 (from Lionel Castle)
+- Finath River 1 (from Zeltennia Castle)
+- Dolbodar Swamp 1 (from Bethla Garrison)
+
+### Ochu
+- Lenalia Plateau 3 (from Gariland Magic City)
+- Finath River 1 (from Bervenia Free City)
+
+### Great Morbol
+- Finath River 4 (from Zeltennia Castle)
+
+
+## Behemoth Family
+
+### Behemoth
+- Bervenia Volcano 2 (from Zeklaus Desert)
+- Bariaus Valley 3 (from Golgorand Execution Site)
+- Doguola Pass 1 (from Bervenia Free City)
+- Poeskas Lake 1 (from Limberry Castle)
+
+### King Behemoth
+- Bervenia Volcano Rare
+- Bariaus Hill Rare
+- Bed Desert 3 (from Bethla Garrison)
+- Poeskas Lake 1 (from Germinas Peak)
+
+### Dark Behemoth
+- Bariaus Hill Rare
+- Poeskas Lake 4 (from Limberry Castle)
+
+
+## Dragon Family
+
+### Dragon
+- Lenalia Plateau 1 (from Gariland Magic City)
+
+### Blue Dragon
+- Mandalia Plains Rare
+- Grog Hill 1 (from Yardow Fort City)
+- Bariaus Valley 3 (from Golgorand Execution Site)
+- Finath River 4 (from Bervenia Free City)
+- Bed Desert 3 (from Bervenia Free City)
+- Bed Desert 4 (from Bethla Garrison)
+
+### Red Dragon
+- Mandalia Plains Rare
+- Zeklaus Desert 3 (from Bervenia Volcano)
+- Bariaus Hill Rare
+- Finath River 1 (from Zeltennia Castle)
+
+
+## Hyudra Family
+
+### Hyudra
+- Bariaus Hill Rare
+- Nelveska Temple (when Sidequest Battles are enabled)
+
+### Hydra
+- Bariaus Hill Rare
+
+### Tiamat
+- Bariaus Hill Rare
+
+# Location and Item Group Names
+
+## Preface
+These are the location name and item name groups for Ivalice Island. These are for use in general item and location
+options such as `local_items` and `excluded_locations` in your YAML.
+
+### Location Groups
+- `Story Battles`
+- - Includes all battles mandatory for the story in the vanilla game.
+- `Sidequest Battles`
+- - Includes all optional event battles from the vanilla game.
+- `Rare Battles`
+- - Includes the special fifth formation for every random battlefield.
+- `Poaches`
+- - Includes the Archipelago locations for every poached monster. Does not affect vanilla rewards in the Fur Shop.
+- `Job Unlocks`
+- - Includes the Archipelago locations for unlocking individual jobs. Does not affect the awarded job items.
+
+### Item Groups
+- `Zodiac Stones`
+- - Includes all thirteen Zodiac Stones.
+- `Characters`
+- - Includes all sixteen special recruitable characters.
+- `Jobs`
+- - Includes all twenty jobs. Note that Squire will always be a starting item, regardless of how this setting is used.
\ No newline at end of file
diff --git a/worlds/fftii/docs/rom_changes.md b/worlds/fftii/docs/rom_changes.md
new file mode 100644
index 000000000000..83abe4ee935e
--- /dev/null
+++ b/worlds/fftii/docs/rom_changes.md
@@ -0,0 +1,19 @@
+## Changes from vanilla
+
+- The world map is no longer unlocked through linear story progression.
+- - Instead, regional passes must be acquired to unlock the locations in those regions
+- - The regions are Gallione (where you start by default), Fovoham, Lesalia, Lionel, Zeltennia, Limberry, and Murond.
+- Story and sidequest battles are now accessed through pressing R2 while on their associated location dot.
+- Randomly occuring battles no longer happen.
+- - Instead, they are accessed by holding Circle while traveling to their location dot.
+- Character recruitments and Ramza job upgrades are accessed by pressing L2 while on a location dot.
+- The goal of the game is still to travel to Murond Death City and defeat Altima.
+- - However, in order to unlock the way there, a number of Zodiac Stones must be acquired first from the multiworld.
+- Character recruitments, shop inventory unlocks, jobs, and Ramza's unique Squire job are unlocked as multiworld items.
+- Characters are recruited with no equipment and in their base job at job level 2.
+- - However, they will receive the benefit of JP Boon items obtained prior to their recruitment.
+- The Deep Dungeon is fully lit.
+- Rafa and Malak's unique abilities now can hit up to ten times (like in the War of the Lions
+and Ivalice Chronicles versions).
+- Cloud can use Limit with any sword, not just the Materia Blade.
+- You can fast forward through event text by holding X.
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/Abilities.py b/worlds/fftii/enemyrando/Abilities.py
new file mode 100644
index 000000000000..ca60ce0eab08
--- /dev/null
+++ b/worlds/fftii/enemyrando/Abilities.py
@@ -0,0 +1,83 @@
+from enum import Enum, unique
+
+
+@unique
+class ActionAbility(Enum):
+ NONE = 0x00
+ JOB = 0xFF
+ RANDOM = 0xFE
+
+ GUTS_C4 = 0x1B
+ WIEGRAF_1_HOLY_SWORD = 0x22
+ IZLUDE_BATTLE_SKILL = 0x33
+ IZLUDE_JUMP = 0x34
+ WIEGRAF_3_PUNCH_SKILL = 0x35
+ CELIA_USE_HAND = 0x36
+ LEDE_USE_HAND = 0x37
+ CELIA_THROW = 0x38
+ LEDE_THROW = 0x39
+ WIEGRAF_3_HOLY_SWORD = 0x3A
+ SWORD_SPIRIT_1 = 0x3B
+ ROFEL_ALL_MAGIC = 0x3D
+ SWORD_SPIRIT_2 = 0x3E
+ BLOOD_SUCK = 0x3F
+ SNIPE = 0x44
+ SWORD_SKILL = 0x46
+ DYCEDARG_ALL_MAGIC = 0x47
+ DESTROY_SWORD = 0x4B
+ HOLY_MAGIC = 0x4C
+ VELIUS_FEAR = 0x67
+ WARLOCK_SUMMON = 0x68
+ ZALERA_FEAR = 0x6B
+ JA_MAGIC = 0x6C
+ HASHMALUM_FEAR = 0x6F
+ DIMENSION_MAGIC = 0x70
+ QUEKLAIN_FEAR = 0x73
+ IMPURE = 0x74
+ ADRAMELK_FEAR = 0x77
+ ADRAMELK_ALL_MAGIC = 0x78
+ ULTIMATE_MAGIC = 0x7B
+ CHAOS = 0x7C
+ COMPLETE_MAGIC = 0x7D
+ SATURATION = 0x7E
+
+@unique
+class ReactionAbility(Enum):
+ NONE = 0x0000
+ RANDOM = 0x01FE
+ MA_SAVE = 0x01A7
+ SPEED_SAVE = 0x01A8
+ COUNTER_MAGIC = 0x01B3
+ COUNTER_FLOOD = 0x01B5
+ ABSORB_USED_MP = 0x01B6
+ AUTO_POTION = 0x01B9
+ COUNTER = 0x01BA
+ WEAPON_GUARD = 0x01BF
+ CATCH = 0x01C2
+ BLADE_GRASP = 0x01C3
+ ARROW_GUARD = 0x01C4
+ HAMEDO = 0x01C5
+
+@unique
+class SupportAbility(Enum):
+ NONE = 0x0000
+ RANDOM = 0x01FE
+ EQUIP_CROSSBOW = 0x01CA
+ HALF_OF_MP = 0x01CE
+ DEFENSE_UP = 0x01D2
+ MAGIC_DEFEND_UP = 0x01D4
+ MARTIAL_ARTS = 0x01D8
+ MAINTENANCE = 0x01DB
+ DEFEND = 0x01DF
+ TWO_HANDS = 0x01DC
+
+@unique
+class MovementAbility(Enum):
+ NONE = 0x0000
+ RANDOM = 0x01FE
+ MOVE_PLUS_1 = 0x01E6
+ JUMP_PLUS_1 = 0x01E9
+ IGNORE_HEIGHT = 0x01EC
+ MOVE_HP_UP = 0x01ED
+ TELEPORT_2 = 0x01F3
+ FLY = 0x01FB
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/BattleMappings.py b/worlds/fftii/enemyrando/BattleMappings.py
new file mode 100644
index 000000000000..182283d8083f
--- /dev/null
+++ b/worlds/fftii/enemyrando/BattleMappings.py
@@ -0,0 +1,3583 @@
+from worlds.fftii.enemyrando.EventCodes import EventCode
+from worlds.fftii.enemyrando.Job import Job
+from worlds.fftii.enemyrando.RandomizedUnitFactory import RandomizedUnitFactory
+from worlds.fftii.enemyrando.RandomizedUnits import RandomizedUnit
+from worlds.fftii.enemyrando.SourceUnit import SourceUnit
+from worlds.fftii.enemyrando.SpriteSet import SpriteSet
+from worlds.fftii.enemyrando.Unit import UnitGender
+
+
+class BattleMapping:
+ name: str
+ battle_id: EventCode
+ battle_level: int
+ source_units = []
+ unit_mapping: dict[SourceUnit, RandomizedUnitFactory]
+
+ def __init__(self, battle_id: int, source_units):
+ self.battle_id = EventCode(battle_id)
+ self.source_units = source_units
+
+ def __copy__(self):
+ raise NotImplementedError
+
+ def __deepcopy__(self, memo):
+ copied_object = BattleMapping(self.battle_id, self.source_units.copy())
+ copied_object.name = self.name
+ return copied_object
+
+
+DolbodarSwampEast1 = BattleMapping(1, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+DolbodarSwampEast2 = BattleMapping(2, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+DolbodarSwampEast3 = BattleMapping(3, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DolbodarSwampEast4 = BattleMapping(4, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DolbodarSwampWest1 = BattleMapping(5, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+DolbodarSwampWest2 = BattleMapping(6, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.URIBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DolbodarSwampWest3 = BattleMapping(7, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+DolbodarSwampWest4 = BattleMapping(8, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsSouth1 = BattleMapping(13, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsSouth2 = BattleMapping(14, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.THIEF, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsSouth3 = BattleMapping(15, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsSouth4 = BattleMapping(16, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsWest1 = BattleMapping(17, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+FovohamPlainsWest2 = BattleMapping(18, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsWest3 = BattleMapping(19, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 2
+#===
+FovohamPlainsWest4 = BattleMapping(20, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsEast1 = BattleMapping(21, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsEast2 = BattleMapping(22, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsEast3 = BattleMapping(23, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsEast4 = BattleMapping(24, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsEast1 = BattleMapping(25, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+SweegyWoodsEast2 = BattleMapping(26, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsEast3 = BattleMapping(27, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsEast4 = BattleMapping(28, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsWest1 = BattleMapping(29, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsWest2 = BattleMapping(30, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsWest3 = BattleMapping(31, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+SweegyWoodsWest4 = BattleMapping(32, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BerveniaVolcanoNorth1 = BattleMapping(37, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+BerveniaVolcanoNorth2 = BattleMapping(38, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BerveniaVolcanoNorth3 = BattleMapping(39, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BerveniaVolcanoNorth4 = BattleMapping(40, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BerveniaVolcanoSouth1 = BattleMapping(41, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+BerveniaVolcanoSouth2 = BattleMapping(42, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+BerveniaVolcanoSouth3 = BattleMapping(43, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BerveniaVolcanoSouth4 = BattleMapping(44, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertNorth1 = BattleMapping(49, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertNorth2 = BattleMapping(50, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+ZeklausDesertNorth3 = BattleMapping(51, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertNorth4 = BattleMapping(52, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+ZeklausDesertSouth1 = BattleMapping(53, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertSouth2 = BattleMapping(54, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertSouth3 = BattleMapping(55, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+ZeklausDesertSouth4 = BattleMapping(56, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertEast1 = BattleMapping(57, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertEast2 = BattleMapping(58, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+ZeklausDesertEast3 = BattleMapping(59, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZeklausDesertEast4 = BattleMapping(60, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+LenaliaPlateauSouth1 = BattleMapping(61, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+LenaliaPlateauSouth2 = BattleMapping(62, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+LenaliaPlateauSouth3 = BattleMapping(63, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+LenaliaPlateauSouth4 = BattleMapping(64, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+LenaliaPlateauNorth1 = BattleMapping(65, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+LenaliaPlateauNorth2 = BattleMapping(66, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.PRIEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+LenaliaPlateauNorth3 = BattleMapping(67, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+LenaliaPlateauNorth4 = BattleMapping(68, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.PRIEST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+ZigolisSwampEast1 = BattleMapping(73, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZigolisSwampEast2 = BattleMapping(74, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZigolisSwampEast3 = BattleMapping(75, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+ZigolisSwampEast4 = BattleMapping(76, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+ZigolisSwampWest1 = BattleMapping(77, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZigolisSwampWest2 = BattleMapping(78, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+ZigolisSwampWest3 = BattleMapping(79, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+ZigolisSwampWest4 = BattleMapping(80, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+ZirekileFallsEast5 = BattleMapping(82, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+BariusHillSouth5 = BattleMapping(83, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DARK_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+LenaliaPlateauSouth5 = BattleMapping(84, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CALCULATOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.CALCULATOR, UnitGender.FEMALE),
+])
+#Used sprite sheets: 2
+#===
+YuguoWoodsWest1 = BattleMapping(85, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+YuguoWoodsWest2 = BattleMapping(86, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+YuguoWoodsWest3 = BattleMapping(87, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+YuguoWoodsWest4 = BattleMapping(88, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+YuguoWoodsEast1 = BattleMapping(89, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+YuguoWoodsEast2 = BattleMapping(90, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+YuguoWoodsEast3 = BattleMapping(91, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+YuguoWoodsEast4 = BattleMapping(92, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+DolbodarSwampWest5 = BattleMapping(93, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PORKY, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GrogHillSouth5 = BattleMapping(94, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+])
+#Used sprite sheets: 1
+#===
+BerveniaVolcanoNorth5 = BattleMapping(95, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleySouth5 = BattleMapping(96, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TIAMAT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsWest1 = BattleMapping(97, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsWest2 = BattleMapping(98, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+AraguayWoodsWest3 = BattleMapping(99, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsWest4 = BattleMapping(100, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsEast1 = BattleMapping(101, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsEast2 = BattleMapping(102, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+AraguayWoodsEast3 = BattleMapping(103, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsEast4 = BattleMapping(104, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverEast5 = BattleMapping(105, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakNorth5 = BattleMapping(106, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.CHEMIST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MEDIATOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MEDIATOR, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+AraguayWoodsWest5 = BattleMapping(107, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+])
+#Used sprite sheets: 2
+#===
+YuguoWoodsEast5 = BattleMapping(108, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SAMURAI, UnitGender.MALE),
+])
+#Used sprite sheets: 1
+#===
+GrogHillWest1 = BattleMapping(109, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+GrogHillWest2 = BattleMapping(110, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+GrogHillWest3 = BattleMapping(111, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GrogHillWest4 = BattleMapping(112, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GrogHillSouth1 = BattleMapping(113, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GrogHillSouth2 = BattleMapping(114, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.GEOMANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.PRIEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+GrogHillSouth3 = BattleMapping(115, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GrogHillSouth4 = BattleMapping(116, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+])
+#Used sprite sheets: 6
+#===
+GrogHillEast1 = BattleMapping(117, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+GrogHillEast2 = BattleMapping(118, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SAMURAI, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 6
+#===
+GrogHillEast3 = BattleMapping(119, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+GrogHillEast4 = BattleMapping(120, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BedDesertSouth1 = BattleMapping(121, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+BedDesertSouth2 = BattleMapping(122, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+BedDesertSouth3 = BattleMapping(123, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BedDesertSouth4 = BattleMapping(124, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+BedDesertNorth1 = BattleMapping(125, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BedDesertNorth2 = BattleMapping(126, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+BedDesertNorth3 = BattleMapping(127, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BedDesertNorth4 = BattleMapping(128, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BedDesertNorth5 = BattleMapping(129, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.GEOMANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+FovohamPlainsWest5 = BattleMapping(130, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+DoguolaPassWest5 = BattleMapping(131, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+SweegyWoodsEast5 = BattleMapping(132, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 7
+#===
+ZirekileFallsWest1 = BattleMapping(133, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsWest2 = BattleMapping(134, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+ZirekileFallsWest3 = BattleMapping(135, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsWest4 = BattleMapping(136, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsEast1 = BattleMapping(137, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsEast2 = BattleMapping(138, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsEast3 = BattleMapping(139, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+ZirekileFallsEast4 = BattleMapping(140, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.GEOMANCER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsSouth1 = BattleMapping(141, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsSouth2 = BattleMapping(142, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsSouth3 = BattleMapping(143, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFallsSouth4 = BattleMapping(144, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SAMURAI, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BariusHillNorth1 = BattleMapping(145, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+BariusHillNorth2 = BattleMapping(146, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BariusHillNorth3 = BattleMapping(147, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusHillNorth4 = BattleMapping(148, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusHillSouth1 = BattleMapping(149, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 1
+#===
+BariusHillSouth2 = BattleMapping(150, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+BariusHillSouth3 = BattleMapping(151, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusHillSouth4 = BattleMapping(152, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.THIEF, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+PoeskasLakeNorth5 = BattleMapping(153, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.PRIEST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.CHEMIST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 7
+#===
+ZigolisSwampWest5 = BattleMapping(154, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SAMURAI, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SQUIRE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 7
+#===
+MandaliaPlainsSouth5 = BattleMapping(155, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 1
+#===
+ZeklausDesertSouth5 = BattleMapping(156, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 2
+#===
+MandaliaPlainsNorth1 = BattleMapping(157, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+MandaliaPlainsNorth2 = BattleMapping(158, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsNorth3 = BattleMapping(159, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsNorth4 = BattleMapping(160, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsSouth1 = BattleMapping(161, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+MandaliaPlainsSouth2 = BattleMapping(162, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsSouth3 = BattleMapping(163, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsSouth4 = BattleMapping(164, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsWest1 = BattleMapping(165, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+MandaliaPlainsWest2 = BattleMapping(166, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsWest3 = BattleMapping(167, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlainsWest4 = BattleMapping(168, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+DoguolaPassEast1 = BattleMapping(169, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPassEast2 = BattleMapping(170, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPassEast3 = BattleMapping(171, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+DoguolaPassEast4 = BattleMapping(172, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPassWest1 = BattleMapping(173, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPassWest2 = BattleMapping(174, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPassWest3 = BattleMapping(175, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPassWest4 = BattleMapping(176, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BariusValleyWest1 = BattleMapping(181, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleyWest2 = BattleMapping(182, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleyWest3 = BattleMapping(183, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BariusValleyWest4 = BattleMapping(184, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BariusValleyEast1 = BattleMapping(185, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleyEast2 = BattleMapping(186, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleyEast3 = BattleMapping(187, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleyEast4 = BattleMapping(188, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleySouth1 = BattleMapping(189, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+BariusValleySouth2 = BattleMapping(190, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+BariusValleySouth3 = BattleMapping(191, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+BariusValleySouth4 = BattleMapping(192, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverWest1 = BattleMapping(193, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverWest2 = BattleMapping(194, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverWest3 = BattleMapping(195, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverWest4 = BattleMapping(196, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverEast1 = BattleMapping(197, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverEast2 = BattleMapping(198, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+FinathRiverEast3 = BattleMapping(199, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+FinathRiverEast4 = BattleMapping(200, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GREAT_MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Horror1 = BattleMapping(201, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Horror2 = BattleMapping(202, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.LANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Horror3 = BattleMapping(203, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SAMURAI, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SAMURAI, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+Horror4 = BattleMapping(204, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 8
+#===
+PoeskasLakeNorth1 = BattleMapping(205, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+PoeskasLakeNorth2 = BattleMapping(206, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+PoeskasLakeNorth3 = BattleMapping(207, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+PoeskasLakeNorth4 = BattleMapping(208, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+PoeskasLakeSouth1 = BattleMapping(209, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+PoeskasLakeSouth2 = BattleMapping(210, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.CHEMIST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+PoeskasLakeSouth3 = BattleMapping(211, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+PoeskasLakeSouth4 = BattleMapping(212, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DARK_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Voyage1 = BattleMapping(213, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+])
+#Used sprite sheets: 2
+#===
+Voyage2 = BattleMapping(214, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Voyage3 = BattleMapping(215, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GREAT_MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DARK_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Voyage4 = BattleMapping(216, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.CALCULATOR, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CALCULATOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+])
+#Used sprite sheets: 6
+#===
+GerminasPeakNorth1 = BattleMapping(217, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakNorth2 = BattleMapping(218, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakNorth3 = BattleMapping(219, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakNorth4 = BattleMapping(220, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakSouth1 = BattleMapping(221, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakSouth2 = BattleMapping(222, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakSouth3 = BattleMapping(223, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+GerminasPeakSouth4 = BattleMapping(224, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.LANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Bridge1 = BattleMapping(225, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+])
+#Used sprite sheets: 2
+#===
+Bridge2 = BattleMapping(226, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Bridge3 = BattleMapping(227, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.THIEF, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Bridge4 = BattleMapping(228, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.PRIEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 9
+#===
+Tiger1 = BattleMapping(229, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.VAMPIRE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 2
+#===
+Tiger2 = BattleMapping(230, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DARK_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+Tiger3 = BattleMapping(231, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Tiger4 = BattleMapping(232, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SAMURAI, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DARK_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SAMURAI, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 10
+#===
+Mlapan1 = BattleMapping(233, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 1
+#===
+Mlapan2 = BattleMapping(234, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.PRIEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Mlapan3 = BattleMapping(235, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.WOODMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TRENT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 7
+#===
+Mlapan4 = BattleMapping(236, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MEDIATOR, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.PRIEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SQUIRE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 12
+#===
+Valkyries1 = BattleMapping(237, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GREAT_MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+])
+#Used sprite sheets: 1
+#===
+Valkyries2 = BattleMapping(238, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Valkyries3 = BattleMapping(239, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Valkyries4 = BattleMapping(240, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SACRED, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 10
+#===
+Delta1 = BattleMapping(241, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.JURAVIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_HAWK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Delta2 = BattleMapping(242, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GRENADE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINDFLARE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Delta3 = BattleMapping(243, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MINITAURUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Delta4 = BattleMapping(244, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.LANCER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.GEOMANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ORACLE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MEDIATOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SQUIDLARKIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TAIJU, UnitGender.MONSTER),
+])
+#Used sprite sheets: 12
+#===
+Terminate1 = BattleMapping(245, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 1
+#===
+Terminate2 = BattleMapping(246, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.AHRIMAN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Terminate3 = BattleMapping(247, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 6
+#===
+Terminate4 = BattleMapping(248, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Nogias1 = BattleMapping(249, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+])
+#Used sprite sheets: 1
+#===
+Nogias2 = BattleMapping(250, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.CUAR, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+Nogias3 = BattleMapping(251, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+Nogias4 = BattleMapping(252, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.THIEF, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBBLEDEGUCK, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BULL_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PISCO_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.EXPLOSIVE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 12
+#===
+SweegyWoods = BattleMapping(384, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.ALGUS), Job.SQUIRE_ALGUS, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BOMB, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+DorterTradeCity1 = BattleMapping(385, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.ALGUS), Job.SQUIRE_ALGUS, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.WIEGRAF1), Job.WHITE_KNIGHT_C1, UnitGender.MALE),
+])
+#Used sprite sheets: 6
+#===
+SandRatCellar = BattleMapping(386, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.ALGUS), Job.SQUIRE_ALGUS, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+GarilandFight = BattleMapping(388, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SQUIRE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+MandaliaPlains = BattleMapping(389, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_PANTHER, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.ALGUS), Job.SQUIRE_ALGUS, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+Miluda1 = BattleMapping(395, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.ALGUS), Job.SQUIRE_ALGUS, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.PRIEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+Miluda2 = BattleMapping(399, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Wiegraf1 = BattleMapping(400, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.WIEGRAF1), Job.WHITE_KNIGHT_C1, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+FortZeakden = BattleMapping(401, [
+ SourceUnit(SpriteSet(SpriteSet.ZALBAG), Job.ARC_KNIGHT_ZALBAG, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.TETA), Job.TETA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.ALGUS), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C1), Job.SQUIRE_DELITA, UnitGender.MALE),
+])
+#Used sprite sheets: 6
+#===
+DDENDversusElidibs = BattleMapping(402, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BYBLOS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.APANDA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ELIDIBS, UnitGender.MONSTER),
+])
+#Used sprite sheets: 3
+#===
+Dorter2 = BattleMapping(403, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GAFGARION), Job.DARK_KNIGHT_GUEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.VORMAV), Job.DIVINE_KNIGHT_VORMAV, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.AGRIAS_GUEST), Job.HOLY_KNIGHT_AGRIAS_GUEST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+])
+#Used sprite sheets: 6
+#===
+AraguayWoods = BattleMapping(404, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GAFGARION), Job.DARK_KNIGHT_GUEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.AGRIAS_GUEST), Job.HOLY_KNIGHT_AGRIAS_GUEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+ZirekileFalls = BattleMapping(405, [
+ SourceUnit(SpriteSet(SpriteSet.GAFGARION), Job.DARK_KNIGHT_GUEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.OVELIA), Job.PRINCESS, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C2), Job.HOLY_KNIGHT_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.AGRIAS_GUEST), Job.HOLY_KNIGHT_AGRIAS_GUEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+ZalandFortCity = BattleMapping(407, [
+ SourceUnit(SpriteSet(SpriteSet.MUSTADIO_GUEST), Job.ENGINEER_GUEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.AGRIAS_GUEST), Job.HOLY_KNIGHT_AGRIAS_GUEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+BariausHill = BattleMapping(409, [
+ SourceUnit(SpriteSet(SpriteSet.MUSTADIO_GUEST), Job.ENGINEER_GUEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.AGRIAS_GUEST), Job.HOLY_KNIGHT_AGRIAS_GUEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+ZigolisSwamp = BattleMapping(410, [
+ SourceUnit(SpriteSet(SpriteSet.MUSTADIO_GUEST), Job.ENGINEER_GUEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.FLOTIBALL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.MORBOL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.URIBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 6
+#===
+GougMachineCity = BattleMapping(411, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.RUDVICH), Job.RUDVICH, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MUSTADIO), Job.ENGINEER_MUSTADIO, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+BariausValley = BattleMapping(413, [
+ SourceUnit(SpriteSet(SpriteSet.AGRIAS), Job.HOLY_KNIGHT_AGRIAS, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+GolgorandExecutionSite = BattleMapping(414, [
+ SourceUnit(SpriteSet(SpriteSet.GAFGARION_ENEMY), Job.DARK_KNIGHT_ENEMY, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.TIMEMAGE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+LionelCastleGate = BattleMapping(415, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GAFGARION_ENEMY), Job.DARK_KNIGHT_ENEMY, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+InsideofLionelCastle = BattleMapping(416, [
+ SourceUnit(SpriteSet(SpriteSet.DRACLAU), Job.DRACLAU, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.QUEKLAIN), Job.QUEKLAIN, UnitGender.MALE),
+])
+#Used sprite sheets: 2
+#===
+GolandCoalCity = BattleMapping(417, [
+ SourceUnit(SpriteSet(SpriteSet.OLAN), Job.ASTROLOGIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.THIEF, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MEDIATOR, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Zarghidas = BattleMapping(419, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.CHEMIST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.CLOUD), Job.SOLDIER, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+OutsideLesaliaGateZalmo1 = BattleMapping(420, [
+ SourceUnit(SpriteSet(SpriteSet.ALMA), Job.CLERIC, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.ZALMO), Job.HOLY_PRIEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.MONK, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+UndergroundBookStorageSecondFloor = BattleMapping(422, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.IZLUDE), Job.KNIGHT_BLADE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.LANCER, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+UndergroundBookStorageThirdFloor = BattleMapping(423, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.IZLUDE), Job.KNIGHT_BLADE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+UndergroundBookStorageFirstFloor = BattleMapping(424, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.WIZARD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.WIEGRAF2), Job.WHITE_KNIGHT_C3, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+GrogHill = BattleMapping(426, [
+ SourceUnit(SpriteSet(SpriteSet.OLAN), Job.ASTROLOGIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SQUIRE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.THIEF, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 7
+#===
+RescueRafa = BattleMapping(428, [
+ SourceUnit(SpriteSet(SpriteSet.RAFA_GUEST), Job.HEAVEN_KNIGHT_GUEST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MALAK), Job.HELL_KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+YuguoWoods = BattleMapping(430, [
+ SourceUnit(SpriteSet(SpriteSet.UNDEAD_WIZARD), Job.WIZARD_UNDEAD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GHOUL, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GUST, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.UNDEAD_TIMEMAGE), Job.TIME_MAGE_UNDEAD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.RAFA_GUEST), Job.HEAVEN_KNIGHT_GUEST, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+RiovanesCastleEntrance = BattleMapping(431, [
+ SourceUnit(SpriteSet(SpriteSet.RAFA_GUEST), Job.HEAVEN_KNIGHT_GUEST, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MALAK), Job.HELL_KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+InsideofRiovanesCastle = BattleMapping(432, [
+ SourceUnit(SpriteSet(SpriteSet.WIEGRAF2), Job.WHITE_KNIGHT_C3, UnitGender.NONE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ARCHAIC_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.WIEGRAF2), Job.WHITE_KNIGHT_C3, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.VELIUS), Job.VELIUS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.FEMALE),
+])
+#Used sprite sheets: 6
+#===
+RooftopofRiovanesCastle = BattleMapping(433, [
+ SourceUnit(SpriteSet(SpriteSet.RAFA), Job.HEAVEN_KNIGHT, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.ELMDOR), Job.ARC_KNIGHT_ELMDOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.CELIA), Job.ASSASSIN_CELIA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MALAK_DEAD), Job.MALAK_DEAD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.LEDE), Job.ASSASSIN_LEDE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+UndergroundBookStorageFourthFloor = BattleMapping(435, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.ROFEL), Job.DIVINE_KNIGHT_ROFEL, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+UndergroundBookStorageFifthFloor = BattleMapping(436, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.ROFEL), Job.DIVINE_KNIGHT_ROFEL, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+MurondDeathCity = BattleMapping(438, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SAMURAI, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.KLETIAN), Job.SORCERER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.TIMEMAGE, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+LostSacredPrecincts = BattleMapping(439, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.TIAMAT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.DARK_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.BALK), Job.ENGINEER_BALK, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+GraveyardofAirships1 = BattleMapping(440, [
+ SourceUnit(SpriteSet(SpriteSet.ALMA_DEAD), Job.ALMA_DEAD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.VORMAV), Job.DIVINE_KNIGHT_VORMAV, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.HASHMALUM), Job.HASHMALUM, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.AJORA), Job.ALMA_DEAD, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+GraveyardofAirships2 = BattleMapping(441, [
+ SourceUnit(SpriteSet(SpriteSet.ALTIMA_2), Job.ALTIMA_2, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.ALTIMA_1), Job.ALTIMA_1, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.AJORA), Job.AJORA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ULTIMA_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.ALMA_GOA), Job.ALMA_INITIAL_DEAD, UnitGender.FEMALE),
+])
+#Used sprite sheets: 5
+#===
+DoguolaPass = BattleMapping(442, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.LANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+BerveniaFreeCity = BattleMapping(443, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.SUMMONER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.ARCHER, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MELIADOUL_ENEMY), Job.DIVINE_KNIGHT_MELIADOUL_ENEMY, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_FEMALE), Job.NINJA, UnitGender.FEMALE),
+])
+#Used sprite sheets: 4
+#===
+FinathRiver = BattleMapping(444, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.YELLOW_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLACK_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.RED_CHOCOBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.URIBO, UnitGender.MONSTER),
+])
+#Used sprite sheets: 2
+#===
+ZalmoII = BattleMapping(445, [
+ SourceUnit(SpriteSet(SpriteSet.DELITA_C2), Job.HOLY_KNIGHT_DELITA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.ZALMO), Job.HOLY_PRIEST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ORACLE, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+BalkI = BattleMapping(447, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.BALK), Job.ENGINEER_BALK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+SouthWallofBethlaGarrison = BattleMapping(448, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+NorthWallofBethlaGarrison = BattleMapping(449, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MONK, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.LANCER, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+BethlaSluice = BattleMapping(450, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.WIZARD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 3
+#===
+GerminasPeak = BattleMapping(452, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.NINJA, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.ARCHER, UnitGender.MALE),
+])
+#Used sprite sheets: 3
+#===
+PoeskasLake = BattleMapping(453, [
+ SourceUnit(SpriteSet(SpriteSet.UNDEAD_SUMMONER), Job.SUMMONER_UNDEAD, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.REVNANT, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.UNDEAD_ORACLE), Job.ORACLE_UNDEAD, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.UNDEAD_ARCHER), Job.ARCHER_UNDEAD, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+OutsideofLimberryCastle = BattleMapping(454, [
+ SourceUnit(SpriteSet(SpriteSet.CELIA), Job.ASSASSIN_CELIA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.APANDA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.LEDE), Job.ASSASSIN_LEDE, UnitGender.FEMALE),
+])
+#Used sprite sheets: 3
+#===
+ElmdorII = BattleMapping(456, [
+ SourceUnit(SpriteSet(SpriteSet.CELIA), Job.ASSASSIN_CELIA, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ULTIMA_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.LEDE), Job.ASSASSIN_LEDE, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.ELMDOR), Job.ARC_KNIGHT_ELMDOR, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+Zalera = BattleMapping(457, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BONE_SNATCH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.LIVING_BONE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.ELMDOR), Job.ARC_KNIGHT_ELMDOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.UNDEAD_KNIGHT), Job.KNIGHT_UNDEAD, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.SKELETON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MELIADOUL), Job.DIVINE_KNIGHT_MELIADOUL, UnitGender.FEMALE),
+ SourceUnit(SpriteSet(SpriteSet.ZALERA), Job.ZALERA, UnitGender.MALE),
+])
+#Used sprite sheets: 5
+#===
+Adramelk = BattleMapping(459, [
+ SourceUnit(SpriteSet(SpriteSet.ZALBAG), Job.ARC_KNIGHT_ZALBAG, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.DYCEDARG), Job.LUNE_KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.ADRAMELK), Job.ADRAMELK, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+StMurondTemple = BattleMapping(460, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.SUMMONER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.GEOMANCER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.MEDIATOR, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.PRIEST, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+HallofStMurondTemple = BattleMapping(461, [
+ SourceUnit(SpriteSet(SpriteSet.ROFEL), Job.DIVINE_KNIGHT_ROFEL, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.KLETIAN), Job.SORCERER, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.VORMAV), Job.DIVINE_KNIGHT_VORMAV, UnitGender.MALE),
+])
+#Used sprite sheets: 3
+#===
+ChapelofStMurondTemple = BattleMapping(462, [
+ SourceUnit(SpriteSet(SpriteSet.ZALBAG_ZOMBIE), Job.ARC_KNIGHT_ZOMBIE, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.VORMAV), Job.DIVINE_KNIGHT_VORMAV, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ARCHAIC_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ULTIMA_DEMON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+CollieryUndergroundThirdFloor = BattleMapping(463, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.BEOWULF), Job.TEMPLE_KNIGHT, UnitGender.MALE),
+])
+#Used sprite sheets: 2
+#===
+CollieryUndergroundSecondFloor = BattleMapping(464, [
+ SourceUnit(SpriteSet(SpriteSet.BEOWULF), Job.TEMPLE_KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.KING_BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BEHEMOTH, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.THIEF, UnitGender.MALE),
+])
+#Used sprite sheets: 4
+#===
+CollieryUndergroundFirstFloor = BattleMapping(465, [
+ SourceUnit(SpriteSet(SpriteSet.GENERIC_MALE), Job.CHEMIST, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.URIBO, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.BEOWULF), Job.TEMPLE_KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.BLUE_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+#===
+UndergroundPassageinGoland = BattleMapping(466, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.OCHU, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.ARCHAIC_DEMON, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.BEOWULF), Job.TEMPLE_KNIGHT, UnitGender.MALE),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.PLAGUE, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.HOLY_DRAGON), Job.HOLY_DRAGON, UnitGender.MONSTER),
+])
+#Used sprite sheets: 5
+#===
+NelveskaTemple = BattleMapping(468, [
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.GOBLIN, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.HYUDRA, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.COCATORIS, UnitGender.MONSTER),
+ SourceUnit(SpriteSet(SpriteSet.MONSTER), Job.STEEL_GIANT, UnitGender.MONSTER),
+])
+#Used sprite sheets: 4
+
+DolbodarSwampEast1.name = 'Dolbodar Swamp East 1'
+DolbodarSwampEast2.name = 'Dolbodar Swamp East 2'
+DolbodarSwampEast3.name = 'Dolbodar Swamp East 3'
+DolbodarSwampEast4.name = 'Dolbodar Swamp East 4'
+DolbodarSwampWest1.name = 'Dolbodar Swamp West 1'
+DolbodarSwampWest2.name = 'Dolbodar Swamp West 2'
+DolbodarSwampWest3.name = 'Dolbodar Swamp West 3'
+DolbodarSwampWest4.name = 'Dolbodar Swamp West 4'
+FovohamPlainsSouth1.name = 'Fovoham Plains South 1'
+FovohamPlainsSouth2.name = 'Fovoham Plains South 2'
+FovohamPlainsSouth3.name = 'Fovoham Plains South 3'
+FovohamPlainsSouth4.name = 'Fovoham Plains South 4'
+FovohamPlainsWest1.name = 'Fovoham Plains West 1'
+FovohamPlainsWest2.name = 'Fovoham Plains West 2'
+FovohamPlainsWest3.name = 'Fovoham Plains West 3'
+FovohamPlainsWest4.name = 'Fovoham Plains West 4'
+FovohamPlainsEast1.name = 'Fovoham Plains East 1'
+FovohamPlainsEast2.name = 'Fovoham Plains East 2'
+FovohamPlainsEast3.name = 'Fovoham Plains East 3'
+FovohamPlainsEast4.name = 'Fovoham Plains East 4'
+SweegyWoodsEast1.name = 'Sweegy Woods East 1'
+SweegyWoodsEast2.name = 'Sweegy Woods East 2'
+SweegyWoodsEast3.name = 'Sweegy Woods East 3'
+SweegyWoodsEast4.name = 'Sweegy Woods East 4'
+SweegyWoodsWest1.name = 'Sweegy Woods West 1'
+SweegyWoodsWest2.name = 'Sweegy Woods West 2'
+SweegyWoodsWest3.name = 'Sweegy Woods West 3'
+SweegyWoodsWest4.name = 'Sweegy Woods West 4'
+BerveniaVolcanoNorth1.name = 'Bervenia Volcano North 1'
+BerveniaVolcanoNorth2.name = 'Bervenia Volcano North 2'
+BerveniaVolcanoNorth3.name = 'Bervenia Volcano North 3'
+BerveniaVolcanoNorth4.name = 'Bervenia Volcano North 4'
+BerveniaVolcanoSouth1.name = 'Bervenia Volcano South 1'
+BerveniaVolcanoSouth2.name = 'Bervenia Volcano South 2'
+BerveniaVolcanoSouth3.name = 'Bervenia Volcano South 3'
+BerveniaVolcanoSouth4.name = 'Bervenia Volcano South 4'
+ZeklausDesertNorth1.name = 'Zeklaus Desert North 1'
+ZeklausDesertNorth2.name = 'Zeklaus Desert North 2'
+ZeklausDesertNorth3.name = 'Zeklaus Desert North 3'
+ZeklausDesertNorth4.name = 'Zeklaus Desert North 4'
+ZeklausDesertSouth1.name = 'Zeklaus Desert South 1'
+ZeklausDesertSouth2.name = 'Zeklaus Desert South 2'
+ZeklausDesertSouth3.name = 'Zeklaus Desert South 3'
+ZeklausDesertSouth4.name = 'Zeklaus Desert South 4'
+ZeklausDesertEast1.name = 'Zeklaus Desert East 1'
+ZeklausDesertEast2.name = 'Zeklaus Desert East 2'
+ZeklausDesertEast3.name = 'Zeklaus Desert East 3'
+ZeklausDesertEast4.name = 'Zeklaus Desert East 4'
+LenaliaPlateauSouth1.name = 'Lenalia Plateau South 1'
+LenaliaPlateauSouth2.name = 'Lenalia Plateau South 2'
+LenaliaPlateauSouth3.name = 'Lenalia Plateau South 3'
+LenaliaPlateauSouth4.name = 'Lenalia Plateau South 4'
+LenaliaPlateauNorth1.name = 'Lenalia Plateau North 1'
+LenaliaPlateauNorth2.name = 'Lenalia Plateau North 2'
+LenaliaPlateauNorth3.name = 'Lenalia Plateau North 3'
+LenaliaPlateauNorth4.name = 'Lenalia Plateau North 4'
+ZigolisSwampEast1.name = 'Zigolis Swamp East 1'
+ZigolisSwampEast2.name = 'Zigolis Swamp East 2'
+ZigolisSwampEast3.name = 'Zigolis Swamp East 3'
+ZigolisSwampEast4.name = 'Zigolis Swamp East 4'
+ZigolisSwampWest1.name = 'Zigolis Swamp West 1'
+ZigolisSwampWest2.name = 'Zigolis Swamp West 2'
+ZigolisSwampWest3.name = 'Zigolis Swamp West 3'
+ZigolisSwampWest4.name = 'Zigolis Swamp West 4'
+ZirekileFallsEast5.name = 'Zirekile Falls East 5'
+BariusHillSouth5.name = 'Barius Hill South 5'
+LenaliaPlateauSouth5.name = 'Lenalia Plateau South 5'
+YuguoWoodsWest1.name = 'Yuguo Woods West 1'
+YuguoWoodsWest2.name = 'Yuguo Woods West 2'
+YuguoWoodsWest3.name = 'Yuguo Woods West 3'
+YuguoWoodsWest4.name = 'Yuguo Woods West 4'
+YuguoWoodsEast1.name = 'Yuguo Woods East 1'
+YuguoWoodsEast2.name = 'Yuguo Woods East 2'
+YuguoWoodsEast3.name = 'Yuguo Woods East 3'
+YuguoWoodsEast4.name = 'Yuguo Woods East 4'
+DolbodarSwampWest5.name = 'Dolbodar Swamp West 5'
+GrogHillSouth5.name = 'Grog Hill South 5'
+BerveniaVolcanoNorth5.name = 'Bervenia Volcano North 5'
+BariusValleySouth5.name = 'Barius Valley South 5'
+AraguayWoodsWest1.name = 'Araguay Woods West 1'
+AraguayWoodsWest2.name = 'Araguay Woods West 2'
+AraguayWoodsWest3.name = 'Araguay Woods West 3'
+AraguayWoodsWest4.name = 'Araguay Woods West 4'
+AraguayWoodsEast1.name = 'Araguay Woods East 1'
+AraguayWoodsEast2.name = 'Araguay Woods East 2'
+AraguayWoodsEast3.name = 'Araguay Woods East 3'
+AraguayWoodsEast4.name = 'Araguay Woods East 4'
+FinathRiverEast5.name = 'Finath River East 5'
+GerminasPeakNorth5.name = 'Germinas Peak North 5'
+AraguayWoodsWest5.name = 'Araguay Woods West 5'
+YuguoWoodsEast5.name = 'Yuguo Woods East 5'
+GrogHillWest1.name = 'Grog Hill West 1'
+GrogHillWest2.name = 'Grog Hill West 2'
+GrogHillWest3.name = 'Grog Hill West 3'
+GrogHillWest4.name = 'Grog Hill West 4'
+GrogHillSouth1.name = 'Grog Hill South 1'
+GrogHillSouth2.name = 'Grog Hill South 2'
+GrogHillSouth3.name = 'Grog Hill South 3'
+GrogHillSouth4.name = 'Grog Hill South 4'
+GrogHillEast1.name = 'Grog Hill East 1'
+GrogHillEast2.name = 'Grog Hill East 2'
+GrogHillEast3.name = 'Grog Hill East 3'
+GrogHillEast4.name = 'Grog Hill East 4'
+BedDesertSouth1.name = 'Bed Desert South 1'
+BedDesertSouth2.name = 'Bed Desert South 2'
+BedDesertSouth3.name = 'Bed Desert South 3'
+BedDesertSouth4.name = 'Bed Desert South 4'
+BedDesertNorth1.name = 'Bed Desert North 1'
+BedDesertNorth2.name = 'Bed Desert North 2'
+BedDesertNorth3.name = 'Bed Desert North 3'
+BedDesertNorth4.name = 'Bed Desert North 4'
+BedDesertNorth5.name = 'Bed Desert North 5'
+FovohamPlainsWest5.name = 'Fovoham Plains West 5'
+DoguolaPassWest5.name = 'Doguola Pass West 5'
+SweegyWoodsEast5.name = 'Sweegy Woods East 5'
+ZirekileFallsWest1.name = 'Zirekile Falls West 1'
+ZirekileFallsWest2.name = 'Zirekile Falls West 2'
+ZirekileFallsWest3.name = 'Zirekile Falls West 3'
+ZirekileFallsWest4.name = 'Zirekile Falls West 4'
+ZirekileFallsEast1.name = 'Zirekile Falls East 1'
+ZirekileFallsEast2.name = 'Zirekile Falls East 2'
+ZirekileFallsEast3.name = 'Zirekile Falls East 3'
+ZirekileFallsEast4.name = 'Zirekile Falls East 4'
+ZirekileFallsSouth1.name = 'Zirekile Falls South 1'
+ZirekileFallsSouth2.name = 'Zirekile Falls South 2'
+ZirekileFallsSouth3.name = 'Zirekile Falls South 3'
+ZirekileFallsSouth4.name = 'Zirekile Falls South 4'
+BariusHillNorth1.name = 'Barius Hill North 1'
+BariusHillNorth2.name = 'Barius Hill North 2'
+BariusHillNorth3.name = 'Barius Hill North 3'
+BariusHillNorth4.name = 'Barius Hill North 4'
+BariusHillSouth1.name = 'Barius Hill South 1'
+BariusHillSouth2.name = 'Barius Hill South 2'
+BariusHillSouth3.name = 'Barius Hill South 3'
+BariusHillSouth4.name = 'Barius Hill South 4'
+PoeskasLakeNorth5.name = 'Poeskas Lake North 5'
+ZigolisSwampWest5.name = 'Zigolis Swamp West 5'
+MandaliaPlainsSouth5.name = 'Mandalia Plains South 5'
+ZeklausDesertSouth5.name = 'Zeklaus Desert South 5'
+MandaliaPlainsNorth1.name = 'Mandalia Plains North 1'
+MandaliaPlainsNorth2.name = 'Mandalia Plains North 2'
+MandaliaPlainsNorth3.name = 'Mandalia Plains North 3'
+MandaliaPlainsNorth4.name = 'Mandalia Plains North 4'
+MandaliaPlainsSouth1.name = 'Mandalia Plains South 1'
+MandaliaPlainsSouth2.name = 'Mandalia Plains South 2'
+MandaliaPlainsSouth3.name = 'Mandalia Plains South 3'
+MandaliaPlainsSouth4.name = 'Mandalia Plains South 4'
+MandaliaPlainsWest1.name = 'Mandalia Plains West 1'
+MandaliaPlainsWest2.name = 'Mandalia Plains West 2'
+MandaliaPlainsWest3.name = 'Mandalia Plains West 3'
+MandaliaPlainsWest4.name = 'Mandalia Plains West 4'
+DoguolaPassEast1.name = 'Doguola Pass East 1'
+DoguolaPassEast2.name = 'Doguola Pass East 2'
+DoguolaPassEast3.name = 'Doguola Pass East 3'
+DoguolaPassEast4.name = 'Doguola Pass East 4'
+DoguolaPassWest1.name = 'Doguola Pass West 1'
+DoguolaPassWest2.name = 'Doguola Pass West 2'
+DoguolaPassWest3.name = 'Doguola Pass West 3'
+DoguolaPassWest4.name = 'Doguola Pass West 4'
+BariusValleyWest1.name = 'Barius Valley West 1'
+BariusValleyWest2.name = 'Barius Valley West 2'
+BariusValleyWest3.name = 'Barius Valley West 3'
+BariusValleyWest4.name = 'Barius Valley West 4'
+BariusValleyEast1.name = 'Barius Valley East 1'
+BariusValleyEast2.name = 'Barius Valley East 2'
+BariusValleyEast3.name = 'Barius Valley East 3'
+BariusValleyEast4.name = 'Barius Valley East 4'
+BariusValleySouth1.name = 'Barius Valley South 1'
+BariusValleySouth2.name = 'Barius Valley South 2'
+BariusValleySouth3.name = 'Barius Valley South 3'
+BariusValleySouth4.name = 'Barius Valley South 4'
+FinathRiverWest1.name = 'Finath River West 1'
+FinathRiverWest2.name = 'Finath River West 2'
+FinathRiverWest3.name = 'Finath River West 3'
+FinathRiverWest4.name = 'Finath River West 4'
+FinathRiverEast1.name = 'Finath River East 1'
+FinathRiverEast2.name = 'Finath River East 2'
+FinathRiverEast3.name = 'Finath River East 3'
+FinathRiverEast4.name = 'Finath River East 4'
+Horror1.name = 'Horror 1'
+Horror2.name = 'Horror 2'
+Horror3.name = 'Horror 3'
+Horror4.name = 'Horror 4'
+PoeskasLakeNorth1.name = 'Poeskas Lake North 1'
+PoeskasLakeNorth2.name = 'Poeskas Lake North 2'
+PoeskasLakeNorth3.name = 'Poeskas Lake North 3'
+PoeskasLakeNorth4.name = 'Poeskas Lake North 4'
+PoeskasLakeSouth1.name = 'Poeskas Lake South 1'
+PoeskasLakeSouth2.name = 'Poeskas Lake South 2'
+PoeskasLakeSouth3.name = 'Poeskas Lake South 3'
+PoeskasLakeSouth4.name = 'Poeskas Lake South 4'
+Voyage1.name = 'Voyage 1'
+Voyage2.name = 'Voyage 2'
+Voyage3.name = 'Voyage 3'
+Voyage4.name = 'Voyage 4'
+GerminasPeakNorth1.name = 'Germina Peak North 1'
+GerminasPeakNorth2.name = 'Germina Peak North 2'
+GerminasPeakNorth3.name = 'Germina Peak North 3'
+GerminasPeakNorth4.name = 'Germina Peak North 4'
+GerminasPeakSouth1.name = 'Germina Peak South 1'
+GerminasPeakSouth2.name = 'Germina Peak South 2'
+GerminasPeakSouth3.name = 'Germina Peak South 3'
+GerminasPeakSouth4.name = 'Germina Peak South 4'
+Bridge1.name = 'Bridge 1'
+Bridge2.name = 'Bridge 2'
+Bridge3.name = 'Bridge 3'
+Bridge4.name = 'Bridge 4'
+Tiger1.name = 'Tiger 1'
+Tiger2.name = 'Tiger 2'
+Tiger3.name = 'Tiger 3'
+Tiger4.name = 'Tiger 4'
+Mlapan1.name = 'Mlapan 1'
+Mlapan2.name = 'Mlapan 2'
+Mlapan3.name = 'Mlapan 3'
+Mlapan4.name = 'Mlapan 4'
+Valkyries1.name = 'Valkyries 1'
+Valkyries2.name = 'Valkyries 2'
+Valkyries3.name = 'Valkyries 3'
+Valkyries4.name = 'Valkyries 4'
+Delta1.name = 'Delta 1'
+Delta2.name = 'Delta 2'
+Delta3.name = 'Delta 3'
+Delta4.name = 'Delta 4'
+Terminate1.name = 'Terminate 1'
+Terminate2.name = 'Terminate 2'
+Terminate3.name = 'Terminate 3'
+Terminate4.name = 'Terminate 4'
+Nogias1.name = 'Nogias 1'
+Nogias2.name = 'Nogias 2'
+Nogias3.name = 'Nogias 3'
+Nogias4.name = 'Nogias 4'
+SweegyWoods.name = 'Sweegy Woods'
+DorterTradeCity1.name = 'Dorter Trade City1'
+SandRatCellar.name = 'Sand Rat Cellar'
+GarilandFight.name = 'Gariland Fight'
+MandaliaPlains.name = 'Mandalia Plains'
+Miluda1.name = 'Miluda1'
+Miluda2.name = 'Miluda2'
+Wiegraf1.name = 'Wiegraf1'
+FortZeakden.name = 'Fort Zeakden'
+DDENDversusElidibs.name = 'DD END versus Elidibs'
+Dorter2.name = 'Dorter2'
+AraguayWoods.name = 'Araguay Woods'
+ZirekileFalls.name = 'Zirekile Falls'
+ZalandFortCity.name = 'Zaland Fort City'
+BariausHill.name = 'Bariaus Hill'
+ZigolisSwamp.name = 'Zigolis Swamp'
+GougMachineCity.name = 'Goug Machine City'
+BariausValley.name = 'Bariaus Valley'
+GolgorandExecutionSite.name = 'Golgorand Execution Site'
+LionelCastleGate.name = 'Lionel Castle Gate '
+InsideofLionelCastle.name = 'Inside of Lionel Castle'
+GolandCoalCity.name = 'Goland Coal City'
+Zarghidas.name = 'Zarghidas'
+OutsideLesaliaGateZalmo1.name = 'Outside Lesalia Gate Zalmo 1'
+UndergroundBookStorageSecondFloor.name = 'Underground Book Storage Second Floor'
+UndergroundBookStorageThirdFloor.name = 'Underground Book Storage Third Floor'
+UndergroundBookStorageFirstFloor.name = 'Underground Book Storage First Floor'
+GrogHill.name = 'Grog Hill '
+RescueRafa.name = 'Rescue Rafa'
+YuguoWoods.name = 'Yuguo Woods'
+RiovanesCastleEntrance.name = 'Riovanes Castle Entrance'
+InsideofRiovanesCastle.name = 'Inside of Riovanes Castle'
+RooftopofRiovanesCastle.name = 'Rooftop of Riovanes Castle'
+UndergroundBookStorageFourthFloor.name = 'Underground Book Storage Fourth Floor'
+UndergroundBookStorageFifthFloor.name = 'Underground Book Storage Fifth Floor'
+MurondDeathCity.name = 'Murond Death City'
+LostSacredPrecincts.name = 'Lost Sacred Precincts'
+GraveyardofAirships1.name = 'Graveyard of Airships 1'
+GraveyardofAirships2.name = 'Graveyard of Airships 2'
+DoguolaPass.name = 'Doguola Pass'
+BerveniaFreeCity.name = 'Bervenia Free City'
+FinathRiver.name = 'Finath River'
+ZalmoII.name = 'Zalmo II'
+BalkI.name = 'Balk I'
+SouthWallofBethlaGarrison.name = 'South Wall of Bethla Garrison'
+NorthWallofBethlaGarrison.name = 'North Wall of Bethla Garrison'
+BethlaSluice.name = 'Bethla Sluice'
+GerminasPeak.name = 'Germinas Peak'
+PoeskasLake.name = 'Poeskas Lake'
+OutsideofLimberryCastle.name = 'Outside of Limberry Castle'
+ElmdorII.name = 'Elmdor II'
+Zalera.name = 'Zalera'
+Adramelk.name = 'Adramelk'
+StMurondTemple.name = 'St Murond Temple'
+HallofStMurondTemple.name = 'Hall of St Murond Temple'
+ChapelofStMurondTemple.name = 'Chapel of St Murond Temple'
+CollieryUndergroundThirdFloor.name = 'Colliery Underground Third Floor'
+CollieryUndergroundSecondFloor.name = 'Colliery Underground Second Floor'
+CollieryUndergroundFirstFloor.name = 'Colliery Underground First Floor'
+UndergroundPassageinGoland.name = 'Underground Passage in Goland'
+NelveskaTemple.name = 'Nelveska Temple'
+
+gallione_only_randoms: list[BattleMapping] = [
+ MandaliaPlainsNorth1,
+ MandaliaPlainsNorth2,
+ MandaliaPlainsNorth3,
+ MandaliaPlainsNorth4,
+ MandaliaPlainsSouth1,
+ MandaliaPlainsSouth2,
+ MandaliaPlainsSouth3,
+ MandaliaPlainsSouth4,
+ MandaliaPlainsSouth5,
+ MandaliaPlainsWest1,
+ MandaliaPlainsWest2,
+ MandaliaPlainsWest3,
+ MandaliaPlainsWest4,
+ SweegyWoodsEast1,
+ SweegyWoodsEast2,
+ SweegyWoodsEast3,
+ SweegyWoodsEast4,
+ SweegyWoodsEast5,
+ SweegyWoodsWest1,
+ SweegyWoodsWest2,
+ SweegyWoodsWest3,
+ SweegyWoodsWest4,
+ LenaliaPlateauSouth1,
+ LenaliaPlateauSouth2,
+ LenaliaPlateauSouth3,
+ LenaliaPlateauSouth4,
+ LenaliaPlateauSouth5
+]
+
+gallione_randoms_from_fovoham: list[BattleMapping] = [
+ LenaliaPlateauNorth1,
+ LenaliaPlateauNorth2,
+ LenaliaPlateauNorth3,
+ LenaliaPlateauNorth4
+]
+
+gallione_randoms: list[BattleMapping] = [*gallione_only_randoms, *gallione_randoms_from_fovoham]
+
+gallione_story_fights: list[BattleMapping] = [
+ GarilandFight,
+ MandaliaPlains,
+ SweegyWoods,
+ DorterTradeCity1,
+ Miluda1,
+ Miluda2,
+ FortZeakden,
+ Dorter2,
+ Adramelk
+]
+
+gallione_fights: list[BattleMapping] = [*gallione_randoms, *gallione_story_fights]
+
+fovoham_only_randoms: list[BattleMapping] = [
+ FovohamPlainsEast1,
+ FovohamPlainsEast2,
+ FovohamPlainsEast3,
+ FovohamPlainsEast4,
+ YuguoWoodsEast1,
+ YuguoWoodsEast2,
+ YuguoWoodsEast3,
+ YuguoWoodsEast4,
+ YuguoWoodsEast5,
+ YuguoWoodsWest1,
+ YuguoWoodsWest2,
+ YuguoWoodsWest3,
+ YuguoWoodsWest4,
+ GrogHillWest1,
+ GrogHillWest2,
+ GrogHillWest3,
+ GrogHillWest4
+]
+
+fovoham_randoms_from_gallione: list[BattleMapping] = [
+ FovohamPlainsWest1,
+ FovohamPlainsWest2,
+ FovohamPlainsWest3,
+ FovohamPlainsWest4,
+ FovohamPlainsWest5,
+ FovohamPlainsSouth1,
+ FovohamPlainsSouth2,
+ FovohamPlainsSouth3,
+ FovohamPlainsSouth4
+]
+
+fovoham_randoms_from_lesalia: list[BattleMapping] = [
+ GrogHillSouth1,
+ GrogHillSouth2,
+ GrogHillSouth3,
+ GrogHillSouth4,
+ GrogHillSouth5
+]
+
+fovoham_randoms_from_zeltennia: list[BattleMapping] = [
+ GrogHillEast1,
+ GrogHillEast2,
+ GrogHillEast3,
+ GrogHillEast4
+]
+
+fovoham_randoms: list[BattleMapping] = [
+ *fovoham_only_randoms, *fovoham_randoms_from_gallione,
+ *fovoham_randoms_from_lesalia, *fovoham_randoms_from_zeltennia
+]
+
+fovoham_story_fights: list[BattleMapping] = [
+ Wiegraf1,
+ GrogHill,
+ RescueRafa,
+ YuguoWoods,
+ RiovanesCastleEntrance,
+ InsideofRiovanesCastle,
+ RooftopofRiovanesCastle
+]
+
+fovoham_fights: list[BattleMapping] = [*fovoham_randoms, *fovoham_story_fights]
+
+lesalia_only_randoms: list[BattleMapping] = [
+ BerveniaVolcanoSouth1,
+ BerveniaVolcanoSouth2,
+ BerveniaVolcanoSouth3,
+ BerveniaVolcanoSouth4,
+ ZeklausDesertNorth1,
+ ZeklausDesertNorth2,
+ ZeklausDesertNorth3,
+ ZeklausDesertNorth4,
+ ZeklausDesertEast1,
+ ZeklausDesertEast2,
+ ZeklausDesertEast3,
+ ZeklausDesertEast4,
+ AraguayWoodsEast1,
+ AraguayWoodsEast2,
+ AraguayWoodsEast3,
+ AraguayWoodsEast4,
+ ZirekileFallsWest1,
+ ZirekileFallsWest2,
+ ZirekileFallsWest3,
+ ZirekileFallsWest4
+]
+
+lesalia_randoms_from_gallione: list[BattleMapping] = [
+ AraguayWoodsWest1,
+ AraguayWoodsWest2,
+ AraguayWoodsWest3,
+ AraguayWoodsWest4,
+ AraguayWoodsWest5,
+]
+
+lesalia_randoms_from_fovoham: list[BattleMapping] = [
+ BerveniaVolcanoNorth1,
+ BerveniaVolcanoNorth2,
+ BerveniaVolcanoNorth3,
+ BerveniaVolcanoNorth4,
+ BerveniaVolcanoNorth5
+]
+
+lesalia_randoms_from_lionel: list[BattleMapping] = [
+ ZirekileFallsSouth1,
+ ZirekileFallsSouth2,
+ ZirekileFallsSouth3,
+ ZirekileFallsSouth4,
+]
+
+lesalia_randoms_from_limberry: list[BattleMapping] = [
+ ZirekileFallsEast1,
+ ZirekileFallsEast2,
+ ZirekileFallsEast3,
+ ZirekileFallsEast4,
+ ZirekileFallsEast5,
+]
+
+lesalia_randoms: list[BattleMapping] = [
+ *lesalia_only_randoms, *lesalia_randoms_from_gallione, *lesalia_randoms_from_fovoham,
+ *lesalia_randoms_from_lionel, *lesalia_randoms_from_limberry
+]
+
+lesalia_story_fights: list[BattleMapping] = [
+ SandRatCellar,
+ AraguayWoods,
+ ZirekileFalls,
+ GolandCoalCity,
+ OutsideLesaliaGateZalmo1,
+ CollieryUndergroundThirdFloor,
+ CollieryUndergroundSecondFloor,
+ CollieryUndergroundFirstFloor
+]
+
+lesalia_fights: list[BattleMapping] = [*lesalia_randoms, *lesalia_story_fights]
+
+lionel_only_randoms: list[BattleMapping] = [
+ BariusHillNorth1,
+ BariusHillNorth2,
+ BariusHillNorth3,
+ BariusHillNorth4,
+ BariusHillSouth1,
+ BariusHillSouth2,
+ BariusHillSouth3,
+ BariusHillSouth4,
+ BariusHillSouth5,
+ ZigolisSwampEast1,
+ ZigolisSwampEast2,
+ ZigolisSwampEast3,
+ ZigolisSwampEast4
+]
+
+lionel_randoms_from_murond: list[BattleMapping] = [
+ ZigolisSwampWest1,
+ ZigolisSwampWest2,
+ ZigolisSwampWest3,
+ ZigolisSwampWest4,
+ ZigolisSwampWest5,
+]
+
+lionel_randoms: list[BattleMapping] = [*lionel_only_randoms, *lionel_randoms_from_murond]
+
+lionel_story_fights: list[BattleMapping] = [
+ ZalandFortCity,
+ BariausHill,
+ ZigolisSwamp,
+ BariausValley,
+ GolgorandExecutionSite,
+ LionelCastleGate,
+ InsideofLionelCastle
+]
+
+lionel_fights: list[BattleMapping] = [*lionel_randoms, *lionel_story_fights]
+
+zeltennia_only_randoms: list[BattleMapping] = [
+ DoguolaPassEast1,
+ DoguolaPassEast2,
+ DoguolaPassEast3,
+ DoguolaPassEast4,
+ FinathRiverEast1,
+ FinathRiverEast2,
+ FinathRiverEast3,
+ FinathRiverEast4,
+ FinathRiverEast5,
+ FinathRiverWest1,
+ FinathRiverWest2,
+ FinathRiverWest3,
+ FinathRiverWest4,
+ GerminasPeakNorth1,
+ GerminasPeakNorth2,
+ GerminasPeakNorth3,
+ GerminasPeakNorth4,
+ GerminasPeakNorth5
+]
+
+zeltennia_randoms_from_fovoham: list[BattleMapping] = [
+ DoguolaPassWest1,
+ DoguolaPassWest2,
+ DoguolaPassWest3,
+ DoguolaPassWest4,
+ DoguolaPassWest5
+]
+
+zeltennia_randoms_from_limberry: list[BattleMapping] = [
+ GerminasPeakSouth1,
+ GerminasPeakSouth2,
+ GerminasPeakSouth3,
+ GerminasPeakSouth4,
+]
+
+zeltennia_randoms: list[BattleMapping] = [
+ *zeltennia_only_randoms, *zeltennia_randoms_from_fovoham, *zeltennia_randoms_from_limberry
+]
+
+zeltennia_story_fights: list[BattleMapping] = [
+ DoguolaPass,
+ BerveniaFreeCity,
+ FinathRiver,
+ ZalmoII,
+ GerminasPeak,
+ NelveskaTemple,
+ Zarghidas
+]
+
+zeltennia_fights: list[BattleMapping] = [*zeltennia_randoms, *zeltennia_story_fights]
+
+limberry_only_randoms: list[BattleMapping] = [
+ BedDesertSouth1,
+ BedDesertSouth2,
+ BedDesertSouth3,
+ BedDesertSouth4,
+ DolbodarSwampEast1,
+ DolbodarSwampEast2,
+ DolbodarSwampEast3,
+ DolbodarSwampEast4,
+ DolbodarSwampWest1,
+ DolbodarSwampWest2,
+ DolbodarSwampWest3,
+ DolbodarSwampWest4,
+ DolbodarSwampWest5,
+ PoeskasLakeSouth1,
+ PoeskasLakeSouth2,
+ PoeskasLakeSouth3,
+ PoeskasLakeSouth4
+]
+
+limberry_randoms_from_zeltennia: list[BattleMapping] = [
+ BedDesertNorth1,
+ BedDesertNorth2,
+ BedDesertNorth3,
+ BedDesertNorth4,
+ BedDesertNorth5,
+ PoeskasLakeNorth1,
+ PoeskasLakeNorth2,
+ PoeskasLakeNorth3,
+ PoeskasLakeNorth4,
+ PoeskasLakeNorth5,
+]
+
+limberry_randoms: list[BattleMapping] = [*limberry_only_randoms, *limberry_randoms_from_zeltennia]
+
+limberry_story_fights: list[BattleMapping] = [
+ BalkI,
+ NorthWallofBethlaGarrison,
+ SouthWallofBethlaGarrison,
+ BethlaSluice,
+ PoeskasLake,
+ OutsideofLimberryCastle,
+ ElmdorII,
+ Zalera
+]
+
+limberry_fights: list[BattleMapping] = [*limberry_randoms, *limberry_story_fights]
+
+nogias_fights: list[BattleMapping] = [
+ Nogias1,
+ Nogias2,
+ Nogias3,
+ Nogias4,
+]
+
+terminate_fights: list[BattleMapping] = [
+ Terminate1,
+ Terminate2,
+ Terminate3,
+ Terminate4,
+]
+
+delta_fights: list[BattleMapping] = [
+ Delta1,
+ Delta2,
+ Delta3,
+ Delta4,
+]
+
+valkyries_fights: list[BattleMapping] = [
+ Valkyries1,
+ Valkyries2,
+ Valkyries3,
+ Valkyries4
+]
+
+mlapan_fights: list[BattleMapping] = [
+ Mlapan1,
+ Mlapan2,
+ Mlapan3,
+ Mlapan4
+]
+
+tiger_fights: list[BattleMapping] = [
+ Tiger1,
+ Tiger2,
+ Tiger3,
+ Tiger4
+]
+
+bridge_fights: list[BattleMapping] = [
+ Bridge1,
+ Bridge2,
+ Bridge3,
+ Bridge4
+]
+
+voyage_fights: list[BattleMapping] = [
+ Voyage1,
+ Voyage2,
+ Voyage3,
+ Voyage4
+]
+
+horror_fights: list[BattleMapping] = [
+ Horror1,
+ Horror2,
+ Horror3,
+ Horror4
+]
+
+deep_dungeon_fights: list[BattleMapping] = [
+ *nogias_fights, *terminate_fights, *delta_fights, *valkyries_fights,
+ *mlapan_fights, *tiger_fights, *bridge_fights, *voyage_fights, *horror_fights
+]
+
+murond_story_fights: list[BattleMapping] = [
+ GougMachineCity,
+ UndergroundBookStorageSecondFloor,
+ UndergroundBookStorageThirdFloor,
+ UndergroundBookStorageFirstFloor,
+ StMurondTemple,
+ HallofStMurondTemple,
+ ChapelofStMurondTemple,
+ UndergroundBookStorageFourthFloor,
+ UndergroundBookStorageFifthFloor,
+ MurondDeathCity,
+ LostSacredPrecincts,
+ GraveyardofAirships1,
+ GraveyardofAirships2,
+ *deep_dungeon_fights
+]
+
+all_randoms: list[BattleMapping] = [
+ *gallione_randoms, *fovoham_randoms, *lesalia_randoms, *lionel_randoms,
+ *zeltennia_randoms, *limberry_randoms
+]
+
+all_story_fights: list[BattleMapping] = [
+ *gallione_story_fights, *fovoham_story_fights, *lesalia_story_fights,
+ *lionel_story_fights, *zeltennia_story_fights, *limberry_story_fights, *murond_story_fights
+]
+
+all_fights: list[BattleMapping] = [*all_randoms, *all_story_fights]
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/Birthday.py b/worlds/fftii/enemyrando/Birthday.py
new file mode 100644
index 000000000000..d657eacd4611
--- /dev/null
+++ b/worlds/fftii/enemyrando/Birthday.py
@@ -0,0 +1,19 @@
+from enum import Enum
+
+
+class Month(Enum):
+ UNKNOWN = 0
+ JANUARY = 1
+ FEBRUARY = 2
+ MARCH = 3
+ APRIL = 4
+ MAY = 5
+ JUNE = 6
+ JULY = 7
+ AUGUST = 8
+ SEPTEMBER = 9
+ OCTOBER = 10
+ NOVEMBER = 11
+ DECEMBER = 12
+ RANDOM = 254
+ NONE = 255
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/EventCodes.py b/worlds/fftii/enemyrando/EventCodes.py
new file mode 100644
index 000000000000..22410b4970f2
--- /dev/null
+++ b/worlds/fftii/enemyrando/EventCodes.py
@@ -0,0 +1,334 @@
+from enum import IntEnum, unique
+
+
+@unique
+class EventCode(IntEnum):
+ DOLBODAR_SWAMP_EAST_1 = 0x001
+ DOLBODAR_SWAMP_EAST_2 = 0x002
+ DOLBODAR_SWAMP_EAST_3 = 0x003
+ DOLBODAR_SWAMP_EAST_4 = 0x004
+ DOLBODAR_SWAMP_WEST_1 = 0x005
+ DOLBODAR_SWAMP_WEST_2 = 0x006
+ DOLBODAR_SWAMP_WEST_3 = 0x007
+ DOLBODAR_SWAMP_WEST_4 = 0x008
+
+ FOVOHAM_PLAINS_SOUTH_1 = 0x00D
+ FOVOHAM_PLAINS_SOUTH_2 = 0x00E
+ FOVOHAM_PLAINS_SOUTH_3 = 0x00F
+ FOVOHAM_PLAINS_SOUTH_4 = 0x010
+ FOVOHAM_PLAINS_WEST_1 = 0x011
+ FOVOHAM_PLAINS_WEST_2 = 0x012
+ FOVOHAM_PLAINS_WEST_3 = 0x013
+ FOVOHAM_PLAINS_WEST_4 = 0x014
+ FOVOHAM_PLAINS_EAST_1 = 0x015
+ FOVOHAM_PLAINS_EAST_2 = 0x016
+ FOVOHAM_PLAINS_EAST_3 = 0x017
+ FOVOHAM_PLAINS_EAST_4 = 0x018
+
+ SWEEGY_WOODS_EAST_1 = 0x019
+ SWEEGY_WOODS_EAST_2 = 0x01A
+ SWEEGY_WOODS_EAST_3 = 0x01B
+ SWEEGY_WOODS_EAST_4 = 0x01C
+ SWEEGY_WOODS_WEST_1 = 0x01D
+ SWEEGY_WOODS_WEST_2 = 0x01E
+ SWEEGY_WOODS_WEST_3 = 0x01F
+ SWEEGY_WOODS_WEST_4 = 0x020
+
+ BEVERNIA_VOLCANO_NORTH_1 = 0x025
+ BEVERNIA_VOLCANO_NORTH_2 = 0x026
+ BEVERNIA_VOLCANO_NORTH_3 = 0x027
+ BEVERNIA_VOLCANO_NORTH_4 = 0x028
+ BEVERNIA_VOLCANO_SOUTH_1 = 0x029
+ BEVERNIA_VOLCANO_SOUTH_2 = 0x02A
+ BEVERNIA_VOLCANO_SOUTH_3 = 0x02B
+ BEVERNIA_VOLCANO_SOUTH_4 = 0x02C
+
+ ZEKLAUS_DESERT_NORTH_1 = 0x031
+ ZEKLAUS_DESERT_NORTH_2 = 0x032
+ ZEKLAUS_DESERT_NORTH_3 = 0x033
+ ZEKLAUS_DESERT_NORTH_4 = 0x034
+ ZEKLAUS_DESERT_SOUTH_1 = 0x035
+ ZEKLAUS_DESERT_SOUTH_2 = 0x036
+ ZEKLAUS_DESERT_SOUTH_3 = 0x037
+ ZEKLAUS_DESERT_SOUTH_4 = 0x038
+ ZEKLAUS_DESERT_EAST_1 = 0x039
+ ZEKLAUS_DESERT_EAST_2 = 0x03A
+ ZEKLAUS_DESERT_EAST_3 = 0x03B
+ ZEKLAUS_DESERT_EAST_4 = 0x03C
+
+ LENALIA_PLATEAU_SOUTH_1 = 0x03D
+ LENALIA_PLATEAU_SOUTH_2 = 0x03E
+ LENALIA_PLATEAU_SOUTH_3 = 0x03F
+ LENALIA_PLATEAU_SOUTH_4 = 0x040
+ LENALIA_PLATEAU_NORTH_1 = 0x041
+ LENALIA_PLATEAU_NORTH_2 = 0x042
+ LENALIA_PLATEAU_NORTH_3 = 0x043
+ LENALIA_PLATEAU_NORTH_4 = 0x044
+
+ ZIGOLIS_SWAMP_EAST_1 = 0x049
+ ZIGOLIS_SWAMP_EAST_2 = 0x04A
+ ZIGOLIS_SWAMP_EAST_3 = 0x04B
+ ZIGOLIS_SWAMP_EAST_4 = 0x04C
+ ZIGOLIS_SWAMP_WEST_1 = 0x04D
+ ZIGOLIS_SWAMP_WEST_2 = 0x04E
+ ZIGOLIS_SWAMP_WEST_3 = 0x04F
+ ZIGOLIS_SWAMP_WEST_4 = 0x050
+
+ ZIREKILE_FALLS_EAST_5 = 0x052
+ BARIUS_HILL_SOUTH_5 = 0x053
+ LENALIA_PLATEAU_SOUTH_5 = 0x054
+
+ YUGUO_WOODS_WEST_1 = 0x055
+ YUGUO_WOODS_WEST_2 = 0x056
+ YUGUO_WOODS_WEST_3 = 0x057
+ YUGUO_WOODS_WEST_4 = 0x058
+ YUGUO_WOODS_EAST_1 = 0x059
+ YUGUO_WOODS_EAST_2 = 0x05A
+ YUGUO_WOODS_EAST_3 = 0x05B
+ YUGUO_WOODS_EAST_4 = 0x05C
+
+ DOLBODAR_SWAMP_WEST_5 = 0x05D
+ GROG_HILL_SOUTH_5 = 0x05E
+ BEVERNIA_VOLCANO_NORTH_5 = 0x05F
+ BARIUS_VALLEY_SOUTH_5 = 0x060
+
+ ARAGUAY_WOODS_WEST_1 = 0x061
+ ARAGUAY_WOODS_WEST_2 = 0x062
+ ARAGUAY_WOODS_WEST_3 = 0x063
+ ARAGUAY_WOODS_WEST_4 = 0x064
+ ARAGUAY_WOODS_EAST_1 = 0x065
+ ARAGUAY_WOODS_EAST_2 = 0x066
+ ARAGUAY_WOODS_EAST_3 = 0x067
+ ARAGUAY_WOODS_EAST_4 = 0x068
+
+ FINATH_RIVER_EAST_5 = 0x069
+ GERMINA_PEAK_NORTH_5 = 0x06A
+ ARAGUAY_WOODS_SOUTH_5 = 0x06B
+ YUGUO_WOODS_EAST_5 = 0x06C
+
+ GROG_HILL_WEST_1 = 0x06D
+ GROG_HILL_WEST_2 = 0x06E
+ GROG_HILL_WEST_3 = 0x06F
+ GROG_HILL_WEST_4 = 0x070
+ GROG_HILL_SOUTH_1 = 0x071
+ GROG_HILL_SOUTH_2 = 0x072
+ GROG_HILL_SOUTH_3 = 0x073
+ GROG_HILL_SOUTH_4 = 0x074
+ GROG_HILL_EAST_1 = 0x075
+ GROG_HILL_EAST_2 = 0x076
+ GROG_HILL_EAST_3 = 0x077
+ GROG_HILL_EAST_4 = 0x078
+
+ BED_DESERT_SOUTH_1 = 0x079
+ BED_DESERT_SOUTH_2 = 0x07A
+ BED_DESERT_SOUTH_3 = 0x07B
+ BED_DESERT_SOUTH_4 = 0x07C
+ BED_DESERT_NORTH_1 = 0x07D
+ BED_DESERT_NORTH_2 = 0x07E
+ BED_DESERT_NORTH_3 = 0x07F
+ BED_DESERT_NORTH_4 = 0x080
+ BED_DESERT_NORTH_5 = 0x081
+
+ FOVOHAM_PLAINS_WEST_5 = 0x082
+ DOGUOLA_PASS_WEST_5 = 0x083
+ SWEEGY_WOODS_EAST_5 = 0x084
+
+ ZIREKILE_FALLS_WEST_1 = 0x085
+ ZIREKILE_FALLS_WEST_2 = 0x086
+ ZIREKILE_FALLS_WEST_3 = 0x087
+ ZIREKILE_FALLS_WEST_4 = 0x088
+ ZIREKILE_FALLS_EAST_1 = 0x089
+ ZIREKILE_FALLS_EAST_2 = 0x08A
+ ZIREKILE_FALLS_EAST_3 = 0x08B
+ ZIREKILE_FALLS_EAST_4 = 0x08C
+ ZIREKILE_FALLS_SOUTH_1 = 0x08D
+ ZIREKILE_FALLS_SOUTH_2 = 0x08E
+ ZIREKILE_FALLS_SOUTH_3 = 0x08F
+ ZIREKILE_FALLS_SOUTH_4 = 0x090
+
+ BARIUS_HILL_NORTH_1 = 0x091
+ BARIUS_HILL_NORTH_2 = 0x092
+ BARIUS_HILL_NORTH_3 = 0x093
+ BARIUS_HILL_NORTH_4 = 0x094
+ BARIUS_HILL_SOUTH_1 = 0x095
+ BARIUS_HILL_SOUTH_2 = 0x096
+ BARIUS_HILL_SOUTH_3 = 0x097
+ BARIUS_HILL_SOUTH_4 = 0x098
+
+ POESKAS_LAKE_NORTH_5 = 0x099
+ ZIGOLIS_SWAMP_WEST_5 = 0x09A
+ MANDALIA_PLAINS_SOUTH_5 = 0x09B
+ ZEKLAUS_DESERT_SOUTH_5 = 0x09C
+
+ MANDALIA_PLAINS_NORTH_1 = 0x09D
+ MANDALIA_PLAINS_NORTH_2 = 0x09E
+ MANDALIA_PLAINS_NORTH_3 = 0x09F
+ MANDALIA_PLAINS_NORTH_4 = 0x0A0
+ MANDALIA_PLAINS_SOUTH_1 = 0x0A1
+ MANDALIA_PLAINS_SOUTH_2 = 0x0A2
+ MANDALIA_PLAINS_SOUTH_3 = 0x0A3
+ MANDALIA_PLAINS_SOUTH_4 = 0x0A4
+ MANDALIA_PLAINS_WEST_1 = 0x0A5
+ MANDALIA_PLAINS_WEST_2 = 0x0A6
+ MANDALIA_PLAINS_WEST_3 = 0x0A7
+ MANDALIA_PLAINS_WEST_4 = 0x0A8
+
+ DOGUOLA_PASS_EAST_1 = 0x0A9
+ DOGUOLA_PASS_EAST_2 = 0x0AA
+ DOGUOLA_PASS_EAST_3 = 0x0AB
+ DOGUOLA_PASS_EAST_4 = 0x0AC
+ DOGUOLA_PASS_WEST_1 = 0x0AD
+ DOGUOLA_PASS_WEST_2 = 0x0AE
+ DOGUOLA_PASS_WEST_3 = 0x0AF
+ DOGUOLA_PASS_WEST_4 = 0x0B0
+
+ END_1 = 0x0B1
+ # END_2 = 0x0B2
+ # END_3 = 0x0B3
+ # END_4 = 0x0B4
+
+ BARIUS_VALLEY_WEST_1 = 0x0B5
+ BARIUS_VALLEY_WEST_2 = 0x0B6
+ BARIUS_VALLEY_WEST_3 = 0x0B7
+ BARIUS_VALLEY_WEST_4 = 0x0B8
+ BARIUS_VALLEY_EAST_1 = 0x0B9
+ BARIUS_VALLEY_EAST_2 = 0x0BA
+ BARIUS_VALLEY_EAST_3 = 0x0BB
+ BARIUS_VALLEY_EAST_4 = 0x0BC
+ BARIUS_VALLEY_SOUTH_1 = 0x0BD
+ BARIUS_VALLEY_SOUTH_2 = 0x0BE
+ BARIUS_VALLEY_SOUTH_3 = 0x0BF
+ BARIUS_VALLEY_SOUTH_4 = 0x0C0
+
+ FINATH_RIVER_WEST_1 = 0x0C1
+ FINATH_RIVER_WEST_2 = 0x0C2
+ FINATH_RIVER_WEST_3 = 0x0C3
+ FINATH_RIVER_WEST_4 = 0x0C4
+ FINATH_RIVER_EAST_1 = 0x0C5
+ FINATH_RIVER_EAST_2 = 0x0C6
+ FINATH_RIVER_EAST_3 = 0x0C7
+ FINATH_RIVER_EAST_4 = 0x0C8
+
+ HORROR_1 = 0x0C9
+ HORROR_2 = 0x0CA
+ HORROR_3 = 0x0CB
+ HORROR_4 = 0x0CC
+
+ POESKAS_LAKE_NORTH_1 = 0x0CD
+ POESKAS_LAKE_NORTH_2 = 0x0CE
+ POESKAS_LAKE_NORTH_3 = 0x0CF
+ POESKAS_LAKE_NORTH_4 = 0x0D0
+ POESKAS_LAKE_SOUTH_1 = 0x0D1
+ POESKAS_LAKE_SOUTH_2 = 0x0D2
+ POESKAS_LAKE_SOUTH_3 = 0x0D3
+ POESKAS_LAKE_SOUTH_4 = 0x0D4
+
+ VOYAGE_1 = 0x0D5
+ VOYAGE_2 = 0x0D6
+ VOYAGE_3 = 0x0D7
+ VOYAGE_4 = 0x0D8
+
+ GERMINA_PEAK_NORTH_1 = 0x0D9
+ GERMINA_PEAK_NORTH_2 = 0x0DA
+ GERMINA_PEAK_NORTH_3 = 0x0DB
+ GERMINA_PEAK_NORTH_4 = 0x0DC
+ GERMINA_PEAK_SOUTH_1 = 0x0DD
+ GERMINA_PEAK_SOUTH_2 = 0x0DE
+ GERMINA_PEAK_SOUTH_3 = 0x0DF
+ GERMINA_PEAK_SOUTH_4 = 0x0E0
+
+ BRIDGE_1 = 0x0E1
+ BRIDGE_2 = 0x0E2
+ BRIDGE_3 = 0x0E3
+ BRIDGE_4 = 0x0E4
+ TIGER_1 = 0x0E5
+ TIGER_2 = 0x0E6
+ TIGER_3 = 0x0E7
+ TIGER_4 = 0x0E8
+ MLAPAN_1 = 0x0E9
+ MLAPAN_2 = 0x0EA
+ MLAPAN_3 = 0x0EB
+ MLAPAN_4 = 0x0EC
+ VALKYRIES_1 = 0x0ED
+ VALKYRIES_2 = 0x0EE
+ VALKYRIES_3 = 0x0EF
+ VALKYRIES_4 = 0x0F0
+ DELTA_1 = 0x0F1
+ DELTA_2 = 0x0F2
+ DELTA_3 = 0x0F3
+ DELTA_4 = 0x0F4
+ TERMINATE_1 = 0x0F5
+ TERMINATE_2 = 0x0F6
+ TERMINATE_3 = 0x0F7
+ TERMINATE_4 = 0x0F8
+ NOGIAS_1 = 0x0F9
+ NOGIAS_2 = 0x0FA
+ NOGIAS_3 = 0x0FB
+ NOGIAS_4 = 0x0FC
+
+ SWEEGY_WOODS = 0x180
+ DORTER_TRADE_CITY1 = 0x181
+ SAND_RAT_CELLAR = 0x182
+ ORBONNE_BATTLE = 0x183
+ GARILAND_FIGHT = 0x184
+ MANDALIA_PLAINS = 0x185
+ MILUDA1 = 0x18B
+ MILUDA2 = 0x18F
+ WIEGRAF1 = 0x190
+ FORT_ZEAKDEN = 0x191
+ DD_END_VERSUS_ELIDIBS = 0x192
+ DORTER2 = 0x193
+ ARAGUAY_WOODS = 0x194
+ ZIREKILE_FALLS = 0x195
+ ZALAND_FORT_CITY = 0x197
+ BARIAUS_HILL = 0x199
+ ZIGOLIS_SWAMP = 0x19A
+ GOUG_MACHINE_CITY = 0x19B
+ BESRODIO_SAVED = 0x19C
+ BARIAUS_VALLEY = 0x19D
+ GOLGORAND_EXECUTION_SITE = 0x19E
+ LIONEL_CASTLE_GATE = 0x19F
+ INSIDE_OF_LIONEL_CASTLE = 0x1A0
+ GOLAND_COAL_CITY = 0x1A1
+ GOLAND_COAL_CITY_POST_BATTLE = 0x1A2
+ ZARGHIDAS = 0x1A3
+ OUTSIDE_LESALIA_GATE_ZALMO_1 = 0x1A4
+ OUTSIDE_LESALIA_GATE_TALK_WITH_ALMA = 0x1A5
+ UNDERGROUND_BOOK_STORAGE_SECOND_FLOOR = 0x1A6
+ UNDERGROUND_BOOK_STORAGE_THIRD_FLOOR = 0x1A7
+ UNDERGROUND_BOOK_STORAGE_FIRST_FLOOR = 0x1A8
+ GROG_HILL = 0x1AA
+ RESCUE_RAFA = 0x1AC
+ YUGUO_WOODS = 0x1AE
+ RIOVANES_CASTLE_ENTRANCE = 0x1AF
+ INSIDE_OF_RIOVANES_CASTLE = 0x1B0
+ ROOFTOP_OF_RIOVANES_CASTLE = 0x1B1
+ UNDERGROUND_BOOK_STORAGE_FOURTH_FLOOR = 0x1B3
+ UNDERGROUND_BOOK_STORAGE_FIFTH_FLOOR = 0x1B4
+ ENTRANCE_TO_THE_OTHER_WORLD = 0x1B5
+ MUROND_DEATH_CITY = 0x1B6
+ LOST_SACRED_PRECINCTS = 0x1B7
+ GRAVEYARD_OF_AIRSHIPS_1 = 0x1B8
+ GRAVEYARD_OF_AIRSHIPS_2 = 0x1B9
+ DOGUOLA_PASS = 0x1BA
+ BERVENIA_FREE_CITY = 0x1BB
+ FINATH_RIVER = 0x1BC
+ ZALMO_II = 0x1BD
+ BALK_I = 0x1BF
+ SOUTH_WALL_OF_BETHLA_GARRISON = 0x1C0
+ NORTH_WALL_OF_BETHLA_GARRISON = 0x1C1
+ BETHLA_SLUICE = 0x1C2
+ GERMINAS_PEAK = 0x1C4
+ POESKAS_LAKE = 0x1C5
+ OUTSIDE_OF_LIMBERRY_CASTLE = 0x1C6
+ ELMDOR_II = 0x1C8
+ ZALERA = 0x1C9
+ ADRAMELK = 0x1CB
+ ST_MUROND_TEMPLE = 0x1CC
+ HALL_OF_ST_MUROND_TEMPLE = 0x1CD
+ CHAPEL_OF_ST_MUROND_TEMPLE = 0x1CE
+ COLLIERY_UNDERGROUND_THIRD_FLOOR = 0x1CF
+ COLLIERY_UNDERGROUND_SECOND_FLOOR = 0x1D0
+ COLLIERY_UNDERGROUND_FIRST_FLOOR = 0x1D1
+ UNDERGROUND_PASSAGE_IN_GOLAND = 0x1D2
+ NELVESKA_TEMPLE = 0x1D4
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/EventIDs.py b/worlds/fftii/enemyrando/EventIDs.py
new file mode 100644
index 000000000000..5eb9fde6bb7c
--- /dev/null
+++ b/worlds/fftii/enemyrando/EventIDs.py
@@ -0,0 +1,344 @@
+events = {
+ 0x001: "Dolbodar Swamp East 1",
+ 0x002: "Dolbodar Swamp East 2",
+ 0x003: "Dolbodar Swamp East 3",
+ 0x004: "Dolbodar Swamp East 4",
+ 0x005: "Dolbodar Swamp West 1",
+ 0x006: "Dolbodar Swamp West 2",
+ 0x007: "Dolbodar Swamp West 3",
+ 0x008: "Dolbodar Swamp West 4",
+
+ 0x00D: "Fovoham Plains South 1",
+ 0x00E: "Fovoham Plains South 2",
+ 0x00F: "Fovoham Plains South 3",
+ 0x010: "Fovoham Plains South 4",
+ 0x011: "Fovoham Plains West 1",
+ 0x012: "Fovoham Plains West 2",
+ 0x013: "Fovoham Plains West 3",
+ 0x014: "Fovoham Plains West 4",
+ 0x015: "Fovoham Plains East 1",
+ 0x016: "Fovoham Plains East 2",
+ 0x017: "Fovoham Plains East 3",
+ 0x018: "Fovoham Plains East 4",
+
+ 0x019: "Sweegy Woods East 1",
+ 0x01A: "Sweegy Woods East 2",
+ 0x01B: "Sweegy Woods East 3",
+ 0x01C: "Sweegy Woods East 4",
+ 0x01D: "Sweegy Woods West 1",
+ 0x01E: "Sweegy Woods West 2",
+ 0x01F: "Sweegy Woods West 3",
+ 0x020: "Sweegy Woods West 4",
+
+ 0x025: "Bevernia Volcano North 1",
+ 0x026: "Bevernia Volcano North 2",
+ 0x027: "Bevernia Volcano North 3",
+ 0x028: "Bevernia Volcano North 4",
+ 0x029: "Bevernia Volcano South 1",
+ 0x02A: "Bevernia Volcano South 2",
+ 0x02B: "Bevernia Volcano South 3",
+ 0x02C: "Bevernia Volcano South 4",
+
+ 0x031: "Zeklaus Desert North 1",
+ 0x032: "Zeklaus Desert North 2",
+ 0x033: "Zeklaus Desert North 3",
+ 0x034: "Zeklaus Desert North 4",
+ 0x035: "Zeklaus Desert South 1",
+ 0x036: "Zeklaus Desert South 2",
+ 0x037: "Zeklaus Desert South 3",
+ 0x038: "Zeklaus Desert South 4",
+ 0x039: "Zeklaus Desert East 1",
+ 0x03A: "Zeklaus Desert East 2",
+ 0x03B: "Zeklaus Desert East 3",
+ 0x03C: "Zeklaus Desert East 4",
+
+ 0x03D: "Lenalia Plateau South 1",
+ 0x03E: "Lenalia Plateau South 2",
+ 0x03F: "Lenalia Plateau South 3",
+ 0x040: "Lenalia Plateau South 4",
+ 0x041: "Lenalia Plateau North 1",
+ 0x042: "Lenalia Plateau North 2",
+ 0x043: "Lenalia Plateau North 3",
+ 0x044: "Lenalia Plateau North 4",
+
+ 0x049: "Zigolis Swamp East 1",
+ 0x04A: "Zigolis Swamp East 2",
+ 0x04B: "Zigolis Swamp East 3",
+ 0x04C: "Zigolis Swamp East 4",
+ 0x04D: "Zigolis Swamp West 1",
+ 0x04E: "Zigolis Swamp West 2",
+ 0x04F: "Zigolis Swamp West 3",
+ 0x050: "Zigolis Swamp West 4",
+
+ 0x052: "Zirekile Falls East 5",
+ 0x053: "Barius Hill South 5",
+ 0x054: "Lenalia Plateau South 5",
+
+ 0x055: "Yuguo Woods West 1",
+ 0x056: "Yuguo Woods West 2",
+ 0x057: "Yuguo Woods West 3",
+ 0x058: "Yuguo Woods West 4",
+ 0x059: "Yuguo Woods East 1",
+ 0x05A: "Yuguo Woods East 2",
+ 0x05B: "Yuguo Woods East 3",
+ 0x05C: "Yuguo Woods East 4",
+
+ 0x05D: "Dolbodar Swamp West 5",
+ 0x05E: "Grog Hill South 5",
+ 0x05F: "Bevernia Volcano North 5",
+ 0x060: "Barius Valley South 5",
+
+ 0x061: "Araguay Woods West 1",
+ 0x062: "Araguay Woods West 2",
+ 0x063: "Araguay Woods West 3",
+ 0x064: "Araguay Woods West 4",
+ 0x065: "Araguay Woods East 1",
+ 0x066: "Araguay Woods East 2",
+ 0x067: "Araguay Woods East 3",
+ 0x068: "Araguay Woods East 4",
+
+ 0x069: "Finath River East 5",
+ 0x06A: "Germina Peak North 5",
+ 0x06B: "Araguay Woods South 5",
+ 0x06C: "Yuguo Woods East 5",
+
+ 0x06D: "Grog Hill West 1",
+ 0x06E: "Grog Hill West 2",
+ 0x06F: "Grog Hill West 3",
+ 0x070: "Grog Hill West 4",
+ 0x071: "Grog Hill South 1",
+ 0x072: "Grog Hill South 2",
+ 0x073: "Grog Hill South 3",
+ 0x074: "Grog Hill South 4",
+ 0x075: "Grog Hill East 1",
+ 0x076: "Grog Hill East 2",
+ 0x077: "Grog Hill East 3",
+ 0x078: "Grog Hill East 4",
+
+ 0x079: "Bed Desert South 1",
+ 0x07A: "Bed Desert South 2",
+ 0x07B: "Bed Desert South 3",
+ 0x07C: "Bed Desert South 4",
+ 0x07D: "Bed Desert North 1",
+ 0x07E: "Bed Desert North 2",
+ 0x07F: "Bed Desert North 3",
+ 0x080: "Bed Desert North 4",
+
+ 0x081: "Bed Desert North 5",
+ 0x082: "Fovoham Plains West 5",
+ 0x083: "Doguola Pass West 5",
+ 0x084: "Sweegy Woods East 5",
+
+ 0x085: "Zirekile Falls West 1",
+ 0x086: "Zirekile Falls West 2",
+ 0x087: "Zirekile Falls West 3",
+ 0x088: "Zirekile Falls West 4",
+ 0x089: "Zirekile Falls East 1",
+ 0x08A: "Zirekile Falls East 2",
+ 0x08B: "Zirekile Falls East 3",
+ 0x08C: "Zirekile Falls East 4",
+ 0x08D: "Zirekile Falls South 1",
+ 0x08E: "Zirekile Falls South 2",
+ 0x08F: "Zirekile Falls South 3",
+ 0x090: "Zirekile Falls South 4",
+
+ 0x091: "Barius Hill North 1",
+ 0x092: "Barius Hill North 2",
+ 0x093: "Barius Hill North 3",
+ 0x094: "Barius Hill North 4",
+ 0x095: "Barius Hill South 1",
+ 0x096: "Barius Hill South 2",
+ 0x097: "Barius Hill South 3",
+ 0x098: "Barius Hill South 4",
+
+ 0x099: "Poeskas Lake North 5",
+ 0x09A: "Zigolis Swamp West 5",
+ 0x09B: "Mandalia Plains South 5",
+ 0x09C: "Zeklaus Desert South 5",
+
+ 0x09D: "Mandalia Plains North 1",
+ 0x09E: "Mandalia Plains North 2",
+ 0x09F: "Mandalia Plains North 3",
+ 0x0A0: "Mandalia Plains North 4",
+ 0x0A1: "Mandalia Plains South 1",
+ 0x0A2: "Mandalia Plains South 2",
+ 0x0A3: "Mandalia Plains South 3",
+ 0x0A4: "Mandalia Plains South 4",
+ 0x0A5: "Mandalia Plains West 1",
+ 0x0A6: "Mandalia Plains West 2",
+ 0x0A7: "Mandalia Plains West 3",
+ 0x0A8: "Mandalia Plains West 4",
+
+ 0x0A9: "Doguola Pass East 1",
+ 0x0AA: "Doguola Pass East 2",
+ 0x0AB: "Doguola Pass East 3",
+ 0x0AC: "Doguola Pass East 4",
+ 0x0AD: "Doguola Pass West 1",
+ 0x0AE: "Doguola Pass West 2",
+ 0x0AF: "Doguola Pass West 3",
+ 0x0B0: "Doguola Pass West 4",
+
+ 0x0B5: "Barius Valley West 1",
+ 0x0B6: "Barius Valley West 2",
+ 0x0B7: "Barius Valley West 3",
+ 0x0B8: "Barius Valley West 4",
+ 0x0B9: "Barius Valley East 1",
+ 0x0BA: "Barius Valley East 2",
+ 0x0BB: "Barius Valley East 3",
+ 0x0BC: "Barius Valley East 4",
+ 0x0BD: "Barius Valley South 1",
+ 0x0BE: "Barius Valley South 2",
+ 0x0BF: "Barius Valley South 3",
+ 0x0C0: "Barius Valley South 4",
+
+ 0x0C1: "Finath River West 1",
+ 0x0C2: "Finath River West 2",
+ 0x0C3: "Finath River West 3",
+ 0x0C4: "Finath River West 4",
+ 0x0C5: "Finath River East 1",
+ 0x0C6: "Finath River East 2",
+ 0x0C7: "Finath River East 3",
+ 0x0C8: "Finath River East 4",
+
+ 0x0C9: "Horror",
+ 0x0CA: "Horror",
+ 0x0CB: "Horror",
+ 0x0CC: "Horror",
+
+ 0x0CD: "Poeskas Lake North 1",
+ 0x0CE: "Poeskas Lake North 2",
+ 0x0CF: "Poeskas Lake North 3",
+ 0x0D0: "Poeskas Lake North 4",
+ 0x0D1: "Poeskas Lake South 1",
+ 0x0D2: "Poeskas Lake South 2",
+ 0x0D3: "Poeskas Lake South 3",
+ 0x0D4: "Poeskas Lake South 4",
+
+ 0x0D5: "Voyage 1",
+ 0x0D6: "Voyage 2",
+ 0x0D7: "Voyage 3",
+ 0x0D8: "Voyage 4",
+
+ 0x0D9: "Germina Peak North 1",
+ 0x0DA: "Germina Peak North 2",
+ 0x0DB: "Germina Peak North 3",
+ 0x0DC: "Germina Peak North 4",
+ 0x0DD: "Germina Peak South 1",
+ 0x0DE: "Germina Peak South 2",
+ 0x0DF: "Germina Peak South 3",
+ 0x0E0: "Germina Peak South 4",
+
+ 0x0E1: "Bridge 1",
+ 0x0E2: "Bridge 2",
+ 0x0E3: "Bridge 3",
+ 0x0E4: "Bridge 4",
+
+ 0x0E5: "Tiger 1",
+ 0x0E6: "Tiger 2",
+ 0x0E7: "Tiger 3",
+ 0x0E8: "Tiger 4",
+
+ 0x0E9: "Mlapan 1",
+ 0x0EA: "Mlapan 2",
+ 0x0EB: "Mlapan 3",
+ 0x0EC: "Mlapan 4",
+
+ 0x0ED: "Valkyries 1",
+ 0x0EE: "Valkyries 2",
+ 0x0EF: "Valkyries 3",
+ 0x0F0: "Valkyries 4",
+
+ 0x0F1: "Delta 1",
+ 0x0F2: "Delta 2",
+ 0x0F3: "Delta 3",
+ 0x0F4: "Delta 4",
+
+ 0x0F5: "Terminate 1",
+ 0x0F6: "Terminate 2",
+ 0x0F7: "Terminate 3",
+ 0x0F8: "Terminate 4",
+
+ 0x0F9: "Nogias 1",
+ 0x0FA: "Nogias 2",
+ 0x0FB: "Nogias 3",
+ 0x0FC: "Nogias 4",
+
+ 0x180: "Sweegy Woods",
+ 0x181: "Dorter Trade City1",
+ 0x182: "Sand Rat Cellar",
+
+ 0x184: "Gariland Fight",
+ 0x185: "Mandalia Plains",
+
+ 0x18B: "Miluda1",
+
+ 0x18F: "Miluda2",
+ 0x190: "Wiegraf1",
+ 0x191: "Fort Zeakden",
+ 0x192: "DD END versus Elidibs",
+ 0x193: "Dorter2",
+ 0x194: "Araguay Woods",
+ 0x195: "Zirekile Falls",
+
+ 0x197: "Zaland Fort City",
+
+ 0x199: "Bariaus Hill",
+ 0x19A: "Zigolis Swamp",
+ 0x19B: "Goug Machine City",
+
+ 0x19D: "Bariaus Valley",
+ 0x19E: "Golgorand Execution Site",
+ 0x19F: "Lionel Castle Gate ",
+ 0x1A0: "Inside of Lionel Castle",
+ 0x1A1: "Goland Coal City",
+
+ 0x1A3: "Zarghidas",
+ 0x1A4: "Outside Lesalia Gate Zalmo 1",
+
+ 0x1A6: "Underground Book Storage Second Floor",
+ 0x1A7: "Underground Book Storage Third Floor",
+ 0x1A8: "Underground Book Storage First Floor",
+
+ 0x1AA: "Grog Hill ",
+
+ 0x1AC: "Rescue Rafa",
+
+ 0x1AE: "Yuguo Woods",
+ 0x1AF: "Riovanes Castle Entrance",
+ 0x1B0: "Inside of Riovanes Castle",
+ 0x1B1: "Rooftop of Riovanes Castle",
+
+ 0x1B3: "Underground Book Storage Fourth Floor",
+ 0x1B4: "Underground Book Storage Fifth Floor",
+
+ 0x1B6: "Murond Death City",
+ 0x1B7: "Lost Sacred Precincts",
+ 0x1B8: "Graveyard of Airships 1",
+ 0x1B9: "Graveyard of Airships 2",
+ 0x1BA: "Doguola Pass",
+ 0x1BB: "Bervenia Free City",
+ 0x1BC: "Finath River",
+ 0x1BD: "Zalmo II",
+
+ 0x1BF: "Balk I",
+ 0x1C0: "South Wall of Bethla Garrison",
+ 0x1C1: "North Wall of Bethla Garrison",
+ 0x1C2: "Bethla Sluice",
+
+ 0x1C4: "Germinas Peak",
+ 0x1C5: "Poeskas Lake",
+ 0x1C6: "Outside of Limberry Castle",
+
+ 0x1C8: "Elmdor II",
+ 0x1C9: "Zalera",
+
+ 0x1CB: "Adramelk",
+ 0x1CC: "St Murond Temple",
+ 0x1CD: "Hall of St Murond Temple",
+ 0x1CE: "Chapel of St Murond Temple",
+ 0x1CF: "Colliery Underground Third Floor",
+ 0x1D0: "Colliery Underground Second Floor",
+ 0x1D1: "Colliery Underground First Floor",
+ 0x1D2: "Underground Passage in Goland",
+ 0x1D4: "Nelveska Temple",
+}
diff --git a/worlds/fftii/enemyrando/Items.py b/worlds/fftii/enemyrando/Items.py
new file mode 100644
index 000000000000..be192d53a911
--- /dev/null
+++ b/worlds/fftii/enemyrando/Items.py
@@ -0,0 +1,24 @@
+from enum import Enum, unique
+
+
+@unique
+class Items(Enum):
+ NONE = 0xFF
+ RANDOM = 0xFE
+
+ BLOOD_SWORD = 0x17
+ SAVE_THE_QUEEN = 0x22
+ EXCALIBUR = 0x23
+ MASAMUNE = 0x2E
+ MACE_OF_ZEUS = 0x41
+ BLAST_GUN = 0x4C
+ BLAZE_GUN = 0x4A
+ GENJI_SHIELD = 0x8C
+ GENJI_HELMET = 0x9B
+ THIEF_HAT = 0xA8
+ CACHUSHA = 0xA9
+ BARETTE = 0xAA
+ GENJI_ARMOR = 0xB7
+ WHITE_ROBE = 0xCC
+ CHANTAGE = 0xEC
+ GENJI_GAUNTLET = 0xD8
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/Job.py b/worlds/fftii/enemyrando/Job.py
new file mode 100644
index 000000000000..81d1bea172f7
--- /dev/null
+++ b/worlds/fftii/enemyrando/Job.py
@@ -0,0 +1,375 @@
+from enum import Enum, IntEnum, unique
+
+
+@unique
+class Job(IntEnum):
+ RAMZA_SQUIRE_CHAPTER_1 = 0x01
+ RAMZA_SQUIRE_CHAPTER_23 = 0x02
+ RAMZA_SQUIRE_CHAPTER_4 = 0x03
+ SQUIRE_DELITA = 0x04
+ HOLY_KNIGHT_DELITA = 0x05
+ ARC_KNIGHT_DELITA = 0x06
+ SQUIRE_ALGUS = 0x07
+ ARC_KNIGHT_ZALBAG = 0x08
+ LUNE_KNIGHT = 0x09
+ PRINCESS = 0x0C
+ HOLY_SWORDSMAN = 0x0D
+ DRAGONER = 0x0F
+ HOLY_PRIEST = 0x10
+ DARK_KNIGHT_ENEMY = 0x11
+ MALAK_DEAD = 0x12
+ ALMA_INITIAL_DEAD = 0x14
+ ASTROLOGIST = 0x15
+ ENGINEER_MUSTADIO = 0x16
+ DARK_KNIGHT_GUEST = 0x17
+ DRACLAU = 0x18
+ HEAVEN_KNIGHT_GUEST = 0x19
+ HELL_KNIGHT = 0x1A
+ ARC_KNIGHT_ELMDOR = 0x1B
+ TETA = 0x1C
+ HOLY_KNIGHT_AGRIAS = 0x1E
+ TEMPLE_KNIGHT = 0x1F
+ WHITE_KNIGHT_C1 = 0x20
+ ENGINEER_GUEST = 0x22
+ RUDVICH = 0x23
+ DIVINE_KNIGHT_VORMAV = 0x24
+ DIVINE_KNIGHT_ROFEL = 0x25
+ KNIGHT_BLADE = 0x26
+ SORCERER = 0x27
+ WHITE_KNIGHT_C3 = 0x28
+ HEAVEN_KNIGHT = 0x29
+ DIVINE_KNIGHT_MELIADOUL = 0x2A
+ ENGINEER_BALK = 0x2B
+ ALMA_DEAD = 0x2C
+ ASSASSIN_CELIA = 0x2D
+ ASSASSIN_LEDE = 0x2E
+ DIVINE_KNIGHT_MELIADOUL_ENEMY = 0x2F
+ CLERIC = 0x30
+ AJORA = 0x31
+ SOLDIER = 0x32
+ ARC_KNIGHT_ZOMBIE = 0x33
+ HOLY_KNIGHT_AGRIAS_GUEST = 0x34
+ KNIGHT_UNDEAD = 0x3D
+ ARCHER_UNDEAD = 0x3F
+ ALTIMA_1 = 0x41
+ WIZARD_UNDEAD = 0x42
+ TIME_MAGE_UNDEAD = 0x44
+ ORACLE_UNDEAD = 0x46
+ SUMMONER_UNDEAD = 0x47
+ ALTIMA_2 = 0x49
+
+ SQUIRE = 0x4A
+ CHEMIST = 0x4B
+ KNIGHT = 0x4C
+ ARCHER = 0x4D
+ MONK = 0x4E
+ PRIEST = 0x4F
+ WIZARD = 0x50
+ TIMEMAGE = 0x51
+ SUMMONER = 0x52
+ THIEF = 0x53
+ MEDIATOR = 0x54
+ ORACLE = 0x55
+ GEOMANCER = 0x56
+ LANCER = 0x57
+ SAMURAI = 0x58
+ NINJA = 0x59
+ CALCULATOR = 0x5A
+ BARD = 0x5B
+ DANCER = 0x5C
+ MIME = 0x5D
+
+ YELLOW_CHOCOBO = 0x5E
+ BLACK_CHOCOBO = 0x5F
+ RED_CHOCOBO = 0x60
+ GOBLIN = 0x61
+ BLACK_GOBLIN = 0x62
+ GOBBLEDEGUCK = 0x63
+ BOMB = 0x64
+ GRENADE = 0x65
+ EXPLOSIVE = 0x66
+ RED_PANTHER = 0x67
+ CUAR = 0x68
+ VAMPIRE = 0x69
+ PISCO_DEMON = 0x6A
+ SQUIDLARKIN = 0x6B
+ MINDFLARE = 0x6C
+ SKELETON = 0x6D
+ BONE_SNATCH = 0x6E
+ LIVING_BONE = 0x6F
+ GHOUL = 0x70
+ GUST = 0x71
+ REVNANT = 0x72
+ FLOTIBALL = 0x73
+ AHRIMAN = 0x74
+ PLAGUE = 0x75
+ JURAVIS = 0x76
+ STEEL_HAWK = 0x77
+ COCATORIS = 0x78
+ URIBO = 0x79
+ PORKY = 0x7A
+ WILDBOW = 0x7B
+ WOODMAN = 0x7C
+ TRENT = 0x7D
+ TAIJU = 0x7E
+ BULL_DEMON = 0x7F
+ MINITAURUS = 0x80
+ SACRED = 0x81
+ MORBOL = 0x82
+ OCHU = 0x83
+ GREAT_MORBOL = 0x84
+ BEHEMOTH = 0x85
+ KING_BEHEMOTH = 0x86
+ DARK_BEHEMOTH = 0x87
+ DRAGON = 0x88
+ BLUE_DRAGON = 0x89
+ RED_DRAGON = 0x8A
+ HYUDRA = 0x8B
+ HYDRA = 0x8C
+ TIAMAT = 0x8D
+
+ HOLY_DRAGON = 0x48
+ BYBLOS = 0x90
+ STEEL_GIANT = 0x91
+ APANDA = 0x96
+ ARCHAIC_DEMON = 0x99
+ ULTIMA_DEMON = 0x9A
+
+ VELIUS = 0x3C
+ ZALERA = 0x3E
+ HASHMALUM = 0x40
+ QUEKLAIN = 0x43
+ ADRAMELK = 0x45
+ ELIDIBS = 0x97
+
+monster_families = {
+ Job.YELLOW_CHOCOBO: [Job.YELLOW_CHOCOBO, Job.BLACK_CHOCOBO, Job.RED_CHOCOBO],
+ Job.BLACK_CHOCOBO: [Job.YELLOW_CHOCOBO, Job.BLACK_CHOCOBO, Job.RED_CHOCOBO],
+ Job.RED_CHOCOBO: [Job.YELLOW_CHOCOBO, Job.BLACK_CHOCOBO, Job.RED_CHOCOBO],
+ Job.GOBLIN: [Job.GOBLIN, Job.BLACK_GOBLIN, Job.GOBBLEDEGUCK],
+ Job.BLACK_GOBLIN: [Job.GOBLIN, Job.BLACK_GOBLIN, Job.GOBBLEDEGUCK],
+ Job.GOBBLEDEGUCK: [Job.GOBLIN, Job.BLACK_GOBLIN, Job.GOBBLEDEGUCK],
+ Job.BOMB: [Job.BOMB, Job.GRENADE, Job.EXPLOSIVE],
+ Job.GRENADE: [Job.BOMB, Job.GRENADE, Job.EXPLOSIVE],
+ Job.EXPLOSIVE: [Job.BOMB, Job.GRENADE, Job.EXPLOSIVE],
+ Job.RED_PANTHER: [Job.RED_PANTHER, Job.CUAR, Job.VAMPIRE],
+ Job.CUAR: [Job.RED_PANTHER, Job.CUAR, Job.VAMPIRE],
+ Job.VAMPIRE: [Job.RED_PANTHER, Job.CUAR, Job.VAMPIRE],
+ Job.PISCO_DEMON: [Job.PISCO_DEMON, Job.SQUIDLARKIN, Job.MINDFLARE],
+ Job.SQUIDLARKIN: [Job.PISCO_DEMON, Job.SQUIDLARKIN, Job.MINDFLARE],
+ Job.MINDFLARE: [Job.PISCO_DEMON, Job.SQUIDLARKIN, Job.MINDFLARE],
+ Job.SKELETON: [Job.SKELETON, Job.BONE_SNATCH, Job.LIVING_BONE],
+ Job.BONE_SNATCH: [Job.SKELETON, Job.BONE_SNATCH, Job.LIVING_BONE],
+ Job.LIVING_BONE: [Job.SKELETON, Job.BONE_SNATCH, Job.LIVING_BONE],
+ Job.GHOUL: [Job.GHOUL, Job.GUST, Job.REVNANT],
+ Job.GUST: [Job.GHOUL, Job.GUST, Job.REVNANT],
+ Job.REVNANT: [Job.GHOUL, Job.GUST, Job.REVNANT],
+ Job.FLOTIBALL: [Job.FLOTIBALL, Job.AHRIMAN, Job.PLAGUE],
+ Job.AHRIMAN: [Job.FLOTIBALL, Job.AHRIMAN, Job.PLAGUE],
+ Job.PLAGUE: [Job.FLOTIBALL, Job.AHRIMAN, Job.PLAGUE],
+ Job.JURAVIS: [Job.JURAVIS, Job.STEEL_HAWK, Job.COCATORIS],
+ Job.STEEL_HAWK: [Job.JURAVIS, Job.STEEL_HAWK, Job.COCATORIS],
+ Job.COCATORIS: [Job.JURAVIS, Job.STEEL_HAWK, Job.COCATORIS],
+ Job.URIBO: [Job.URIBO, Job.PORKY, Job.WILDBOW],
+ Job.PORKY: [Job.URIBO, Job.PORKY, Job.WILDBOW],
+ Job.WILDBOW: [Job.URIBO, Job.PORKY, Job.WILDBOW],
+ Job.WOODMAN: [Job.WOODMAN, Job.TRENT, Job.TAIJU],
+ Job.TRENT: [Job.WOODMAN, Job.TRENT, Job.TAIJU],
+ Job.TAIJU: [Job.WOODMAN, Job.TRENT, Job.TAIJU],
+ Job.BULL_DEMON: [Job.BULL_DEMON, Job.MINITAURUS, Job.SACRED],
+ Job.MINITAURUS: [Job.BULL_DEMON, Job.MINITAURUS, Job.SACRED],
+ Job.SACRED: [Job.BULL_DEMON, Job.MINITAURUS, Job.SACRED],
+ Job.MORBOL: [Job.MORBOL, Job.OCHU, Job.GREAT_MORBOL],
+ Job.OCHU: [Job.MORBOL, Job.OCHU, Job.GREAT_MORBOL],
+ Job.GREAT_MORBOL: [Job.MORBOL, Job.OCHU, Job.GREAT_MORBOL],
+ Job.BEHEMOTH: [Job.BEHEMOTH, Job.KING_BEHEMOTH, Job.DARK_BEHEMOTH],
+ Job.KING_BEHEMOTH: [Job.BEHEMOTH, Job.KING_BEHEMOTH, Job.DARK_BEHEMOTH],
+ Job.DARK_BEHEMOTH: [Job.BEHEMOTH, Job.KING_BEHEMOTH, Job.DARK_BEHEMOTH],
+ Job.DRAGON: [Job.DRAGON, Job.BLUE_DRAGON, Job.RED_DRAGON],
+ Job.BLUE_DRAGON: [Job.DRAGON, Job.BLUE_DRAGON, Job.RED_DRAGON],
+ Job.RED_DRAGON: [Job.DRAGON, Job.BLUE_DRAGON, Job.RED_DRAGON],
+ Job.HYUDRA: [Job.HYUDRA, Job.HYDRA, Job.TIAMAT],
+ Job.HYDRA: [Job.HYUDRA, Job.HYDRA, Job.TIAMAT],
+ Job.TIAMAT: [Job.HYUDRA, Job.HYDRA, Job.TIAMAT]
+}
+
+special_character_jobs = {
+ 0x01: "Ramza Squire (Chapter 1)",
+ 0x02: "Ramza Squire (Chapter 2-3)",
+ 0x03: "Ramza Squire (Chapter 4)",
+ 0x04: "Squire (Delita)",
+ 0x05: "Holy Knight (Delita)",
+ 0x06: "Arc Knight (Delita)",
+ 0x07: "Squire (Algus)",
+ 0x08: "Arc Knight (Zalbag)",
+ 0x09: "Lune Knight",
+ 0x0C: "Princess",
+ 0x0D: "Holy Swordsman",
+ 0x0F: "Dragoner",
+ 0x10: "Holy Priest",
+ 0x11: "Dark Knight (Enemy)",
+ 0x12: "Malak (Dead)",
+ 0x14: "Alma (Initial Dead)",
+ 0x15: "Astrologist",
+ 0x16: "Engineer (Mustadio)",
+ 0x17: "Dark Knight (Guest)",
+ 0x18: "Draclau",
+ 0x19: "Heaven Knight (Guest)",
+ 0x1A: "Hell Knight",
+ 0x1B: "Arc Knight (Elmdor)",
+ 0x1C: "Teta",
+ 0x1E: "Holy Knight (Agrias)",
+ 0x1F: "Temple Knight",
+ 0x20: "White Knight (Wiegraf, Chapter 1)",
+ 0x22: "Engineer (Mustadio, Guest))",
+ 0x23: "Rudvich",
+ 0x24: "Divine Knight (Vormav)",
+ 0x25: "Divine Knight (Rofel)",
+ 0x26: "Knight Blade",
+ 0x27: "Sorcerer",
+ 0x28: "White Knight (Wiegraf, Chapter 3)",
+ 0x29: "Heaven Knight",
+ 0x2A: "Divine Knight (Meliadoul)",
+ 0x2B: "Engineer (Balk)",
+ 0x2C: "Alma (Dead)",
+ 0x2D: "Assassin (Celia)",
+ 0x2E: "Assassin (Lede)",
+ 0x2F: "Divine Knight (Meliadoul, Enemy)",
+ 0x30: "Cleric",
+ 0x31: "Ajora",
+ 0x32: "Soldier",
+ 0x33: "Arc Knight (Zombie)",
+ 0x34: "Holy Knight (Agrias, Guest)",
+ 0x3D: "Knight (Undead)",
+ 0x3F: "Archer (Undead)",
+ 0x41: "Altima 1",
+ 0x42: "Wizard (Undead)",
+ 0x44: "Time Mage (Undead)",
+ 0x46: "Oracle (Undead)",
+ 0x47: "Summoner (Undead)",
+ 0x49: "Altima 2"
+}
+
+generic_jobs = {
+ 0x4A: "Squire",
+ 0x4B: "Chemist",
+ 0x4C: "Knight",
+ 0x4D: "Archer",
+ 0x4E: "Monk",
+ 0x4F: "Priest",
+ 0x50: "Wizard",
+ 0x51: "Time Mage",
+ 0x52: "Summoner",
+ 0x53: "Thief",
+ 0x54: "Mediator",
+ 0x55: "Oracle",
+ 0x56: "Geomancer",
+ 0x57: "Lancer",
+ 0x58: "Samurai",
+ 0x59: "Ninja",
+ 0x5A: "Calculator",
+ 0x5B: "Bard",
+ 0x5C: "Dancer",
+ 0x5D: "Mime"
+}
+
+generic_monster_jobs = {
+ 0x5E: "Yellow Chocobo",
+ 0x5F: "Black Chocobo",
+ 0x60: "Red Chocobo",
+ 0x61: "Goblin",
+ 0x62: "Black Goblin",
+ 0x63: "Gobbledeguck",
+ 0x64: "Bomb",
+ 0x65: "Grenade",
+ 0x66: "Explosive",
+ 0x67: "Red Panther",
+ 0x68: "Cuar",
+ 0x69: "Vampire",
+ 0x6A: "Pisco Demon",
+ 0x6B: "Squidlarkin",
+ 0x6C: "Mindflare",
+ 0x6D: "Skeleton",
+ 0x6E: "Bone Snatch",
+ 0x6F: "Living Bone",
+ 0x70: "Ghoul",
+ 0x71: "Gust",
+ 0x72: "Revnant",
+ 0x73: "Flotiball",
+ 0x74: "Ahriman",
+ 0x75: "Plague",
+ 0x76: "Juravis",
+ 0x77: "Steel Hawk",
+ 0x78: "Cocatoris",
+ 0x79: "Uribo",
+ 0x7A: "Porky",
+ 0x7B: "Wildbow",
+ 0x7C: "Woodman",
+ 0x7D: "Trent",
+ 0x7E: "Taiju",
+ 0x7F: "Bull Demon",
+ 0x80: "Minitaurus",
+ 0x81: "Sacred",
+ 0x82: "Morbol",
+ 0x83: "Ochu",
+ 0x84: "Great Morbol",
+ 0x85: "Behemoth",
+ 0x86: "King Behemoth",
+ 0x87: "Dark Behemoth",
+ 0x88: "Dragon",
+ 0x89: "Blue Dragon",
+ 0x8A: "Red Dragon",
+ 0x8B: "Hyudra",
+ 0x8C: "Hydra",
+ 0x8D: "Tiamat"
+}
+
+special_monster_jobs = {
+ 0x48: "Holy Dragon",
+ 0x90: "Byblos",
+ 0x91: "Steel Giant",
+ 0x96: "Apanda",
+ 0x99: "Archaic Demon",
+ 0x9A: "Ultima Demon"
+
+}
+
+lucavi_jobs = {
+ 0x3C: "Warlock (Velius)",
+ 0x3E: "Angel of Death (Zalera)",
+ 0x40: "Regulator (Hashmalum)",
+ 0x43: "Impure King (Queklain)",
+ 0x45: "Ghost of Fury (Adramelk)",
+ 0x97: "Serpentarius (Elidibs)"
+}
+
+all_jobs = {
+ **special_character_jobs,
+ **generic_jobs,
+ **generic_monster_jobs,
+ **special_monster_jobs,
+ **lucavi_jobs
+}
+
+@unique
+class UnlockedJob(Enum):
+ BASE = 0x00
+ CHEMIST = 0x01
+ KNIGHT = 0x02
+ ARCHER = 0x03
+ MONK = 0x04
+ PRIEST = 0x05
+ WIZARD = 0x06
+ TIMEMAGE = 0x07
+ SUMMONER = 0x08
+ THIEF = 0x09
+ MEDIATOR = 0x0A
+ ORACLE = 0x0B
+ GEOMANCER = 0x0C
+ LANCER = 0x0D
+ SAMURAI = 0x0E
+ NINJA = 0x0F
+ CALCULATOR = 0x10
+ BARD = 0x11
+ DANCER = 0x12
+ MIME = 0x13
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/RandomizedMappings.py b/worlds/fftii/enemyrando/RandomizedMappings.py
new file mode 100644
index 000000000000..9636b2de4fe1
--- /dev/null
+++ b/worlds/fftii/enemyrando/RandomizedMappings.py
@@ -0,0 +1,143 @@
+from worlds.fftii.enemyrando.RandomizedUnitFactory import RandomizedUnitFactory
+from worlds.fftii.enemyrando.RandomizedUnits import *
+
+base_mapping = {
+ job: job for job in list(Job)
+}
+
+for job in list(Job):
+ print(Job(job).name)
+
+allowed_mappings: dict[Job, dict[type[RandomizedUnit], int]] = {
+ Job.RAMZA_SQUIRE_CHAPTER_1: {RamzaC1Squire: 1},
+ Job.RAMZA_SQUIRE_CHAPTER_23: {RamzaC23Squire: 1},
+ Job.RAMZA_SQUIRE_CHAPTER_4: {RamzaC4Squire: 9, RamzaC4SquireFullSkillset: 1},
+ Job.SQUIRE_DELITA: {DelitaSquire: 1},
+ Job.HOLY_KNIGHT_DELITA: {DelitaHolyKnight: 1},
+ Job.ARC_KNIGHT_DELITA: {DelitaArcKnight: 1},
+ Job.SQUIRE_ALGUS: {Algus: 9, AlgusWithCrossbow: 1},
+ Job.ARC_KNIGHT_ZALBAG: {ZalbagArcKnight: 1},
+ Job.LUNE_KNIGHT: {LuneKnight: 1},
+ Job.PRINCESS: {Princess: 1},
+ Job.HOLY_SWORDSMAN: {HolySwordsman: 9, HolySwordsmanWithExcalibur: 1},
+ Job.DRAGONER: {Dragoner: 1},
+ Job.HOLY_PRIEST: {HolyPriest: 1},
+ Job.DARK_KNIGHT_ENEMY: {DarkKnight: 1},
+ Job.ASTROLOGIST: {Astrologist: 1},
+ Job.ENGINEER_MUSTADIO: {EngineerMustadio: 1},
+ Job.DARK_KNIGHT_GUEST: {DarkKnight: 1},
+ Job.HEAVEN_KNIGHT_GUEST: {HeavenKnight: 1},
+ Job.HELL_KNIGHT: {HellKnight: 1},
+ Job.ARC_KNIGHT_ELMDOR: {ArcKnightElmdor: 9, ArcKnightElmdorWithKit: 1},
+ Job.HOLY_KNIGHT_AGRIAS: {HolyKnightAgrias: 1},
+ Job.TEMPLE_KNIGHT: {TempleKnight: 1},
+ Job.WHITE_KNIGHT_C1: {WhiteKnightChapter1: 9, WhiteKnightChapter1WithCounter: 1},
+ Job.ENGINEER_GUEST: {EngineerMustadio: 1},
+ Job.DIVINE_KNIGHT_VORMAV: {DivineKnightVormav: 1, DivineKnightRofel: 1, DivineKnightMeliadoul: 1},
+ Job.DIVINE_KNIGHT_ROFEL: {DivineKnightVormav: 1, DivineKnightRofel: 1, DivineKnightMeliadoul: 1},
+ Job.KNIGHT_BLADE: {KnightBlade: 9, KnightBladeWithKit: 1},
+ Job.SORCERER: {Sorcerer: 1},
+ Job.WHITE_KNIGHT_C3: {WhiteKnight: 9, WhiteKnightWithCounter: 1},
+ Job.HEAVEN_KNIGHT: {HeavenKnight: 1},
+ Job.DIVINE_KNIGHT_MELIADOUL: {DivineKnightVormav: 1, DivineKnightRofel: 1, DivineKnightMeliadoul: 1},
+ Job.ENGINEER_BALK: {EngineerBalk: 1},
+ Job.ASSASSIN_CELIA: {AssassinCelia: 1, AssassinLede: 1},
+ Job.ASSASSIN_LEDE: {AssassinCelia: 1, AssassinLede: 1},
+ Job.DIVINE_KNIGHT_MELIADOUL_ENEMY: {DivineKnightVormav: 1, DivineKnightRofel: 1, DivineKnightMeliadoul: 1},
+ Job.CLERIC: {Cleric: 9, ClericWithUltima: 1},
+ Job.SOLDIER: {Soldier: 1},
+ Job.ARC_KNIGHT_ZOMBIE: {ArcKnightZombie: 1},
+ Job.HOLY_KNIGHT_AGRIAS_GUEST: {HolyKnightAgrias: 1},
+ Job.KNIGHT_UNDEAD: {UndeadKnight: 1},
+ Job.ARCHER_UNDEAD: {UndeadArcher: 1},
+ Job.ALTIMA_1: {Altima1: 1},
+ Job.WIZARD_UNDEAD: {UndeadWizard: 1},
+ Job.TIME_MAGE_UNDEAD: {UndeadTimeMage: 1},
+ Job.ORACLE_UNDEAD: {UndeadOracle: 1},
+ Job.SUMMONER_UNDEAD: {UndeadSummoner: 1},
+ Job.ALTIMA_2: {Altima2: 1},
+ Job.SQUIRE: {MaleSquire: 1, FemaleSquire: 1},
+ Job.CHEMIST: {MaleChemist: 1, FemaleChemist: 1},
+ Job.KNIGHT: {MaleKnight: 1, FemaleChemist: 1},
+ Job.ARCHER: {MaleArcher: 1, FemaleArcher: 1},
+ Job.MONK: {MaleMonk: 1, FemaleMonk: 1},
+ Job.PRIEST: {MalePriest: 1, FemalePriest: 1},
+ Job.WIZARD: {MaleWizard: 1, FemaleWizard: 1},
+ Job.TIMEMAGE: {MaleTimeMage: 1, FemaleTimeMage: 1},
+ Job.SUMMONER: {MaleSummoner: 1, FemaleSummoner: 1},
+ Job.THIEF: {MaleThief: 1, FemaleThief: 1},
+ Job.MEDIATOR: {MaleMediator: 1, FemaleMediator: 1},
+ Job.ORACLE: {MaleOracle: 1, FemaleOracle: 1},
+ Job.GEOMANCER: {MaleGeomancer: 1, FemaleGeomancer: 1},
+ Job.LANCER: {MaleLancer: 1, FemaleLancer: 1},
+ Job.SAMURAI: {MaleSamurai: 1, FemaleSamurai: 1},
+ Job.NINJA: {MaleNinja: 1, FemaleNinja: 1},
+ Job.CALCULATOR: {MaleCalculator: 1, FemaleCalculator: 1},
+ Job.BARD: {MaleBard: 1},
+ Job.DANCER: {FemaleDancer: 1},
+ Job.MIME: {MaleMime: 1, FemaleMime: 1},
+ Job.YELLOW_CHOCOBO: {YellowChocobo: 1, BlackChocobo: 1, RedChocobo: 1},
+ Job.BLACK_CHOCOBO: {YellowChocobo: 1, BlackChocobo: 1, RedChocobo: 1},
+ Job.RED_CHOCOBO: {YellowChocobo: 1, BlackChocobo: 1, RedChocobo: 1},
+ Job.GOBLIN: {Goblin: 1, BlackGoblin: 1, Gobbledeguck: 1},
+ Job.BLACK_GOBLIN: {Goblin: 1, BlackGoblin: 1, Gobbledeguck: 1},
+ Job.GOBBLEDEGUCK: {Goblin: 1, BlackGoblin: 1, Gobbledeguck: 1},
+ Job.BOMB: {Bomb: 1, Grenade: 1, Explosive: 1},
+ Job.GRENADE: {Bomb: 1, Grenade: 1, Explosive: 1},
+ Job.EXPLOSIVE: {Bomb: 1, Grenade: 1, Explosive: 1},
+ Job.RED_PANTHER: {RedPanther: 1, Cuar: 1, Vampire: 1},
+ Job.CUAR: {RedPanther: 1, Cuar: 1, Vampire: 1},
+ Job.VAMPIRE: {RedPanther: 1, Cuar: 1, Vampire: 1},
+ Job.PISCO_DEMON: {PiscoDemon: 1, Squidlarkin: 1, Mindflare: 1},
+ Job.SQUIDLARKIN: {PiscoDemon: 1, Squidlarkin: 1, Mindflare: 1},
+ Job.MINDFLARE: {PiscoDemon: 1, Squidlarkin: 1, Mindflare: 1},
+ Job.SKELETON: {Skeleton: 1, BoneSnatch: 1, LivingBone: 1},
+ Job.BONE_SNATCH: {Skeleton: 1, BoneSnatch: 1, LivingBone: 1},
+ Job.LIVING_BONE: {Skeleton: 1, BoneSnatch: 1, LivingBone: 1},
+ Job.GHOUL: {Ghoul: 1, Gust: 1, Revnant: 1},
+ Job.GUST: {Ghoul: 1, Gust: 1, Revnant: 1},
+ Job.REVNANT: {Ghoul: 1, Gust: 1, Revnant: 1},
+ Job.FLOTIBALL: {Flotiball: 1, Ahriman: 1, Plague: 1},
+ Job.AHRIMAN: {Flotiball: 1, Ahriman: 1, Plague: 1},
+ Job.PLAGUE: {Flotiball: 1, Ahriman: 1, Plague: 1},
+ Job.JURAVIS: {Juravis: 1, SteelHawk: 1, Cocatoris: 1},
+ Job.STEEL_HAWK: {Juravis: 1, SteelHawk: 1, Cocatoris: 1},
+ Job.COCATORIS: {Juravis: 1, SteelHawk: 1, Cocatoris: 1},
+ Job.URIBO: {Uribo: 1, Porky: 1, Wildbow: 1},
+ Job.PORKY: {Uribo: 1, Porky: 1, Wildbow: 1},
+ Job.WILDBOW: {Uribo: 1, Porky: 1, Wildbow: 1},
+ Job.WOODMAN: {Woodman: 1, Trent: 1, Taiju: 1},
+ Job.TRENT: {Woodman: 1, Trent: 1, Taiju: 1},
+ Job.TAIJU: {Woodman: 1, Trent: 1, Taiju: 1},
+ Job.BULL_DEMON: {BullDemon: 1, Minitaurus: 1, Sacred: 1},
+ Job.MINITAURUS: {BullDemon: 1, Minitaurus: 1, Sacred: 1},
+ Job.SACRED: {BullDemon: 1, Minitaurus: 1, Sacred: 1},
+ Job.MORBOL: {Morbol: 1, Ochu: 1, GreatMorbol: 1},
+ Job.OCHU: {Morbol: 1, Ochu: 1, GreatMorbol: 1},
+ Job.GREAT_MORBOL: {Morbol: 1, Ochu: 1, GreatMorbol: 1},
+ Job.BEHEMOTH: {Behemoth: 1, KingBehemoth: 1, DarkBehemoth: 1},
+ Job.KING_BEHEMOTH: {Behemoth: 1, KingBehemoth: 1, DarkBehemoth: 1},
+ Job.DARK_BEHEMOTH: {Behemoth: 1, KingBehemoth: 1, DarkBehemoth: 1},
+ Job.DRAGON: {Dragon: 1, BlueDragon: 1, RedDragon: 1},
+ Job.BLUE_DRAGON: {Dragon: 1, BlueDragon: 1, RedDragon: 1},
+ Job.RED_DRAGON: {Dragon: 1, BlueDragon: 1, RedDragon: 1},
+ Job.HYUDRA: {Hyudra: 1, Hydra: 1, Tiamat: 1},
+ Job.HYDRA: {Hyudra: 1, Hydra: 1, Tiamat: 1},
+ Job.TIAMAT: {Hyudra: 1, Hydra: 1, Tiamat: 1},
+ Job.HOLY_DRAGON: {HolyDragon: 1},
+ Job.BYBLOS: {Byblos: 1},
+ Job.STEEL_GIANT: {SteelGiant: 1},
+ Job.APANDA: {Apanda: 1},
+ Job.ARCHAIC_DEMON: {ArchaicDemon: 1, UltimaDemon: 1},
+ Job.ULTIMA_DEMON: {ArchaicDemon: 1, UltimaDemon: 1},
+ Job.VELIUS: {VeliusWithKit: 1},
+ Job.ZALERA: {ZaleraWithKit: 1},
+ Job.HASHMALUM: {HashmalumWithKit: 1},
+ Job.QUEKLAIN: {QueklainWithKit: 1},
+ Job.ADRAMELK: {AdramelkWithKit: 1},
+ Job.ELIDIBS: {Elidibs: 1}
+}
+
+factories = {
+ job: RandomizedUnitFactory(mapping) for job, mapping in allowed_mappings.items()
+}
diff --git a/worlds/fftii/enemyrando/RandomizedUnitFactory.py b/worlds/fftii/enemyrando/RandomizedUnitFactory.py
new file mode 100644
index 000000000000..cda76c731113
--- /dev/null
+++ b/worlds/fftii/enemyrando/RandomizedUnitFactory.py
@@ -0,0 +1,24 @@
+from random import Random
+
+from worlds.fftii.enemyrando.RandomizedUnits import RandomizedUnit
+
+
+class RandomizedUnitFactory:
+ units: dict[type[RandomizedUnit], int]
+ random: Random
+
+ def __init__(self, units: dict[type[RandomizedUnit], int], random: Random = Random()):
+ self.units = units
+ self.random = random
+
+
+ def get_lowest_difficulty(self) -> int:
+ return min([unit.difficulty for unit in self.units.keys()])
+
+ def get_unit(self, difficulty: int) -> type[RandomizedUnit] | None:
+ """Returns a unit from the factory with a difficulty less than or equal to the specified difficulty"""
+ if difficulty < self.get_lowest_difficulty():
+ return None
+ eligible_units = {unit: self.units[unit] for unit in self.units.keys() if self.units[unit] <= difficulty}
+ chosen_unit: type[RandomizedUnit] = self.random.sample(eligible_units, k=1, counts=eligible_units.values()).pop()
+ return chosen_unit
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/RandomizedUnits.py b/worlds/fftii/enemyrando/RandomizedUnits.py
new file mode 100644
index 000000000000..323726db800b
--- /dev/null
+++ b/worlds/fftii/enemyrando/RandomizedUnits.py
@@ -0,0 +1,1216 @@
+from worlds.fftii.enemyrando.Abilities import ReactionAbility, SupportAbility, MovementAbility, \
+ ActionAbility
+from worlds.fftii.enemyrando.Birthday import Month
+from worlds.fftii.enemyrando.Items import Items
+from worlds.fftii.enemyrando.Job import UnlockedJob, Job
+from worlds.fftii.enemyrando.SpriteSet import SpriteSet
+from worlds.fftii.enemyrando.Unit import UnitGender
+
+RANDOM_VALUE = 0xFE
+
+class RandomizedUnit:
+ job: Job
+ job_name: str
+ sprite_set: SpriteSet
+ sprite_set_name: str
+ gender: UnitGender
+ birthday_month: Month = Month.RANDOM
+ birthday_day: int = RANDOM_VALUE
+ brave: int = RANDOM_VALUE
+ faith: int = RANDOM_VALUE
+ unlocked_job: UnlockedJob
+ unlocked_job_level: int
+ primary: ActionAbility = ActionAbility.JOB
+ secondary: ActionAbility = ActionAbility.RANDOM
+ reaction: ReactionAbility = ReactionAbility.RANDOM
+ support: SupportAbility = SupportAbility.RANDOM
+ movement: MovementAbility = MovementAbility.RANDOM
+ head: Items = Items.RANDOM
+ body: Items = Items.RANDOM
+ accessory: Items = Items.RANDOM
+ right_hand: Items = Items.RANDOM
+ left_hand: Items = Items.RANDOM
+ difficulty: int = 0
+
+#region Generic humanss
+class MaleSquire(RandomizedUnit):
+ job = Job.SQUIRE
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleSquire(RandomizedUnit):
+ job = Job.SQUIRE
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleChemist(RandomizedUnit):
+ job = Job.CHEMIST
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleChemist(RandomizedUnit):
+ job = Job.CHEMIST
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleKnight(RandomizedUnit):
+ job = Job.KNIGHT
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleKnight(RandomizedUnit):
+ job = Job.KNIGHT
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleArcher(RandomizedUnit):
+ job = Job.ARCHER
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleArcher(RandomizedUnit):
+ job = Job.ARCHER
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleMonk(RandomizedUnit):
+ job = Job.MONK
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleMonk(RandomizedUnit):
+ job = Job.MONK
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MalePriest(RandomizedUnit):
+ job = Job.PRIEST
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemalePriest(RandomizedUnit):
+ job = Job.PRIEST
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleWizard(RandomizedUnit):
+ job = Job.WIZARD
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleWizard(RandomizedUnit):
+ job = Job.WIZARD
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleTimeMage(RandomizedUnit):
+ job = Job.TIMEMAGE
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleTimeMage(RandomizedUnit):
+ job = Job.TIMEMAGE
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleSummoner(RandomizedUnit):
+ job = Job.SUMMONER
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleSummoner(RandomizedUnit):
+ job = Job.SUMMONER
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleThief(RandomizedUnit):
+ job = Job.THIEF
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleThief(RandomizedUnit):
+ job = Job.THIEF
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleMediator(RandomizedUnit):
+ job = Job.MEDIATOR
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleMediator(RandomizedUnit):
+ job = Job.MEDIATOR
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleOracle(RandomizedUnit):
+ job = Job.ORACLE
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleOracle(RandomizedUnit):
+ job = Job.ORACLE
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleGeomancer(RandomizedUnit):
+ job = Job.GEOMANCER
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleGeomancer(RandomizedUnit):
+ job = Job.GEOMANCER
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleLancer(RandomizedUnit):
+ job = Job.LANCER
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleLancer(RandomizedUnit):
+ job = Job.LANCER
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleSamurai(RandomizedUnit):
+ job = Job.SAMURAI
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleSamurai(RandomizedUnit):
+ job = Job.SAMURAI
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleNinja(RandomizedUnit):
+ job = Job.NINJA
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleNinja(RandomizedUnit):
+ job = Job.NINJA
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleCalculator(RandomizedUnit):
+ job = Job.CALCULATOR
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleCalculator(RandomizedUnit):
+ job = Job.CALCULATOR
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleBard(RandomizedUnit):
+ job = Job.BARD
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleDancer(RandomizedUnit):
+ job = Job.DANCER
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+
+
+class MaleMime(RandomizedUnit):
+ job = Job.MIME
+ sprite_set = SpriteSet.GENERIC_MALE
+ gender = UnitGender.MALE
+
+
+class FemaleMime(RandomizedUnit):
+ job = Job.MIME
+ sprite_set = SpriteSet.GENERIC_FEMALE
+ gender = UnitGender.FEMALE
+#endregion
+
+#region Special humans
+class RamzaC1Squire(RandomizedUnit):
+ job = Job.RAMZA_SQUIRE_CHAPTER_1
+ sprite_set = SpriteSet.RAMZA_C1
+ gender = UnitGender.MALE
+
+
+class RamzaC23Squire(RandomizedUnit):
+ job = Job.RAMZA_SQUIRE_CHAPTER_23
+ sprite_set = SpriteSet.RAMZA_C23
+ gender = UnitGender.MALE
+
+
+class RamzaC4Squire(RandomizedUnit):
+ job = Job.RAMZA_SQUIRE_CHAPTER_4
+ sprite_set = SpriteSet.RAMZA_C4
+ gender = UnitGender.MALE
+
+
+class RamzaC4SquireFullSkillset(RamzaC4Squire):
+ primary = ActionAbility.GUTS_C4
+
+
+class DelitaSquire(RandomizedUnit):
+ job = Job.SQUIRE_DELITA
+ sprite_set = SpriteSet.DELITA_C1
+ gender = UnitGender.MALE
+
+
+class DelitaHolyKnight(RandomizedUnit):
+ job = Job.HOLY_KNIGHT_DELITA
+ sprite_set = SpriteSet.DELITA_C2
+ gender = UnitGender.MALE
+ difficulty = 4
+
+class DelitaArcKnight(RandomizedUnit):
+ job = Job.ARC_KNIGHT_DELITA
+ sprite_set = SpriteSet.DELITA_C4
+ gender = UnitGender.MALE
+ difficulty = 8
+
+
+class Algus(RandomizedUnit):
+ job = Job.SQUIRE_ALGUS
+ sprite_set = SpriteSet.ALGUS
+ gender = UnitGender.MALE
+
+class AlgusWithCrossbow(Algus):
+ support = SupportAbility.EQUIP_CROSSBOW
+
+
+class ZalbagArcKnight(RandomizedUnit):
+ job = Job.ARC_KNIGHT_ZALBAG
+ sprite_set = SpriteSet.ZALBAG
+ gender = UnitGender.MALE
+ difficulty = 4
+
+class LuneKnight(RandomizedUnit):
+ job = Job.LUNE_KNIGHT
+ sprite_set = SpriteSet.DYCEDARG
+ gender = UnitGender.MALE
+ difficulty = 4
+
+class Princess(RandomizedUnit):
+ job = Job.PRINCESS
+ sprite_set = SpriteSet.OVELIA
+ gender = UnitGender.FEMALE
+
+
+class HolySwordsman(RandomizedUnit):
+ job = Job.HOLY_SWORDSMAN
+ sprite_set = SpriteSet.ORLANDU
+ gender = UnitGender.MALE
+ difficulty = 8
+
+class HolySwordsmanWithExcalibur(HolySwordsman):
+ right_hand: Items = Items.EXCALIBUR
+ difficulty = 12
+
+
+class Dragoner(RandomizedUnit):
+ job = Job.DRAGONER
+ sprite_set = SpriteSet.REIS
+ gender = UnitGender.FEMALE
+ difficulty = 4
+
+class HolyPriest(RandomizedUnit):
+ job = Job.HOLY_PRIEST
+ sprite_set = SpriteSet.ZALMO
+ gender = UnitGender.MALE
+
+
+class DarkKnight(RandomizedUnit):
+ job = Job.DARK_KNIGHT_GUEST
+ sprite_set = SpriteSet.GAFGARION
+ gender = UnitGender.MALE
+
+
+class Astrologist(RandomizedUnit):
+ job = Job.ASTROLOGIST
+ sprite_set = SpriteSet.OLAN
+ gender = UnitGender.MALE
+ difficulty = 4
+
+
+class EngineerMustadio(RandomizedUnit):
+ job = Job.ENGINEER_MUSTADIO
+ sprite_set = SpriteSet.MUSTADIO
+ gender = UnitGender.MALE
+
+
+class HellKnight(RandomizedUnit):
+ job = Job.HELL_KNIGHT
+ sprite_set = SpriteSet.MALAK
+ gender = UnitGender.MALE
+
+
+class ArcKnightElmdor(RandomizedUnit):
+ job = Job.ARC_KNIGHT_ELMDOR
+ sprite_set = SpriteSet.ELMDOR
+ gender = UnitGender.MALE
+ difficulty = 8
+
+class ArcKnightElmdorWithKit(ArcKnightElmdor):
+ primary = ActionAbility.SWORD_SPIRIT_2
+ secondary = ActionAbility.BLOOD_SUCK
+ reaction = ReactionAbility.BLADE_GRASP
+ difficulty = 12
+
+
+class HolyKnightAgrias(RandomizedUnit):
+ job = Job.HOLY_KNIGHT_AGRIAS
+ sprite_set = SpriteSet.AGRIAS
+ gender = UnitGender.FEMALE
+ difficulty = 4
+
+
+class TempleKnight(RandomizedUnit):
+ job = Job.TEMPLE_KNIGHT
+ sprite_set = SpriteSet.BEOWULF
+ gender = UnitGender.MALE
+ difficulty = 6
+
+
+class DivineKnightVormav(RandomizedUnit):
+ job = Job.DIVINE_KNIGHT_VORMAV
+ sprite_set = SpriteSet.VORMAV
+ gender = UnitGender.MALE
+ difficulty = 6
+
+
+class DivineKnightRofel(RandomizedUnit):
+ job = Job.DIVINE_KNIGHT_ROFEL
+ sprite_set = SpriteSet.ROFEL
+ gender = UnitGender.MALE
+ difficulty = 6
+
+
+class KnightBlade(RandomizedUnit):
+ job = Job.KNIGHT_BLADE
+ sprite_set = SpriteSet.IZLUDE
+ gender = UnitGender.MALE
+ difficulty = 4
+
+
+class KnightBladeWithKit(KnightBlade):
+ primary = ActionAbility.IZLUDE_BATTLE_SKILL
+ secondary = ActionAbility.IZLUDE_JUMP
+ support = SupportAbility.MAINTENANCE
+ difficulty = 6
+
+
+class Sorcerer(RandomizedUnit):
+ job = Job.SORCERER
+ sprite_set = SpriteSet.KLETIAN
+ gender = UnitGender.MALE
+ difficulty = 8
+
+
+class WhiteKnight(RandomizedUnit):
+ job = Job.WHITE_KNIGHT_C3
+ sprite_set = SpriteSet.WIEGRAF2
+ gender = UnitGender.MALE
+ difficulty = 4
+
+
+class WhiteKnightWithCounter(WhiteKnight):
+ reaction = ReactionAbility.COUNTER
+
+
+class WhiteKnightChapter1(WhiteKnight):
+ job = Job.WHITE_KNIGHT_C1
+ sprite_set = SpriteSet.WIEGRAF1
+
+
+class WhiteKnightChapter1WithCounter(WhiteKnightChapter1):
+ reaction = ReactionAbility.COUNTER
+
+
+class HeavenKnight(RandomizedUnit):
+ job = Job.HEAVEN_KNIGHT
+ sprite_set = SpriteSet.RAFA
+ gender = UnitGender.FEMALE
+
+
+class DivineKnightMeliadoul(RandomizedUnit):
+ job = Job.DIVINE_KNIGHT_MELIADOUL
+ sprite_set = SpriteSet.MELIADOUL
+ gender = UnitGender.FEMALE
+ difficulty = 6
+
+
+class EngineerBalk(RandomizedUnit):
+ job = Job.ENGINEER_BALK
+ sprite_set = SpriteSet.BALK
+ gender = UnitGender.MALE
+
+
+class AssassinCelia(RandomizedUnit):
+ job = Job.ASSASSIN_CELIA
+ sprite_set = SpriteSet.CELIA
+ gender = UnitGender.FEMALE
+ difficulty = 10
+
+
+class AssassinLede(RandomizedUnit):
+ job = Job.ASSASSIN_LEDE
+ sprite_set = SpriteSet.LEDE
+ gender = UnitGender.FEMALE
+ difficulty = 10
+
+
+class Cleric(RandomizedUnit):
+ job = Job.CLERIC
+ sprite_set = SpriteSet.ALMA
+ gender = UnitGender.FEMALE
+
+
+class ClericWithUltima(Cleric):
+ primary = ActionAbility.HOLY_MAGIC
+ difficulty = 4
+
+
+class Soldier(RandomizedUnit):
+ job = Job.SOLDIER
+ sprite_set = SpriteSet.CLOUD
+ gender = UnitGender.MALE
+ difficulty = 4
+
+
+class ArcKnightZombie(RandomizedUnit):
+ job = Job.ARC_KNIGHT_ZOMBIE
+ sprite_set = SpriteSet.ZALBAG_ZOMBIE
+ gender = UnitGender.MALE
+ difficulty = 8
+
+
+class ArcKnightZombieWithKit(ArcKnightZombie):
+ secondary = ActionAbility.BLOOD_SUCK
+ difficulty = 12
+
+
+class UndeadKnight(RandomizedUnit):
+ job = Job.KNIGHT_UNDEAD
+ sprite_set = SpriteSet.UNDEAD_KNIGHT
+ gender = UnitGender.MONSTER
+
+
+class UndeadArcher(RandomizedUnit):
+ job = Job.ARCHER_UNDEAD
+ sprite_set = SpriteSet.UNDEAD_ARCHER
+ gender = UnitGender.MALE
+
+
+class UndeadWizard(RandomizedUnit):
+ job = Job.WIZARD_UNDEAD
+ sprite_set = SpriteSet.UNDEAD_WIZARD
+ gender = UnitGender.MALE
+
+
+class UndeadTimeMage(RandomizedUnit):
+ job = Job.TIME_MAGE_UNDEAD
+ sprite_set = SpriteSet.UNDEAD_TIMEMAGE
+ gender = UnitGender.FEMALE
+
+
+class UndeadOracle(RandomizedUnit):
+ job = Job.ORACLE_UNDEAD
+ sprite_set = SpriteSet.UNDEAD_ORACLE
+ gender = UnitGender.MALE
+
+
+class UndeadSummoner(RandomizedUnit):
+ job = Job.SUMMONER_UNDEAD
+ sprite_set = SpriteSet.UNDEAD_SUMMONER
+ gender = UnitGender.FEMALE
+
+
+class Altima1(RandomizedUnit):
+ job = Job.ALTIMA_1
+ sprite_set = SpriteSet.ALTIMA_1
+ gender = UnitGender.MONSTER
+ primary = ActionAbility.ULTIMATE_MAGIC
+ secondary = ActionAbility.CHAOS
+ difficulty = 14
+
+
+class Altima2(RandomizedUnit):
+ job = Job.ALTIMA_2
+ sprite_set = SpriteSet.ALTIMA_2
+ gender = UnitGender.MONSTER
+ primary = ActionAbility.COMPLETE_MAGIC
+ secondary = ActionAbility.SATURATION
+ difficulty = 14
+#endregion
+
+#region Generic monsters
+class GenericMonster(RandomizedUnit):
+ sprite_set = SpriteSet.MONSTER
+ gender = UnitGender.MONSTER
+
+
+class YellowChocobo(GenericMonster):
+ job = Job.YELLOW_CHOCOBO
+
+
+class BlackChocobo(GenericMonster):
+ job = Job.BLACK_CHOCOBO
+ difficulty = 4
+
+
+class RedChocobo(GenericMonster):
+ job = Job.RED_CHOCOBO
+ difficulty = 6
+
+
+class Goblin(GenericMonster):
+ job = Job.GOBLIN
+
+
+class BlackGoblin(GenericMonster):
+ job = Job.BLACK_GOBLIN
+
+
+class Gobbledeguck(GenericMonster):
+ job = Job.GOBBLEDEGUCK
+
+
+class Bomb(GenericMonster):
+ job = Job.BOMB
+
+
+class Grenade(GenericMonster):
+ job = Job.GRENADE
+
+
+class Explosive(GenericMonster):
+ job = Job.EXPLOSIVE
+
+
+class RedPanther(GenericMonster):
+ job = Job.RED_PANTHER
+
+
+class Cuar(GenericMonster):
+ job = Job.CUAR
+
+
+class Vampire(GenericMonster):
+ job = Job.VAMPIRE
+
+
+class PiscoDemon(GenericMonster):
+ job = Job.PISCO_DEMON
+
+
+class Squidlarkin(GenericMonster):
+ job = Job.SQUIDLARKIN
+
+
+class Mindflare(GenericMonster):
+ job = Job.MINDFLARE
+ difficulty = 6
+
+
+class Skeleton(GenericMonster):
+ job = Job.SKELETON
+
+
+class BoneSnatch(GenericMonster):
+ job = Job.BONE_SNATCH
+
+
+class LivingBone(GenericMonster):
+ job = Job.LIVING_BONE
+
+
+class Ghoul(GenericMonster):
+ job = Job.GHOUL
+
+
+class Gust(GenericMonster):
+ job = Job.GUST
+
+
+class Revnant(GenericMonster):
+ job = Job.REVNANT
+
+
+class Flotiball(GenericMonster):
+ job = Job.FLOTIBALL
+
+
+class Ahriman(GenericMonster):
+ job = Job.AHRIMAN
+
+
+class Plague(GenericMonster):
+ job = Job.PLAGUE
+
+
+class Juravis(GenericMonster):
+ job = Job.JURAVIS
+
+
+class SteelHawk(GenericMonster):
+ job = Job.STEEL_HAWK
+
+
+class Cocatoris(GenericMonster):
+ job = Job.COCATORIS
+
+
+class Uribo(GenericMonster):
+ job = Job.URIBO
+
+
+class Porky(GenericMonster):
+ job = Job.PORKY
+
+
+class Wildbow(GenericMonster):
+ job = Job.WILDBOW
+
+
+class Woodman(GenericMonster):
+ job = Job.WOODMAN
+
+
+class Trent(GenericMonster):
+ job = Job.TRENT
+
+
+class Taiju(GenericMonster):
+ job = Job.TAIJU
+
+
+class BullDemon(GenericMonster):
+ job = Job.BULL_DEMON
+
+
+class Minitaurus(GenericMonster):
+ job = Job.MINITAURUS
+
+
+class Sacred(GenericMonster):
+ job = Job.SACRED
+
+
+class Morbol(GenericMonster):
+ job = Job.MORBOL
+
+
+class Ochu(GenericMonster):
+ job = Job.OCHU
+
+
+class GreatMorbol(GenericMonster):
+ job = Job.GREAT_MORBOL
+
+
+class Behemoth(GenericMonster):
+ job = Job.BEHEMOTH
+ difficulty = 4
+
+
+class KingBehemoth(GenericMonster):
+ job = Job.KING_BEHEMOTH
+ difficulty = 6
+
+
+class DarkBehemoth(GenericMonster):
+ job = Job.DARK_BEHEMOTH
+ difficulty = 8
+
+
+class Dragon(GenericMonster):
+ job = Job.DRAGON
+
+
+class BlueDragon(GenericMonster):
+ job = Job.BLUE_DRAGON
+ difficulty = 4
+
+
+class RedDragon(GenericMonster):
+ job = Job.RED_DRAGON
+ difficulty = 4
+
+
+class Hyudra(GenericMonster):
+ job = Job.HYUDRA
+ difficulty = 4
+
+
+class Hydra(GenericMonster):
+ job = Job.HYDRA
+ difficulty = 6
+
+
+class Tiamat(GenericMonster):
+ job = Job.TIAMAT
+ difficulty = 8
+#endregion
+
+#region Special monsters
+class SpecialMonster(RandomizedUnit):
+ sprite_set = SpriteSet.MONSTER
+ gender = UnitGender.MONSTER
+ difficulty = 6
+
+
+class HolyDragon(SpecialMonster):
+ job = Job.HOLY_DRAGON
+ sprite_set = SpriteSet.HOLY_DRAGON
+
+
+class Byblos(SpecialMonster):
+ job = Job.BYBLOS
+
+
+class SteelGiant(SpecialMonster):
+ job = Job.STEEL_GIANT
+
+
+class Apanda(SpecialMonster):
+ job = Job.APANDA
+
+
+class ArchaicDemon(SpecialMonster):
+ job = Job.ARCHAIC_DEMON
+
+
+class UltimaDemon(SpecialMonster):
+ job = Job.ULTIMA_DEMON
+#endregion
+
+#region Lucavi
+class Velius(RandomizedUnit):
+ job = Job.VELIUS
+ sprite_set = SpriteSet.VELIUS
+ gender = UnitGender.MONSTER
+ difficulty = 8
+
+
+class VeliusWithKit(Velius):
+ primary = ActionAbility.VELIUS_FEAR
+ secondary = ActionAbility.WARLOCK_SUMMON
+
+
+class Zalera(RandomizedUnit):
+ job = Job.ZALERA
+ sprite_set = SpriteSet.ZALERA
+ gender = UnitGender.MALE
+ difficulty = 8
+
+
+class ZaleraWithKit(Zalera):
+ primary = ActionAbility.ZALERA_FEAR
+ secondary = ActionAbility.JA_MAGIC
+
+
+class Hashmalum(RandomizedUnit):
+ job = Job.HASHMALUM
+ sprite_set = SpriteSet.HASHMALUM
+ gender = UnitGender.MALE
+ difficulty = 10
+
+
+class HashmalumWithKit(Hashmalum):
+ primary = ActionAbility.HASHMALUM_FEAR
+ secondary = ActionAbility.DIMENSION_MAGIC
+
+
+class Queklain(RandomizedUnit):
+ job = Job.QUEKLAIN
+ sprite_set = SpriteSet.QUEKLAIN
+ gender = UnitGender.MALE
+ difficulty = 6
+
+
+class QueklainWithKit(Queklain):
+ primary = ActionAbility.QUEKLAIN_FEAR
+ secondary = ActionAbility.IMPURE
+
+
+class Adramelk(RandomizedUnit):
+ job = Job.ADRAMELK
+ sprite_set = SpriteSet.ADRAMELK
+ gender = UnitGender.MONSTER
+ difficulty = 8
+
+
+class AdramelkWithKit(Adramelk):
+ primary = ActionAbility.ADRAMELK_FEAR
+ secondary = ActionAbility.ADRAMELK_ALL_MAGIC
+
+
+class Elidibs(RandomizedUnit):
+ job = Job.ELIDIBS
+ sprite_set = SpriteSet.MONSTER
+ gender = UnitGender.MONSTER
+ difficulty = 12
+#endregion
+
+#region Story enemies
+class Wiegraf1Boss(WhiteKnightChapter1):
+ birthday_month = Month.AUGUST
+ birthday_day = 23
+ brave = 71
+ faith = 64
+ primary = ActionAbility.WIEGRAF_1_HOLY_SWORD
+ reaction = ReactionAbility.COUNTER
+ movement = MovementAbility.JUMP_PLUS_1
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 5
+
+class AlgusBoss(Algus):
+ birthday_month = Month.AUGUST
+ birthday_day = 29
+ brave = 32
+ faith = 67
+ job = Job.KNIGHT
+ reaction = ReactionAbility.AUTO_POTION
+ support = SupportAbility.EQUIP_CROSSBOW
+ movement = MovementAbility.MOVE_PLUS_1
+ unlocked_job = UnlockedJob.GEOMANCER
+ unlocked_job_level = 3
+
+class Gafgarion(DarkKnight):
+ job = Job.DARK_KNIGHT_ENEMY
+ birthday_month = Month.AUGUST
+ birthday_day = 26
+ brave = 61
+ faith = 67
+
+class Gafgarion1Boss(Gafgarion):
+ unlocked_job = UnlockedJob.GEOMANCER
+ unlocked_job_level = 5
+ right_hand = Items.BLOOD_SWORD
+
+class Gafgarion2Boss(Gafgarion):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 7
+ secondary = ActionAbility.NONE
+
+class QueklainBoss(QueklainWithKit):
+ birthday_month = Month.NOVEMBER
+ birthday_day = 15
+ brave = 70
+ faith = 70
+
+class Zalmo(HolyPriest):
+ birthday_month = Month.DECEMBER
+ birthday_day = 7
+ brave = 54
+ faith = 78
+ body = Items.WHITE_ROBE
+
+class Zalmo1Boss(Zalmo):
+ reaction = ReactionAbility.ARROW_GUARD
+ support = SupportAbility.HALF_OF_MP
+ movement = MovementAbility.MOVE_HP_UP
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 7
+
+class IzludeBoss(KnightBladeWithKit):
+ birthday_month = Month.MAY
+ birthday_day = 28
+ brave = 73
+ faith = 62
+ reaction = ReactionAbility.COUNTER
+ movement = MovementAbility.IGNORE_HEIGHT
+
+class Wiegraf2Boss(WhiteKnightWithCounter):
+ birthday_month = Month.AUGUST
+ birthday_day = 23
+ brave = 71
+ faith = 64
+ support = SupportAbility.TWO_HANDS
+ movement = MovementAbility.MOVE_PLUS_1
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 7
+
+class Malak(HellKnight):
+ birthday_month = Month.JUNE
+ birthday_day = 9
+ brave = 69
+ faith = 31
+ unlocked_job = UnlockedJob.BASE
+
+class Malak1Boss(Malak):
+ unlocked_job_level = 4
+
+class Malak2Boss(Malak):
+ unlocked_job_level = 3
+
+class Wiegraf3Boss(WhiteKnightWithCounter):
+ birthday_month = Month.AUGUST
+ birthday_day = 23
+ brave = 71
+ faith = 64
+ primary = ActionAbility.WIEGRAF_3_HOLY_SWORD
+ secondary = ActionAbility.WIEGRAF_3_PUNCH_SKILL
+ support = SupportAbility.MAINTENANCE
+ movement = MovementAbility.MOVE_PLUS_1
+
+class VeliusBoss(VeliusWithKit):
+ birthday_month = Month.AUGUST
+ birthday_day = 23
+ brave = 70
+ faith = 70
+
+class Elmdor(ArcKnightElmdor):
+ birthday_month = Month.MAY
+ birthday_day = 21
+ brave = 70
+ faith = 70
+
+
+class Elmdor1Boss(Elmdor):
+ primary = ActionAbility.SWORD_SPIRIT_1
+ secondary = ActionAbility.NONE
+ reaction = ReactionAbility.HAMEDO
+ support = SupportAbility.TWO_HANDS
+ movement = MovementAbility.IGNORE_HEIGHT
+
+class Celia(AssassinCelia):
+ birthday_month = Month.AUGUST
+ birthday_day = 30
+ brave = 65
+ faith = 70
+ primary = ActionAbility.CELIA_USE_HAND
+
+class Lede(AssassinLede):
+ birthday_month = Month.DECEMBER
+ birthday_day = 13
+ brave = 65
+ faith = 70
+ primary = ActionAbility.LEDE_USE_HAND
+
+class Celia1Boss(Celia):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 8
+ accessory = Items.CACHUSHA
+
+class Lede1Boss(Lede):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 8
+ accessory = Items.BARETTE
+
+class MeliadoulBoss(DivineKnightMeliadoul):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 4
+ birthday_month = Month.DECEMBER
+ birthday_day = 24
+ brave = 67
+ faith = 68
+ accessory = Items.CHANTAGE
+
+class Zalmo2Boss(Zalmo):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 8
+
+class Balk(EngineerBalk):
+ birthday_month = Month.DECEMBER
+ birthday_day = 6
+ brave = 64
+ faith = 62
+
+class Balk1Boss(Balk):
+ right_hand = Items.BLAZE_GUN
+ difficulty = 12
+
+class Celia2Boss(Celia):
+ unlocked_job = UnlockedJob.SUMMONER
+ unlocked_job_level = 3
+
+class Lede2Boss(Lede):
+ unlocked_job = UnlockedJob.SUMMONER
+ unlocked_job_level = 3
+
+class Celia3Boss(Celia2Boss):
+ secondary = ActionAbility.CELIA_THROW
+
+class Lede3Boss(Lede2Boss):
+ secondary = ActionAbility.LEDE_THROW
+
+class Elmdor2Boss(Elmdor):
+ primary = ActionAbility.SWORD_SPIRIT_1
+ secondary = ActionAbility.BLOOD_SUCK
+ reaction = ReactionAbility.BLADE_GRASP
+ support = SupportAbility.MARTIAL_ARTS
+ movement = MovementAbility.TELEPORT_2
+ right_hand = Items.MASAMUNE
+ left_hand = Items.GENJI_SHIELD
+ head = Items.GENJI_HELMET
+ body = Items.GENJI_ARMOR
+ accessory = Items.GENJI_GAUNTLET
+ difficulty = 12
+
+class ZaleraBoss(ZaleraWithKit):
+ birthday_month = Month.MAY
+ birthday_day = 21
+ brave = 70
+ faith = 70
+
+class DycedargBoss(LuneKnight):
+ birthday_month = Month.OCTOBER
+ birthday_day = 24
+ brave = 66
+ faith = 77
+ primary = ActionAbility.SWORD_SKILL
+ secondary = ActionAbility.DYCEDARG_ALL_MAGIC
+ reaction = ReactionAbility.CATCH
+ support = SupportAbility.DEFEND
+ movement = MovementAbility.MOVE_PLUS_1
+ difficulty = 6
+
+class AdramelkBoss(AdramelkWithKit):
+ birthday_month = Month.OCTOBER
+ birthday_day = 24
+ brave = 70
+ faith = 70
+
+class VormavBoss(DivineKnightVormav):
+ birthday_month = Month.JULY
+ birthday_day = 23
+ brave = 65
+ faith = 70
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 4
+ secondary = ActionAbility.NONE
+ reaction = ReactionAbility.COUNTER
+ support = SupportAbility.DEFENSE_UP
+ movement = MovementAbility.MOVE_PLUS_1
+
+class Rofel(DivineKnightRofel):
+ birthday_month = Month.DECEMBER
+ birthday_day = 23
+ brave = 60
+ faith = 68
+
+class Rofel1Boss(Rofel):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 4
+ secondary = ActionAbility.NONE
+ reaction = ReactionAbility.COUNTER_FLOOD
+ support = SupportAbility.DEFENSE_UP
+ movement = MovementAbility.JUMP_PLUS_1
+
+class Kletian(Sorcerer):
+ birthday_month = Month.JUNE
+ birthday_day = 6
+ brave = 51
+ faith = 81
+
+class Kletian1Boss(Kletian):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 6
+ reaction = ReactionAbility.COUNTER_MAGIC
+ support = SupportAbility.MAGIC_DEFEND_UP
+ movement = MovementAbility.IGNORE_HEIGHT
+
+class ZalbagBoss(ArcKnightZombie):
+ birthday_month = Month.JUNE
+ birthday_day = 30
+ brave = 33
+ faith = 77
+ primary = ActionAbility.DESTROY_SWORD
+ secondary = ActionAbility.BLOOD_SUCK
+ reaction = ReactionAbility.SPEED_SAVE
+ support = SupportAbility.DEFENSE_UP
+ movement = MovementAbility.MOVE_HP_UP
+
+class Rofel2Boss(Rofel):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 8
+ secondary = ActionAbility.ROFEL_ALL_MAGIC
+ reaction = ReactionAbility.WEAPON_GUARD
+ support = SupportAbility.DEFENSE_UP
+ movement = MovementAbility.IGNORE_HEIGHT
+ right_hand = Items.SAVE_THE_QUEEN
+ difficulty = 12
+
+class Kletian2Boss(Kletian):
+ unlocked_job = UnlockedJob.BASE
+ unlocked_job_level = 6
+ secondary = ActionAbility.NONE
+ reaction = ReactionAbility.MA_SAVE
+ support = SupportAbility.MAGIC_DEFEND_UP
+ movement = MovementAbility.FLY
+ right_hand = Items.MACE_OF_ZEUS
+ difficulty = 10
+
+class Balk2Boss(Balk):
+ primary = ActionAbility.SNIPE
+ secondary = ActionAbility.NONE
+ reaction = ReactionAbility.COUNTER
+ support = SupportAbility.MARTIAL_ARTS
+ movement = MovementAbility.MOVE_HP_UP
+ right_hand = Items.BLAST_GUN
+ head = Items.THIEF_HAT
+ difficulty = 12
+
+class Hashmalum(HashmalumWithKit):
+ birthday_month = Month.JULY
+ birthday_day = 23
+ brave = 70
+ faith = 70
+
+class Altima1Boss(Altima1):
+ birthday_month = Month.SEPTEMBER
+ birthday_day = 11
+ brave = 70
+ faith = 70
+ reaction = ReactionAbility.ABSORB_USED_MP
+ support = SupportAbility.NONE
+ movement = MovementAbility.NONE
+
+
+class Altima2Boss(Altima2):
+ birthday_month = Month.SEPTEMBER
+ birthday_day = 11
+ brave = 60
+ faith = 70
+ reaction = ReactionAbility.NONE
+ support = SupportAbility.NONE
+ movement = MovementAbility.NONE
+
+
+
+#endregion
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/Sector.py b/worlds/fftii/enemyrando/Sector.py
new file mode 100644
index 000000000000..6cb35a58ba9c
--- /dev/null
+++ b/worlds/fftii/enemyrando/Sector.py
@@ -0,0 +1,157 @@
+import os
+
+from EventIDs import events
+from worlds.fftii.enemyrando.Job import all_jobs, Job
+from worlds.fftii.enemyrando.SourceUnit import SourceUnit
+from worlds.fftii.enemyrando.SpriteSet import SpriteSet
+from worlds.fftii.enemyrando.Unit import Unit
+
+
+class Sector:
+ header_size = 0x18
+ data_size = 0x800
+ error_size = 0x118
+ sector_size = header_size + data_size + error_size
+ header: bytearray
+ data: bytearray
+ error: bytearray
+ all_data: bytearray
+
+ def __init__(self, new_data):
+ self.header = new_data[:self.header_size]
+ self.data = bytearray(new_data[self.header_size:self.header_size + self.data_size])
+ self.error = new_data[self.header_size + self.data_size:]
+ self.all_data = new_data
+ assert len(self.header) == self.header_size
+ assert len(self.data) == self.data_size
+ assert len(self.error) == self.error_size
+
+
+
+class ENTDEntry:
+ name: str
+ location: int
+ index: int
+ unit_count = 16
+ unit_length = 40
+ total_length = unit_count * unit_length
+ units: list[bytearray] = []
+ unit_datas: list[Unit] = []
+ all_data: bytearray
+
+ def __init__(self, entd_data: bytearray, name: str, location: int):
+ self.units = []
+ self.unit_datas = []
+ self.all_data = bytearray()
+ self.name = name
+ self.location = location
+ for i in range(self.unit_count):
+ self.units.append(entd_data[i * self.unit_length:(i + 1) * self.unit_length])
+ assert len(self.units) == 16, len(self.units)
+
+ def __repr__(self):
+ return self.name
+
+ def apply_data(self):
+ for unit_data in self.unit_datas:
+ self.all_data.extend(unit_data.unit_data)
+
+iso_filename = "Final Fantasy Tactics (USA).bin"
+entd_size = 80 * 1024
+entd_sector_count = 40
+event_count = 128
+entd_start = 0x0875FD30
+
+with open(os.path.join("..", "..", "..", iso_filename), "rb") as file:
+ iso_data = file.read()
+ total_entd_length = entd_sector_count * Sector.sector_size
+ entd_list: list[bytearray] = []
+ all_entd_sectors: list[Sector] = list()
+ for i in range(4):
+ start = entd_start + (i * total_entd_length)
+ entd = iso_data[start:start + total_entd_length]
+ entd_sectors = []
+ for j in range(entd_sector_count):
+ new_sector = Sector(entd[j * Sector.sector_size:(j + 1) * Sector.sector_size])
+ entd_sectors.append(new_sector)
+ all_entd_sectors.append(new_sector)
+ entd_data = bytearray()
+ for sector in entd_sectors:
+ entd_data.extend(sector.data)
+ entd_list.append(entd_data)
+ full_entd = bytearray()
+ for entd in entd_list:
+ full_entd.extend(entd)
+ print(hex(len(full_entd)))
+ used_units = []
+ entd_entries: list[ENTDEntry] = list()
+ for i in range(0x1D5):
+ if i in events.keys():
+ entd_data = full_entd[i * ENTDEntry.total_length:(i + 1) * ENTDEntry.total_length]
+ new_entd_entry = ENTDEntry(entd_data, events[i], i * ENTDEntry.total_length)
+ new_entd_entry.index = i
+ print(f"{str(new_entd_entry).replace(" ", "")}.name = '{str(new_entd_entry)}'")
+ for unit in new_entd_entry.units:
+ if unit[0] > 0 and unit[1] > 0:
+ new_unit = Unit(unit)
+ if new_unit.job != 0:
+ used_units.append(new_unit.job)
+ else:
+ new_unit = Unit(unit)
+ new_entd_entry.unit_datas.append(new_unit)
+ used_sprite_sheets = set()
+ used_source_units = set()
+ for unit in new_entd_entry.unit_datas:
+ if unit.sprite_set > 0:
+ source_unit = SourceUnit(SpriteSet(unit.sprite_set), Job(unit.job), unit.gender)
+ used_source_units.add(source_unit)
+ included = False
+ for source in used_sprite_sheets:
+ if source == source_unit:
+ included = True
+ if not included:
+ used_sprite_sheets.add(source_unit)
+ for source in used_source_units:
+ pass
+ #print(f"#Used sprite sheets: {len(used_sprite_sheets)}")
+ new_entd_entry.apply_data()
+ entd_entries.append(new_entd_entry)
+ exit()
+ new_full_entd: bytearray = full_entd.copy()
+ for entd_entry in entd_entries:
+ start_location: int = entd_entry.location
+ end_location: int = entd_entry.location + ENTDEntry.total_length
+ new_full_entd[start_location:end_location] = entd_entry.all_data
+ new_all_entd_sectors: list[Sector] = list()
+ for sector in all_entd_sectors:
+ new_all_entd_sectors.append(Sector(sector.all_data))
+ for i in range(len(new_full_entd)):
+ #assert new_full_entd[i] == full_entd[i], (i, new_full_entd[i], full_entd[i])
+ sector_number: int = i // 2048
+ new_all_entd_sectors[sector_number].data[i % 2048] = new_full_entd[i]
+ for i in range(len(all_entd_sectors)):
+ for j in range(len(all_entd_sectors[i].data)):
+ pass
+ #assert all_entd_sectors[i].data[j] == new_all_entd_sectors[i].data[j]
+ new_all_data: bytearray = bytearray()
+ new_all_data.extend(new_all_entd_sectors[i].header)
+ new_all_data.extend(new_all_entd_sectors[i].data)
+ new_all_data.extend(new_all_entd_sectors[i].error)
+ new_all_entd_sectors[i].all_data = new_all_data
+ new_iso_data = bytearray(iso_data)
+ new_sector_data: bytearray = bytearray()
+ for sector in new_all_entd_sectors:
+ new_sector_data.extend(sector.all_data)
+ new_iso_data[entd_start:entd_start + len(new_sector_data)] = new_sector_data
+ for i in range(len(new_iso_data)):
+ pass
+ #assert iso_data[i] == new_iso_data[i]
+ with open("test.bin", "wb") as file2:
+ pass
+ #file2.write(new_iso_data)
+ print("\n\n======\n\n")
+ for unit_id in all_jobs.keys():
+ if unit_id not in used_units:
+ print(all_jobs[unit_id])
+
+ pass
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/SourceUnit.py b/worlds/fftii/enemyrando/SourceUnit.py
new file mode 100644
index 000000000000..919d43dbc5fe
--- /dev/null
+++ b/worlds/fftii/enemyrando/SourceUnit.py
@@ -0,0 +1,33 @@
+from worlds.fftii.enemyrando.Job import Job, monster_families
+from worlds.fftii.enemyrando.SpriteSet import SpriteSet
+from worlds.fftii.enemyrando.Unit import UnitGender
+
+
+class SourceUnit:
+ sprite_set: SpriteSet
+ job: Job
+ gender: UnitGender
+
+ def __init__(self, sprite_set: SpriteSet, job: Job, gender: UnitGender):
+ self.sprite_set = sprite_set
+ self.job = job
+ self.gender = gender
+
+ def __repr__(self):
+ return f"Sprite: {self.sprite_set.name}, Job: {self.job.name}, Gender: {self.gender.name}"
+
+ def __eq__(self, other):
+ if self.sprite_set != other.sprite_set:
+ return False
+ if self.job != other.job:
+ if self.job in monster_families.keys():
+ if other.job not in monster_families[self.job]:
+ return False
+ else:
+ return False
+ if self.gender != other.gender:
+ return False
+ return True
+
+ def __hash__(self):
+ return hash((self.sprite_set.value, self.job.value, self.gender.value))
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/SpriteSet.py b/worlds/fftii/enemyrando/SpriteSet.py
new file mode 100644
index 000000000000..ad17932c323f
--- /dev/null
+++ b/worlds/fftii/enemyrando/SpriteSet.py
@@ -0,0 +1,141 @@
+from enum import Enum, unique
+
+
+@unique
+class SpriteSet(Enum):
+ RAMZA_C1 = 0x01
+ RAMZA_C23 = 0x02
+ RAMZA_C4 = 0x03
+ DELITA_C1 = 0x04
+ DELITA_C2 = 0x05
+ DELITA_C4 = 0x06
+ ALGUS = 0x07
+ ZALBAG = 0x08
+ DYCEDARG = 0x09
+ OVELIA = 0x0C
+ ORLANDU = 0x0D
+ REIS = 0x0F
+ ZALMO = 0x10
+ GAFGARION_ENEMY = 0x11
+ MALAK_DEAD = 0x12
+ ALMA_GOA = 0x14
+ OLAN = 0x15
+ MUSTADIO = 0x16
+ GAFGARION = 0x17
+ DRACLAU = 0x18
+ RAFA_GUEST = 0x19
+ MALAK = 0x1A
+ ELMDOR = 0x1B
+ TETA = 0x1C
+ AGRIAS = 0x1E
+ BEOWULF = 0x1F
+ WIEGRAF1 = 0x20
+ MUSTADIO_GUEST = 0x22
+ RUDVICH = 0x23
+ VORMAV = 0x24
+ ROFEL = 0x25
+ IZLUDE = 0x26
+ KLETIAN = 0x27
+ WIEGRAF2 = 0x28
+ RAFA = 0x29
+ MELIADOUL = 0x2A
+ BALK = 0x2B
+ ALMA_DEAD = 0x2C
+ CELIA = 0x2D
+ LEDE = 0x2E
+ MELIADOUL_ENEMY = 0x2F
+ ALMA = 0x30
+ AJORA = 0x31
+ CLOUD = 0x32
+ ZALBAG_ZOMBIE = 0x33
+ AGRIAS_GUEST = 0x34
+ VELIUS = 0x3C
+ UNDEAD_KNIGHT = 0x3D
+ ZALERA = 0x3E
+ UNDEAD_ARCHER = 0x3F
+ HASHMALUM = 0x40
+ ALTIMA_1 = 0x41
+ UNDEAD_WIZARD = 0x42
+ QUEKLAIN = 0x43
+ UNDEAD_TIMEMAGE = 0x44
+ ADRAMELK = 0x45
+ UNDEAD_ORACLE = 0x46
+ UNDEAD_SUMMONER = 0x47
+ HOLY_DRAGON = 0x48
+ ALTIMA_2 = 0x49
+ GENERIC_MALE = 0x80
+ GENERIC_FEMALE = 0x81
+ MONSTER = 0x82
+
+sprite_sets = {
+ 0x01: "Ramza",
+ 0x02: "Ramza",
+ 0x03: "Ramza",
+ 0x04: "Delita",
+ 0x05: "Delita",
+ 0x06: "Delita",
+ 0x07: "Algus",
+ 0x08: "Zalbag",
+ 0x09: "Dycedarg",
+ 0x0A: "Larg",
+ 0x0B: "Goltana",
+ 0x0C: "Ovelia",
+ 0x0D: "Orlandu",
+ 0x0E: "High Priest",
+ 0x0F: "Reis",
+ 0x10: "Zalmo",
+ 0x11: "Gafgarion",
+ 0x12: "Malak",
+ 0x13: "Simon",
+ 0x14: "Alma",
+ 0x15: "Olan",
+ 0x16: "Mustadio",
+ 0x17: "Gafgarion",
+ 0x18: "Draclau",
+ 0x19: "Rafa",
+ 0x1A: "Malak",
+ 0x1B: "Elmdor",
+ 0x1C: "Teta",
+ 0x1D: "Barinten",
+ 0x1E: "Agrias",
+ 0x1F: "Beowulf",
+ 0x20: "Wiegraf",
+ 0x21: "Balmafula",
+ 0x22: "Mustadio",
+ 0x23: "Rudvich",
+ 0x24: "Vormav",
+ 0x25: "Rofel",
+ 0x26: "Izlude",
+ 0x27: "Kletian",
+ 0x28: "Wiegraf",
+ 0x29: "Rafa",
+ 0x2A: "Meliadoul",
+ 0x2B: "Balk",
+ 0x2C: "Alma",
+ 0x2D: "Celia",
+ 0x2E: "Lede",
+ 0x2F: "Meliadoul",
+ 0x30: "Alma",
+ 0x31: "Ajora",
+ 0x32: "Cloud",
+ 0x33: "Zalbag",
+ 0x34: "Agrias",
+
+ 0x3C: "Velius",
+ 0x3D: "Undead Knight",
+ 0x3E: "Zalera",
+
+ 0x40: "Hashmalum",
+ 0x41: "Altima",
+
+ 0x43: "Queklain",
+
+ 0x45: "Adramelk",
+
+ 0x48: "Holy Dragon",
+ 0x49: "Altima",
+
+ 0x80: "Generic Male",
+ 0x81: "Generic Female",
+ 0x82: "Monster"
+}
\ No newline at end of file
diff --git a/worlds/fftii/enemyrando/Unit.py b/worlds/fftii/enemyrando/Unit.py
new file mode 100644
index 000000000000..d5cf1110d305
--- /dev/null
+++ b/worlds/fftii/enemyrando/Unit.py
@@ -0,0 +1,184 @@
+from copy import copy
+from enum import Enum
+
+from worlds.fftii.enemyrando.Job import all_jobs, UnlockedJob
+from worlds.fftii.enemyrando.SpriteSet import sprite_sets
+
+
+class UnitGender(Enum):
+ MALE = "Male"
+ FEMALE = "Female"
+ MONSTER = "Monster"
+ NONE = "None"
+
+class UnitTeam(Enum):
+ BLUE = 0x40
+ RED = 1
+
+class Unit:
+ unit_data: bytearray
+
+ job_offset = 0x0A
+ job: int
+ job_name: str
+
+ sprite_set_offset = 0x00
+ sprite_set: int
+ sprite_set_name: str
+
+ flags_offset = 0x01
+ flags: int
+ gender: UnitGender
+
+ flags2_offset = 0x18
+ flags2: int
+ team: UnitTeam
+
+ birthday_month_offset = 0x04
+ birthday_month: int
+
+ birthday_day_offset = 0x05
+ birthday_day: int
+
+ brave_offset = 0x06
+ brave: int
+
+ faith_offset = 0x07
+ faith: int
+
+ unlocked_job_offset = 0x08
+ unlocked_job: int
+ unlocked_job_level_offset = 0x09
+ unlocked_job_level: int
+
+ primary_offset = 0x1D
+ primary: int
+
+ secondary_offset = 0x0B
+ secondary: int
+
+ reaction_offset = 0x0C
+ reaction: int
+
+ support_offset = 0x0E
+ support: int
+
+ movement_offset = 0x10
+ movement: int
+
+ head_offset = 0x12
+ head: int
+
+ body_offset = 0x13
+ body: int
+
+ accessory_offset = 0x14
+ accessory: int
+
+ right_hand_offset = 0x15
+ right_hand: int
+
+ left_hand_offset = 0x16
+ left_hand: int
+
+ def __init__(self, unit_data):
+ self.unit_data = copy(unit_data)
+ self.job = unit_data[self.job_offset]
+ self.sprite_set = unit_data[self.sprite_set_offset]
+ self.flags = unit_data[self.flags_offset]
+ self.flags2 = unit_data[self.flags2_offset]
+ self.birthday_month = unit_data[self.birthday_month_offset]
+ self.birthday_day = unit_data[self.birthday_day_offset]
+ self.brave = unit_data[self.brave_offset]
+ self.faith = unit_data[self.faith_offset]
+ self.unlocked_job = unit_data[self.unlocked_job_offset]
+ self.unlocked_job_level = unit_data[self.unlocked_job_level_offset]
+ self.primary = unit_data[self.primary_offset]
+ self.secondary = unit_data[self.secondary_offset]
+ self.reaction = int.from_bytes(unit_data[self.reaction_offset:self.reaction_offset + 2])
+ self.support = int.from_bytes(unit_data[self.support_offset:self.support_offset + 2])
+ self.movement = int.from_bytes(unit_data[self.movement_offset:self.movement_offset + 2])
+ self.head = unit_data[self.head_offset]
+ self.body = unit_data[self.body_offset]
+ self.accessory = unit_data[self.accessory_offset]
+ self.right_hand = unit_data[self.right_hand_offset]
+ self.left_hand = unit_data[self.left_hand_offset]
+ if self.job in all_jobs.keys():
+ self.job_name = all_jobs[self.job]
+ else:
+ if self.job == 0:
+ self.job_name = "None"
+ else:
+ self.job_name = f"Unknown Job {str(hex(self.job))}"
+ if self.sprite_set in sprite_sets.keys():
+ self.sprite_set_name = sprite_sets[self.sprite_set]
+ else:
+ if self.job == 0:
+ self.sprite_set_name = "None"
+ else:
+ self.sprite_set_name = "Unknown sprite set"
+ self.gender = self.get_gender()
+ self.team = self.get_team()
+ self.apply_unit_data()
+ for i in range(len(self.unit_data)):
+ assert self.unit_data[i] == unit_data[i], (hex(i), self.unit_data[i], unit_data[i])
+ assert self.unit_data == unit_data
+
+ def get_gender(self):
+ male = self.flags & 0x80
+ female = self.flags & 0x40
+ monster = self.flags & 0x20
+ if male > 0:
+ assert female == 0
+ assert monster == 0
+ return UnitGender.MALE
+ if female > 0:
+ assert male == 0
+ assert monster == 0
+ return UnitGender.FEMALE
+ if monster > 0:
+ assert male == 0
+ assert female == 0
+ return UnitGender.MONSTER
+ return UnitGender.NONE
+
+ def get_team(self):
+ team_bits = (self.flags2 & 0x30) >> 4
+ if team_bits == 0:
+ return UnitTeam.BLUE
+ if team_bits == 1:
+ return UnitTeam.RED
+ raise ValueError(str(bin(self.flags2)))
+
+ def apply_unit_data(self):
+ self.unit_data[self.job_offset] = self.job
+ self.unit_data[self.sprite_set_offset] = self.sprite_set
+ self.unit_data[self.flags_offset] = self.flags
+ self.unit_data[self.flags2_offset] = self.flags2
+ self.unit_data[self.unlocked_job_offset] = self.unlocked_job
+ self.unit_data[self.unlocked_job_level_offset] = self.unlocked_job_level
+ self.unit_data[self.birthday_month_offset] = self.birthday_month
+ self.unit_data[self.birthday_day_offset] = self.birthday_day
+ self.unit_data[self.brave_offset] = self.brave
+ self.unit_data[self.faith_offset] = self.faith
+ self.unit_data[self.primary_offset] = self.primary
+ self.unit_data[self.secondary_offset] = self.secondary
+ self.unit_data[self.reaction_offset] = self.reaction // 256
+ self.unit_data[self.reaction_offset + 1] = self.reaction % 256
+ self.unit_data[self.support_offset] = self.support // 256
+ self.unit_data[self.support_offset + 1] = self.support % 256
+ self.unit_data[self.movement_offset] = self.movement // 256
+ self.unit_data[self.movement_offset + 1] = self.movement % 256
+ self.unit_data[self.head_offset] = self.head
+ self.unit_data[self.body_offset] = self.body
+ self.unit_data[self.accessory_offset] = self.accessory
+ self.unit_data[self.right_hand_offset] = self.right_hand
+ self.unit_data[self.left_hand_offset] = self.left_hand
+
+
+ def __repr__(self):
+ try:
+ return_string = f"{self.job_name} ({UnlockedJob(self.unlocked_job).name} {self.unlocked_job_level})"
+ except:
+ return_string = self.job_name
+ return return_string
diff --git a/worlds/fftii/enemyrando/__init__.py b/worlds/fftii/enemyrando/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/worlds/fftii/fftiiapjobs.bsdiff4 b/worlds/fftii/fftiiapjobs.bsdiff4
new file mode 100644
index 000000000000..c128560ab26b
Binary files /dev/null and b/worlds/fftii/fftiiapjobs.bsdiff4 differ
diff --git a/worlds/fftii/fftiivanillajobs.bsdiff4 b/worlds/fftii/fftiivanillajobs.bsdiff4
new file mode 100644
index 000000000000..b1bae98c5483
Binary files /dev/null and b/worlds/fftii/fftiivanillajobs.bsdiff4 differ
diff --git a/worlds/fftii/test/__init__.py b/worlds/fftii/test/__init__.py
new file mode 100644
index 000000000000..2a85a983deb3
--- /dev/null
+++ b/worlds/fftii/test/__init__.py
@@ -0,0 +1,7 @@
+# The __init__.py file of the test directory should be empty.
+# (Before you say it: Comments are fine, smart*ss ;D)
+
+# You'll want to start with reading bases.py.
+
+# If you want to read more about tests, there is also the "Tests" section of the World API document:
+# https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#tests
diff --git a/worlds/fftii/test/bases.py b/worlds/fftii/test/bases.py
new file mode 100644
index 000000000000..f2895b46f516
--- /dev/null
+++ b/worlds/fftii/test/bases.py
@@ -0,0 +1,25 @@
+from test.bases import WorldTestBase
+from .. import FinalFantasyTacticsIvaliceIslandWorld
+
+# Tests are a big topic.
+# The testing API and the core code in general empower you to test all kinds of complicated custom behavior.
+# However, for APQuest, we'll stick to some of the more basic tests.
+
+
+# Most of your testing will probably be done using the generic WorldTestBase.
+# WorldTestBase is a class that performs a set of generic tests on your world using a given set of options.
+# It also enables you to write custom tests with a slew of generic helper functions.
+# The first thing you'll want to do is subclass it. You'll want to override "game" And "world" like this.
+class FFTIITestBase(WorldTestBase):
+ game = "Final Fantasy Tactics Ivalice Island"
+ world: FinalFantasyTacticsIvaliceIslandWorld
+
+
+# The actual tests you write should be in files whose names start with "test_".
+# Ideally, you should group similar tests together in one file, where each file has some overarching significance.
+
+# The best order to read these tests in is:
+# 1. test_easy_mode.py
+# 2. test_hard_mode.py
+# 3. test_extra_starting_chest.py
+# 4. test_hammer.py
diff --git a/worlds/fftii/test/test_items.py b/worlds/fftii/test/test_items.py
new file mode 100644
index 000000000000..798615845488
--- /dev/null
+++ b/worlds/fftii/test/test_items.py
@@ -0,0 +1,239 @@
+from Fill import distribute_items_restrictive
+from .bases import FFTIITestBase
+
+from ..data.locations import (story_zodiac_stone_locations, sidequest_zodiac_stone_locations,
+ altima_only_story_zodiac_stone_locations)
+from ..data.items import zodiac_stone_names, earned_job_names, shop_item_names, rare_item_names, gil_item_names, \
+ jp_item_names
+
+
+class TestZodiacStonesCorrectCount(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 1,
+ "zodiac_stones_in_pool": 13,
+ "sidequest_battles": "true"
+ }
+
+ run_default_tests = False
+
+ def test_correct_stone_count(self):
+ stones_in_pool = self.get_items_by_name(zodiac_stone_names)
+ self.assertEqual(len(stones_in_pool), 13, stones_in_pool)
+
+class TestZodiacStonesCorrectAdjustment(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 1,
+ "zodiac_stones_in_pool": 6,
+ "zodiac_stones_required": 12,
+ "sidequest_battles": "true"
+ }
+
+ def test_correct_stone_count(self):
+ stones_in_pool = self.get_items_by_name(zodiac_stone_names)
+ self.assertEqual(len(stones_in_pool), 12, stones_in_pool)
+
+class TestZodiacStonesCorrectAdjustmentForVanillaStones(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 11,
+ "zodiac_stones_required": 13,
+ "sidequest_battles": "false",
+ "final_battles": 0
+ }
+
+ def test_correct_stone_count(self):
+ self.world_setup()
+ distribute_items_restrictive(self.multiworld)
+ all_locations = self.multiworld.get_locations(self.player)
+ zodiac_stone_locations = [location for location in all_locations if location.item.name in zodiac_stone_names]
+ self.assertEqual(len(zodiac_stone_locations), len(story_zodiac_stone_locations), zodiac_stone_locations)
+
+class TestZodiacStonesCorrectAdjustmentForVanillaStonesForSidequestsOn(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 11,
+ "zodiac_stones_required": 13,
+ "sidequest_battles": "true",
+ "final_battles": 0
+ }
+
+ def test_correct_stone_count(self):
+ self.world_setup()
+ distribute_items_restrictive(self.multiworld)
+ all_locations = self.multiworld.get_locations(self.player)
+ zodiac_stone_locations = [location for location in all_locations if location.item.name in zodiac_stone_names]
+ self.assertEqual(
+ len(zodiac_stone_locations),
+ len(story_zodiac_stone_locations) + len(sidequest_zodiac_stone_locations),
+ zodiac_stone_locations)
+
+
+class TestZodiacStonesCorrectAdjustmentForVanillaStonesForSidequestsAndAltimaOnlyOn(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 11,
+ "zodiac_stones_required": 13,
+ "sidequest_battles": "true",
+ "final_battles": 1
+ }
+
+ def test_correct_stone_count(self):
+ self.world_setup()
+ distribute_items_restrictive(self.multiworld)
+ all_locations = self.multiworld.get_locations(self.player)
+ zodiac_stone_locations = [location for location in all_locations if location.item.name in zodiac_stone_names]
+ self.assertEqual(
+ len(zodiac_stone_locations),
+ len(story_zodiac_stone_locations) + len(sidequest_zodiac_stone_locations) + len(altima_only_story_zodiac_stone_locations),
+ zodiac_stone_locations)
+
+
+class TestZodiacStonesCorrectAdjustmentForVanillaStonesForAltimaOnly(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 11,
+ "zodiac_stones_required": 13,
+ "sidequest_battles": "false",
+ "final_battles": 1
+ }
+
+ def test_correct_stone_count(self):
+ self.world_setup()
+ distribute_items_restrictive(self.multiworld)
+ all_locations = self.multiworld.get_locations(self.player)
+ zodiac_stone_locations = [location for location in all_locations if location.item.name in zodiac_stone_names]
+ self.assertEqual(
+ len(zodiac_stone_locations),
+ len(story_zodiac_stone_locations) + len(altima_only_story_zodiac_stone_locations),
+ zodiac_stone_locations)
+
+class TestJobsOn(FFTIITestBase):
+ options = {
+ "job_unlocks": "true"
+ }
+
+ def test_jobs_in_pool(self):
+ jobs_in_pool = self.get_items_by_name(earned_job_names)
+ self.assertEqual(len(jobs_in_pool), 19, jobs_in_pool)
+
+ def test_job_requirements(self):
+ with self.subTest("Starter jobs"):
+ # Because we start with Squire precollected, we can reach Knight/Archer from the start.
+ self.assertTrue(self.can_reach_location("Squire Unlock"))
+ self.assertTrue(self.can_reach_location("Chemist Unlock"))
+ self.assertTrue(self.can_reach_location("Knight Unlock"))
+ self.assertTrue(self.can_reach_location("Archer Unlock"))
+ with self.subTest("Knight unlocks"):
+ self.assertAccessDependency(
+ ["Monk Unlock", "Samurai Unlock"],
+ [["Knight"]],
+ only_check_listed=True)
+ with self.subTest("Archer unlocks"):
+ self.assertAccessDependency(
+ ["Thief Unlock", "Ninja Unlock"],
+ [["Archer"]],
+ only_check_listed=True)
+ with self.subTest("Monk unlocks"):
+ self.assertAccessDependency(
+ ["Geomancer Unlock", "Samurai Unlock"],
+ [["Monk"]],
+ only_check_listed=True)
+ with self.subTest("Thief unlocks"):
+ self.assertAccessDependency(
+ ["Lancer Unlock", "Ninja Unlock"],
+ [["Thief"]],
+ only_check_listed=True)
+ with self.subTest("Lancer unlocks"):
+ self.assertAccessDependency(
+ ["Dancer Unlock", "Samurai Unlock", "Mime Unlock"],
+ [["Lancer"]],
+ only_check_listed=True)
+ with self.subTest("Geomancer unlocks"):
+ self.assertAccessDependency(
+ ["Dancer Unlock", "Ninja Unlock", "Mime Unlock"],
+ [["Geomancer"]],
+ only_check_listed=True)
+ with self.subTest("Chemist unlocks"):
+ self.assertAccessDependency(
+ ["Priest Unlock", "Wizard Unlock"],
+ [["Chemist"]],
+ only_check_listed=True)
+ with self.subTest("Priest unlocks"):
+ self.assertAccessDependency(
+ ["Oracle Unlock", "Calculator Unlock"],
+ [["Priest"]],
+ only_check_listed=True)
+ with self.subTest("Wizard unlocks"):
+ self.assertAccessDependency(
+ ["Time Mage Unlock", "Calculator Unlock"],
+ [["Wizard"]],
+ only_check_listed=True)
+ with self.subTest("Oracle unlocks"):
+ self.assertAccessDependency(
+ ["Mediator Unlock", "Calculator Unlock"],
+ [["Oracle"]],
+ only_check_listed=True)
+ with self.subTest("Time Mage unlocks"):
+ self.assertAccessDependency(
+ ["Summoner Unlock", "Calculator Unlock"],
+ [["Time Mage"]],
+ only_check_listed=True)
+ with self.subTest("Mediator unlocks"):
+ self.assertAccessDependency(
+ ["Bard Unlock", "Mime Unlock"],
+ [["Mediator"]],
+ only_check_listed=True)
+ with self.subTest("Summoner unlocks"):
+ self.assertAccessDependency(
+ ["Bard Unlock", "Mime Unlock"],
+ [["Summoner"]],
+ only_check_listed=True)
+
+class TestJobsOff(FFTIITestBase):
+ options = {
+ "job_unlocks": "false"
+ }
+
+ def test_jobs_in_pool(self):
+ jobs_in_pool = self.get_items_by_name(earned_job_names)
+ self.assertEqual(len(jobs_in_pool), 0, jobs_in_pool)
+
+class TestNormalItemsOff(FFTIITestBase):
+ options = {
+ "normal_item_weight": 0,
+ "rare_item_weohjt": 1
+ }
+
+ def test_normal_items_not_in_pool(self):
+ normal_items_in_pool = self.get_items_by_name(shop_item_names)
+ self.assertEqual(len(normal_items_in_pool), 0, normal_items_in_pool)
+
+class TestRareItemsOff(FFTIITestBase):
+ options = {
+ "normal_item_weight": 1,
+ "rare_item_weight": 0
+ }
+
+ def test_rare_items_not_in_pool(self):
+ rare_items_in_pool = self.get_items_by_name(rare_item_names)
+ self.assertEqual(len(rare_items_in_pool), 0, rare_items_in_pool)
+
+class TestGilItemsOff(FFTIITestBase):
+ options = {
+ "normal_item_weight": 1,
+ "bonus_gil_item_weight": 0
+ }
+
+ def test_normal_items_not_in_pool(self):
+ gil_items_in_pool = self.get_items_by_name(gil_item_names)
+ self.assertEqual(len(gil_items_in_pool), 0, gil_items_in_pool)
+
+class TestJPItemsOff(FFTIITestBase):
+ options = {
+ "normal_item_weight": 1,
+ "jp_boon_item_weight": 0
+ }
+
+ def test_normal_items_not_in_pool(self):
+ jp_items_in_pool = self.get_items_by_name(jp_item_names)
+ self.assertEqual(len(jp_items_in_pool), 0, jp_items_in_pool)
\ No newline at end of file
diff --git a/worlds/fftii/test/test_locations.py b/worlds/fftii/test/test_locations.py
new file mode 100644
index 000000000000..42cacadbd3cb
--- /dev/null
+++ b/worlds/fftii/test/test_locations.py
@@ -0,0 +1,679 @@
+from Fill import distribute_items_restrictive
+from .bases import FFTIITestBase
+
+from ..data.items import zodiac_stone_names, world_map_pass_names, earned_job_names
+from ..data.locations import (location_sort_list, job_unlock_locations, monster_location_names, world_map_regions,
+ gallione_regions, fovoham_regions, lesalia_regions, lionel_regions, zeltennia_regions,
+ limberry_regions, murond_regions)
+from ..data.logic.FFTLocation import LocationNames
+from ..data.logic.Monsters import monster_locations_lookup, monster_family_lookup, MonsterNames, monster_families
+
+zodiac_stone_locations = [
+ LocationNames.LIONEL_2_STORY.value,
+ LocationNames.GOUG_STORY.value,
+ LocationNames.RIOVANES_2_STORY.value,
+ LocationNames.RIOVANES_3_STORY.value,
+ LocationNames.BETHLA_SLUICE_STORY.value,
+ LocationNames.LIMBERRY_2_STORY.value,
+ LocationNames.LIMBERRY_3_STORY.value,
+ LocationNames.IGROS_STORY.value,
+]
+
+zodiac_stone_locations_with_sidequests = [
+ LocationNames.LIONEL_2_STORY.value,
+ LocationNames.GOUG_STORY.value,
+ LocationNames.RIOVANES_2_STORY.value,
+ LocationNames.RIOVANES_3_STORY.value,
+ LocationNames.BETHLA_SLUICE_STORY.value,
+ LocationNames.LIMBERRY_2_STORY.value,
+ LocationNames.LIMBERRY_3_STORY.value,
+ LocationNames.IGROS_STORY.value,
+ LocationNames.GOLAND_4_SIDEQUEST.value,
+ LocationNames.NELVESKA_SIDEQUEST.value,
+ LocationNames.END_SIDEQUEST.value
+]
+
+sidequest_names = [
+ LocationNames.GOLAND_1_SIDEQUEST.value,
+ LocationNames.GOLAND_2_SIDEQUEST.value,
+ LocationNames.GOLAND_3_SIDEQUEST.value,
+ LocationNames.GOLAND_4_SIDEQUEST.value,
+ LocationNames.NELVESKA_SIDEQUEST.value,
+ LocationNames.ZARGHIDAS_SIDEQUEST.value,
+ LocationNames.NOGIAS_SIDEQUEST.value,
+ LocationNames.TERMINATE_SIDEQUEST.value,
+ LocationNames.DELTA_SIDEQUEST.value,
+ LocationNames.VALKYRIES_SIDEQUEST.value,
+ LocationNames.MLAPAN_SIDEQUEST.value,
+ LocationNames.TIGER_SIDEQUEST.value,
+ LocationNames.BRIDGE_SIDEQUEST.value,
+ LocationNames.VOYAGE_SIDEQUEST.value,
+ LocationNames.HORROR_SIDEQUEST.value,
+ LocationNames.END_SIDEQUEST.value,
+ LocationNames.BEOWULF_RECRUIT.value,
+ LocationNames.WORKER_8_RECRUIT.value,
+ LocationNames.REIS_DRAGON_RECRUIT.value,
+ LocationNames.REIS_HUMAN_RECRUIT.value,
+ LocationNames.CLOUD_RECRUIT.value,
+ LocationNames.BYBLOS_RECRUIT.value
+]
+
+rare_battle_names = [
+ LocationNames.MANDALIA_RARE.value,
+ LocationNames.SWEEGY_RARE.value,
+ LocationNames.LENALIA_RARE.value,
+ LocationNames.FOVOHAM_RARE.value,
+ LocationNames.YUGUO_RARE.value,
+ LocationNames.GROG_RARE.value,
+ LocationNames.BERVENIA_VOLCANO_RARE.value,
+ LocationNames.ZEKLAUS_RARE.value,
+ LocationNames.ARAGUAY_RARE.value,
+ LocationNames.ZIREKILE_RARE.value,
+ LocationNames.BARIAUS_HILL_RARE.value,
+ LocationNames.BARIAUS_VALLEY_RARE.value,
+ LocationNames.ZIGOLIS_RARE.value,
+ LocationNames.DOGUOLA_RARE.value,
+ LocationNames.FINATH_RARE.value,
+ LocationNames.GERMINAS_RARE.value,
+ LocationNames.BED_RARE.value,
+ LocationNames.DOLBODAR_RARE.value,
+ LocationNames.POESKAS_RARE.value
+]
+
+class TestAllRegionsReachable(FFTIITestBase):
+ def test_regions_from_gallione(self):
+ with self.subTest("Test all"):
+ self.collect_by_name(world_map_pass_names)
+ self.collect_by_name("Progressive Shop Level")
+ self.collect_by_name(zodiac_stone_names)
+ self.collect_by_name(earned_job_names)
+ for region in world_map_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Fovoham regions"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Fovoham Pass")
+ for region in fovoham_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Lesalia regions"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Lesalia Pass")
+ for region in lesalia_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Lionel regions from Lesalia"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Lesalia Pass")
+ self.collect_by_name("Lionel Pass")
+ for region in lionel_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Lionel regions from Murond"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Murond Pass")
+ self.collect_by_name("Lionel Pass")
+ for region in lionel_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Zeltennia regions"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Fovoham Pass")
+ self.collect_by_name("Zeltennia Pass")
+ for region in zeltennia_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Limberry regions"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Lesalia Pass")
+ self.collect_by_name("Limberry Pass")
+ for region in limberry_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+ with self.subTest("Test Murond regions"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Murond Pass")
+ self.collect_by_name("Lionel Pass")
+ self.collect_by_name(zodiac_stone_names)
+ for region in murond_regions:
+ self.assertTrue(self.can_reach_region(region.name), region.name)
+
+class TestZodiacStonesOnVanillaStones(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 13,
+ "sidequest_battles": "false"
+ }
+
+ def test_stones_on_correct_locations(self) -> None:
+ with self.subTest("Test stones are only on story locations"):
+ self.world_setup()
+ distribute_items_restrictive(self.multiworld)
+
+ all_stones = []
+ all_locations = self.multiworld.get_locations(self.player)
+ for location in all_locations:
+ if location.item.name in zodiac_stone_names:
+ all_stones.append(location.item)
+ for stone in all_stones:
+ if stone.location is not None:
+ self.assertTrue(stone.location.name in zodiac_stone_locations)
+
+class TestZodiacStonesOnVanillaStonesWithSidequests(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 13,
+ "sidequest_battles": "true"
+ }
+
+ def test_stones_on_correct_locations(self) -> None:
+ with self.subTest("Test stones are only on story and sidequest locations"):
+ self.world_setup()
+ distribute_items_restrictive(self.multiworld)
+
+ all_stones = []
+ all_locations = self.multiworld.get_locations(self.player)
+ for location in all_locations:
+ if location.item.name in zodiac_stone_names:
+ all_stones.append(location.item)
+ for stone in all_stones:
+ if stone.location is not None:
+ self.assertTrue(stone.location.name in zodiac_stone_locations_with_sidequests)
+
+class TestSidequestsNotInPoolWithOptionDisabled(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 13,
+ "sidequest_battles": "false"
+ }
+
+ def test_sidequests_not_in_pool(self) -> None:
+ with self.subTest("Test sidequests are not locations with option disabled."):
+ all_locations = self.multiworld.get_locations(self.player)
+ for location in all_locations:
+ self.assertTrue(location.name not in sidequest_names)
+
+class TestSidequestsInPoolWithOptionEnabled(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 13,
+ "sidequest_battles": "true"
+ }
+
+ def test_sidequests_in_pool(self) -> None:
+ with self.subTest("Test sidequests are locations with option enabled."):
+ all_locations = self.multiworld.get_locations(self.player)
+ all_location_names = [location.name for location in all_locations]
+ for location_name in sidequest_names:
+ self.assertTrue(location_name in all_location_names)
+
+
+class TestRareBattlesNotInPoolWithOptionDisabled(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 13,
+ "rare_battles": "false"
+ }
+
+ def test_rare_battles_not_in_pool(self) -> None:
+ with self.subTest("Test rare battles are not locations with option disabled."):
+ all_locations = self.multiworld.get_locations(self.player)
+ for location in all_locations:
+ self.assertTrue(location.name not in rare_battle_names)
+
+class TestRareBattlesInPoolWithOptionEnabled(FFTIITestBase):
+ options = {
+ "zodiac_stone_locations": 0,
+ "zodiac_stones_in_pool": 13,
+ "rare_battles": "true"
+ }
+
+ def test_rare_battles_in_pool(self) -> None:
+ with self.subTest("Test rare battles are locations with option enabled."):
+ all_locations = self.multiworld.get_locations(self.player)
+ all_location_names = [location.name for location in all_locations]
+ for location_name in rare_battle_names:
+ self.assertTrue(location_name in all_location_names)
+
+class TestRegionLogicGallioneStart(FFTIITestBase):
+ options = {
+ #"starting_region": 0 # Gallione
+ "sidequest_battles": "true",
+ "rare_battles": "true",
+ "final_battles": 0
+ }
+
+ run_default_tests = False
+
+ def test_region_access(self) -> None:
+ tested_locations = set()
+ with self.subTest("Test Gallione Access"):
+ test_locations = [
+ LocationNames.GARILAND_STORY,
+ LocationNames.MANDALIA_STORY,
+ LocationNames.IGROS_STORY,
+ LocationNames.THIEVES_FORT_STORY,
+ LocationNames.SWEEGY_STORY,
+ LocationNames.DORTER_1_STORY,
+ LocationNames.DORTER_2_STORY,
+ LocationNames.LENALIA_STORY,
+ LocationNames.ZEAKDEN_STORY,
+ LocationNames.MANDALIA_SHOP,
+ LocationNames.LENALIA_SHOP,
+ LocationNames.ZEAKDEN_SHOP,
+ LocationNames.RAMZA_CHAPTER_2_UNLOCK,
+ LocationNames.RAD_RECRUIT,
+ LocationNames.ALICIA_RECRUIT,
+ LocationNames.LAVIAN_RECRUIT,
+ LocationNames.MANDALIA_RARE,
+ LocationNames.SWEEGY_RARE,
+ LocationNames.LENALIA_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ self.collect_by_name("Progressive Shop Level")
+ self.collect_by_name(earned_job_names)
+ location_names = [str(location.value) for location in test_locations]
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Fovoham Access"):
+ test_locations = [
+ LocationNames.FOVOHAM_STORY,
+ LocationNames.RIOVANES_1_STORY,
+ LocationNames.RIOVANES_2_STORY,
+ LocationNames.RIOVANES_3_STORY,
+ LocationNames.YUGUO_STORY,
+ LocationNames.YARDOW_STORY,
+ LocationNames.GROG_STORY,
+ LocationNames.RIOVANES_SHOP,
+ LocationNames.YARDOW_SHOP,
+ LocationNames.RAMZA_CHAPTER_4_UNLOCK,
+ LocationNames.RAFA_RECRUIT,
+ LocationNames.MALAK_RECRUIT,
+ LocationNames.FOVOHAM_RARE,
+ LocationNames.YUGUO_RARE,
+ LocationNames.GROG_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ location_names = [str(location.value) for location in test_locations]
+
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Fovoham Pass")
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Lesalia Access"):
+ test_locations = [
+ LocationNames.ZEKLAUS_STORY,
+ LocationNames.ARAGUAY_STORY,
+ LocationNames.ZIREKILE_STORY,
+ LocationNames.GOLAND_STORY,
+ LocationNames.LESALIA_STORY,
+ LocationNames.ZEKLAUS_SHOP,
+ LocationNames.ZIREKILE_SHOP,
+ LocationNames.LESALIA_SHOP,
+ LocationNames.BOCO_RECRUIT,
+ LocationNames.GOLAND_1_SIDEQUEST,
+ LocationNames.GOLAND_2_SIDEQUEST,
+ LocationNames.GOLAND_3_SIDEQUEST,
+ LocationNames.GOLAND_4_SIDEQUEST,
+ LocationNames.BEOWULF_RECRUIT,
+ LocationNames.REIS_DRAGON_RECRUIT,
+ LocationNames.WORKER_8_RECRUIT,
+ LocationNames.BERVENIA_VOLCANO_RARE,
+ LocationNames.ZEKLAUS_RARE,
+ LocationNames.ARAGUAY_RARE,
+ LocationNames.ZIREKILE_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ location_names = [str(location.value) for location in test_locations]
+
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Lesalia Pass")
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Lionel Access From Lesalia"):
+ test_locations = [
+ LocationNames.ZALAND_STORY,
+ LocationNames.BARIAUS_HILL_STORY,
+ LocationNames.LIONEL_1_STORY,
+ LocationNames.LIONEL_2_STORY,
+ LocationNames.BARIAUS_VALLEY_STORY,
+ LocationNames.GOLGORAND_STORY,
+ LocationNames.ZIGOLIS_STORY,
+ LocationNames.BARIAUS_HILL_SHOP,
+ LocationNames.LIONEL_SHOP,
+ LocationNames.BARIAUS_VALLEY_SHOP,
+ LocationNames.AGRIAS_RECRUIT,
+ LocationNames.BARIAUS_HILL_RARE,
+ LocationNames.BARIAUS_VALLEY_RARE,
+ LocationNames.ZIGOLIS_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ location_names = [str(location.value) for location in test_locations]
+
+ self.remove_by_name(world_map_pass_names)
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Lionel Pass")
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Lesalia Pass")
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Lionel Access From Murond"):
+ test_locations = [
+ LocationNames.ZALAND_STORY,
+ LocationNames.BARIAUS_HILL_STORY,
+ LocationNames.LIONEL_1_STORY,
+ LocationNames.LIONEL_2_STORY,
+ LocationNames.BARIAUS_VALLEY_STORY,
+ LocationNames.GOLGORAND_STORY,
+ LocationNames.ZIGOLIS_STORY,
+ LocationNames.BARIAUS_HILL_SHOP,
+ LocationNames.LIONEL_SHOP,
+ LocationNames.BARIAUS_VALLEY_SHOP,
+ LocationNames.AGRIAS_RECRUIT,
+ LocationNames.BARIAUS_HILL_RARE,
+ LocationNames.BARIAUS_VALLEY_RARE,
+ LocationNames.ZIGOLIS_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ location_names = [str(location.value) for location in test_locations]
+
+ self.remove_by_name(world_map_pass_names)
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Lionel Pass")
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Murond Pass")
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Zeltennia Access"):
+ test_locations = [
+ LocationNames.DOGUOLA_STORY,
+ LocationNames.BERVENIA_CITY_STORY,
+ LocationNames.FINATH_STORY,
+ LocationNames.ZELTENNIA_STORY,
+ LocationNames.GERMINAS_STORY,
+ LocationNames.NELVESKA_SIDEQUEST,
+ LocationNames.ZARGHIDAS_SIDEQUEST,
+ LocationNames.REIS_HUMAN_RECRUIT,
+ LocationNames.CLOUD_RECRUIT,
+ LocationNames.DOGUOLA_RARE,
+ LocationNames.FINATH_RARE,
+ LocationNames.GERMINAS_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ location_names = [str(location.value) for location in test_locations]
+
+ self.remove_by_name(world_map_pass_names)
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Zeltennia Pass")
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Fovoham Pass")
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Limberry Access"):
+ test_locations = [
+ LocationNames.BED_STORY,
+ LocationNames.BETHLA_NORTH_STORY,
+ LocationNames.BETHLA_SOUTH_STORY,
+ LocationNames.BETHLA_SLUICE_STORY,
+ LocationNames.POESKAS_STORY,
+ LocationNames.LIMBERRY_1_STORY,
+ LocationNames.LIMBERRY_2_STORY,
+ LocationNames.LIMBERRY_3_STORY,
+ LocationNames.BETHLA_SHOP,
+ LocationNames.LIMBERRY_SHOP,
+ LocationNames.ORLANDU_RECRUIT,
+ LocationNames.MELIADOUL_RECRUIT,
+ LocationNames.BED_RARE,
+ LocationNames.POESKAS_RARE,
+ LocationNames.DOLBODAR_RARE
+ ]
+ tested_locations.update(set(test_locations))
+ location_names = [str(location.value) for location in test_locations]
+
+ self.remove_by_name(world_map_pass_names)
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Limberry Pass")
+ for location in location_names:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Lesalia Pass")
+ for location in location_names:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test Murond Access"):
+ test_locations_west = [
+ LocationNames.MUROND_TEMPLE_1_STORY,
+ LocationNames.MUROND_TEMPLE_2_STORY,
+ LocationNames.MUROND_TEMPLE_3_STORY,
+ LocationNames.GOUG_STORY,
+ LocationNames.UBS_1_STORY,
+ LocationNames.UBS_2_STORY,
+ LocationNames.UBS_3_STORY,
+ LocationNames.ORBONNE_SHOP,
+ LocationNames.MUSTADIO_RECRUIT
+ ]
+ tested_locations.update(set(test_locations_west))
+ test_locations_east = [
+ LocationNames.UBS_4_STORY,
+ LocationNames.UBS_5_STORY,
+ LocationNames.MUROND_DEATH_CITY_STORY,
+ LocationNames.PRECINCTS_STORY,
+ LocationNames.AIRSHIPS_1_STORY,
+ LocationNames.AIRSHIPS_2_STORY,
+ LocationNames.NOGIAS_SIDEQUEST,
+ LocationNames.TERMINATE_SIDEQUEST,
+ LocationNames.DELTA_SIDEQUEST,
+ LocationNames.VALKYRIES_SIDEQUEST,
+ LocationNames.MLAPAN_SIDEQUEST,
+ LocationNames.TIGER_SIDEQUEST,
+ LocationNames.BRIDGE_SIDEQUEST,
+ LocationNames.VOYAGE_SIDEQUEST,
+ LocationNames.HORROR_SIDEQUEST,
+ LocationNames.END_SIDEQUEST,
+ LocationNames.BYBLOS_RECRUIT
+ ]
+ tested_locations.update(set(test_locations_east))
+ location_names_west = [str(location.value) for location in test_locations_west]
+ location_names_east = [str(location.value) for location in test_locations_east]
+
+ self.remove_by_name(world_map_pass_names)
+ for location in [*location_names_west, *location_names_east]:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Murond Pass")
+ for location in location_names_east:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ for location in location_names_west:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Lionel Pass")
+ for location in [*location_names_west, *location_names_east]:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+ with self.subTest("Test All Locations Were Tested"):
+ for location in location_sort_list:
+ if location not in job_unlock_locations:
+ self.assertTrue(location in tested_locations, location)
+
+class TestRegionLogicGallioneStartAltimaOnly(FFTIITestBase):
+ options = {
+ #"starting_region": 0 # Gallione
+ "sidequest_battles": "true",
+ "rare_battles": "true",
+ "final_battles": 1
+ }
+
+ run_default_tests = False
+
+ def test_region_access(self) -> None:
+ with self.subTest("Test Murond Access"):
+ test_locations_west = [
+ LocationNames.MUROND_TEMPLE_1_STORY,
+ LocationNames.MUROND_TEMPLE_2_STORY,
+ LocationNames.MUROND_TEMPLE_3_STORY,
+ LocationNames.GOUG_STORY,
+ LocationNames.UBS_1_STORY,
+ LocationNames.UBS_2_STORY,
+ LocationNames.UBS_3_STORY,
+ LocationNames.UBS_4_STORY,
+ LocationNames.UBS_5_STORY,
+ LocationNames.MUROND_DEATH_CITY_STORY,
+ LocationNames.PRECINCTS_STORY,
+ LocationNames.AIRSHIPS_1_STORY,
+ LocationNames.ORBONNE_SHOP,
+ LocationNames.MUSTADIO_RECRUIT
+ ]
+ test_locations_east = [
+ LocationNames.AIRSHIPS_2_STORY,
+ LocationNames.NOGIAS_SIDEQUEST,
+ LocationNames.TERMINATE_SIDEQUEST,
+ LocationNames.DELTA_SIDEQUEST,
+ LocationNames.VALKYRIES_SIDEQUEST,
+ LocationNames.MLAPAN_SIDEQUEST,
+ LocationNames.TIGER_SIDEQUEST,
+ LocationNames.BRIDGE_SIDEQUEST,
+ LocationNames.VOYAGE_SIDEQUEST,
+ LocationNames.HORROR_SIDEQUEST,
+ LocationNames.END_SIDEQUEST,
+ LocationNames.BYBLOS_RECRUIT
+ ]
+ location_names_west = [str(location.value) for location in test_locations_west]
+ location_names_east = [str(location.value) for location in test_locations_east]
+
+ self.collect_all_but(["Murond Pass", "Lionel Pass"])
+ for location in [*location_names_west, *location_names_east]:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Murond Pass")
+ for location in location_names_east:
+ self.assertFalse(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ for location in location_names_west:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+ self.collect_by_name("Lionel Pass")
+ for location in [*location_names_west, *location_names_east]:
+ self.assertTrue(self.world.get_location(location).can_reach(self.multiworld.state), location)
+
+class TestPoachLogicWithGallioneStart(FFTIITestBase):
+ options = {
+ "poach_locations": "true",
+ "job_unlocks": "true",
+ "sidequest_battles": "true"
+ }
+
+ run_default_tests = False
+
+ def test_jobs_required(self):
+ with self.subTest("Test Thief required"):
+ self.assertAccessDependency(
+ monster_location_names,
+ [["Thief"]],
+ only_check_listed=True,
+ )
+ with self.subTest("Test Mediator and Thief required for Wildbow"):
+ self.assertAccessDependency(
+ ["Poach Wildbow"],
+ [["Thief", "Mediator"]],
+ only_check_listed=True
+ )
+ with self.subTest("Test passes required"):
+ self.assertAccessDependency(
+ ["Poach Porky"],
+ [
+ ["Lesalia Pass", "Limberry Pass", "Thief"],
+ ["Fovoham Pass", "Zeltennia Pass", "Limberry Pass", "Thief"],
+ ["Fovoham Pass", "Zeltennia Pass", "Thief", "Mediator"],
+ ["Lesalia Pass", "Lionel Pass", "Thief", "Mediator"]
+ ],
+ only_check_listed=True
+ )
+
+ def test_gallione_monsters(self):
+ self.collect_by_name("Progressive Shop Level")
+ self.collect_by_name(earned_job_names)
+ with self.subTest("Test Thief logic"):
+ self.collect_by_name("Thief")
+ for monster_location in monster_location_names:
+ monster_data = monster_locations_lookup[monster_location[6:]]
+ if len(monster_data.gallione_locations) > 0:
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.collect_by_name("Fovoham Pass")
+ if len(monster_data.fovoham_locations) > 0:
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ #self.remove_by_name("Fovoham Pass")
+ self.collect_by_name("Lesalia Pass")
+ if len(monster_data.lesalia_locations) > 0:
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.remove_by_name("Fovoham Pass")
+ self.collect_by_name("Lionel Pass")
+ if len(monster_data.lionel_locations) > 0:
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.remove_by_name("Lionel Pass")
+ self.collect_by_name("Fovoham Pass")
+ self.collect_by_name("Zeltennia Pass")
+ if len(monster_data.zeltennia_locations) > 0:
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.remove_by_name("Zeltennia Pass")
+ self.collect_by_name("Limberry Pass")
+ if len(monster_data.limberry_locations) > 0:
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ with self.subTest("Test Mediator logic"):
+ self.remove_by_name(world_map_pass_names)
+ self.collect_by_name("Mediator")
+ for monster_location in monster_location_names:
+ monster_name = monster_location[6:]
+ monster_family_name = monster_family_lookup[MonsterNames(monster_name)]
+ monster_family_values = monster_families[monster_family_name]
+ monster_datas = []
+ for monster in monster_family_values:
+ monster_datas.append(monster_locations_lookup[monster.value])
+ if any([
+ len(monster_datas[0].gallione_locations) > 0,
+ len(monster_datas[1].gallione_locations) > 0,
+ len(monster_datas[2].gallione_locations) > 0,
+ ]):
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.collect_by_name("Fovoham Pass")
+ if any([
+ len(monster_datas[0].fovoham_locations) > 0,
+ len(monster_datas[1].fovoham_locations) > 0,
+ len(monster_datas[2].fovoham_locations) > 0,
+ ]):
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.remove_by_name("Fovoham Pass")
+ # self.remove_by_name("Fovoham Pass")
+ self.collect_by_name("Lesalia Pass")
+ if any([
+ len(monster_datas[0].lesalia_locations) > 0,
+ len(monster_datas[1].lesalia_locations) > 0,
+ len(monster_datas[2].lesalia_locations) > 0,
+ ]):
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.collect_by_name("Lionel Pass")
+ if any([
+ len(monster_datas[0].lionel_locations) > 0,
+ len(monster_datas[1].lionel_locations) > 0,
+ len(monster_datas[2].lionel_locations) > 0,
+ ]):
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.remove_by_name("Lionel Pass")
+ self.collect_by_name("Fovoham Pass")
+ self.collect_by_name("Zeltennia Pass")
+ if any([
+ len(monster_datas[0].zeltennia_locations) > 0,
+ len(monster_datas[1].zeltennia_locations) > 0,
+ len(monster_datas[2].zeltennia_locations) > 0,
+ ]):
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
+ self.remove_by_name("Zeltennia Pass")
+ self.collect_by_name("Limberry Pass")
+ if any([
+ len(monster_datas[0].limberry_locations) > 0,
+ len(monster_datas[1].limberry_locations) > 0,
+ len(monster_datas[2].limberry_locations) > 0,
+ ]):
+ self.assertTrue(self.can_reach_location(monster_location), monster_location)
\ No newline at end of file
diff --git a/worlds/generic/__init__.py b/worlds/generic/__init__.py
index 2e614eba4ddc..c4aef4f67bb0 100644
--- a/worlds/generic/__init__.py
+++ b/worlds/generic/__init__.py
@@ -17,7 +17,7 @@ class GenericWeb(WebWorld):
'A guide detailing the commands available to the user when participating in an Archipelago session.',
'English', 'commands_en.md', 'commands/en', ['jat2980', 'Ijwu'])
mac = Tutorial('Archipelago Setup Guide for Mac', 'A guide detailing how to run Archipelago clients on macOS.',
- 'English', 'mac_en.md','mac/en', ['Bicoloursnake'])
+ 'English', 'mac_en.md','mac/en', ['Bicoloursnake', 'silasary'])
plando = Tutorial('Archipelago Plando Guide', 'A guide to understanding and using plando for your game.',
'English', 'plando_en.md', 'plando/en', ['alwaysintreble', 'Alchav'])
setup = Tutorial('Getting Started',
diff --git a/worlds/generic/docs/mac_en.md b/worlds/generic/docs/mac_en.md
index b06593f5011d..4db48d2abd3a 100644
--- a/worlds/generic/docs/mac_en.md
+++ b/worlds/generic/docs/mac_en.md
@@ -19,14 +19,17 @@ It is generally recommended that you use a virtual environment to run python bas
2. Run the command `python3 -m venv venv` to create a virtual environment. Running this command will create a new directory at the specified path, so make sure that path is clear for a new directory to be created.
3. Run the command `source venv/bin/activate` to activate the virtual environment.
4. If you want to exit the virtual environment, run the command `deactivate`.
+## Building the App
+1. Run the command `python3 setup.py bdist_mac` to build an App Bundle. This step may take a while.
+2. Back in Finder, go to the new `build` subdirectory, and copy the Archipelago app you just made to your Applications directory.
## Steps to Run the Clients
-1. Run the command `python3 Launcher.py`.
+1. Launch the Archipelago Launcher
2. If your game doesn't have a patch file, just click the desired client in the right side column.
3. If your game does have a patch file, click the 'Open Patch' button and navigate to your patch file (the filename extension will look something like apsm, aplttp, apsmz3, etc.).
4. If the patching process needs a rom, but cannot find it, it will ask you to navigate to your legally obtained rom.
5. Your client should now be running and rom created (where applicable).
## Additional Steps for SNES Games
-1. If using RetroArch, the instructions to set up your emulator [here in the Link to the Past setup guide](https://archipelago.gg/tutorial/A%20Link%20to%20the%20Past/multiworld/en) also work on the macOS version of RetroArch.
+1. If using RetroArch, the instructions to set up your emulator [here in the Link to the Past setup guide](/tutorial/A%20Link%20to%20the%20Past/multiworld/en) also work on the macOS version of RetroArch.
2. Double click on the SNI tar.gz download to extract the files to an SNI directory. If it isn't already, rename this directory to SNI to make some steps easier.
3. Move the SNI directory out of the downloads directory, preferably into the Archipelago directory created earlier.
4. If the SNI directory is correctly named and moved into the Archipelago directory, it should auto run with the SNI client. If it doesn't automatically run, open up the SNI directory and run the SNI executable file manually.
diff --git a/worlds/hades/Client.py b/worlds/hades/Client.py
index 52315f896a4c..d6f2da789e9f 100644
--- a/worlds/hades/Client.py
+++ b/worlds/hades/Client.py
@@ -1,4 +1,3 @@
-from __future__ import annotations
import os
import sys
import asyncio
@@ -6,6 +5,7 @@
import importlib.util
import Utils
import pathlib
+from typing import Dict, NamedTuple, Optional
from NetUtils import ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
@@ -28,7 +28,7 @@ class HadesClientCommandProcessor(ClientCommandProcessor):
def _cmd_resync(self):
"""Manually trigger a resync."""
#This is a really stupid solution, but it works so idk
- asyncio.create_task(self.ctx.check_connection_and_send_items_and_request_starting_info(""))
+ Utils.async_start(self.ctx.check_connection_and_send_items_and_request_starting_info(""))
class HadesContext(CommonContext):
@@ -36,27 +36,29 @@ class HadesContext(CommonContext):
command_processor = HadesClientCommandProcessor
game = "Hades"
items_handling = 0b111 # full remote
- cache_items_received_names = []
- hades_slot_data = None
- players_id_to_name = None
- creating_location_to_item_mapping = False
- missing_locations_cache = []
- checked_locations_cache = []
- location_name_to_id = None
- location_to_item_map_created = False
- deathlink_pending = False
- deathlink_enabled = False
- is_connected = False
- is_receiving_items_from_connect_package = False
- polycosmos_version = "0.13"
- compact_setting_string = ""
-
- def __init__(self, server_address, password):
+ polycosmos_version = "0.14"
+
+ is_connected : bool
+ deathlink_pending : bool
+ deathlink_enabled : bool
+ creating_location_to_item_mapping : bool
+ is_receiving_items_from_connect_package : bool
+
+ location_name_to_id : dict
+
+ def __init__(self, server_address: Optional[str] = None, password: Optional[str] = None):
super(HadesContext, self).__init__(server_address, password)
- self.send_index: int = 0
- self.syncing = False
- self.awaiting_bridge = False
- # Load here any data you might need the client to know about
+ self.hades_slot_data = None
+
+ self.is_connected = False
+ self.deathlink_pending = False
+ self.deathlink_enabled = False
+ self.creating_location_to_item_mapping = False
+ self.is_receiving_items_from_connect_package = False
+
+ self.missing_locations_cache = []
+ self.checked_locations_cache = []
+
# Add hook to comunicate with StyxScribe
subsume.AddHook(self.send_location_check_to_server, styx_scribe_recieve_prefix + "Locations updated:",
@@ -69,7 +71,7 @@ def __init__(self, server_address, password):
subsume.AddHook(self.send_location_hint_to_server, styx_scribe_recieve_prefix \
+ "Locations hinted:", "HadesClient")
- async def server_auth(self, password_requested: bool = False):
+ async def server_auth(self, password_requested: bool = False) -> None:
# This is called to autentificate with the server.
if password_requested and not self.password:
await super(HadesContext, self).server_auth(password_requested)
@@ -77,7 +79,7 @@ async def server_auth(self, password_requested: bool = False):
self.tags = set()
await self.send_connect()
- async def connection_closed(self):
+ async def connection_closed(self) -> None:
# This is called when the connection is closed (duh!)
# This will send the message always, but only process by Styx scribe if actually in game
subsume.Send(styx_scribe_send_prefix + "Connection Error")
@@ -102,22 +104,21 @@ async def shutdown(self):
# ----------------- Package Management section starts --------------------------------
- def on_package(self, cmd: str, args: dict):
+ def on_package(self, cmd: str, args: dict) -> None:
# This is what is done when a package arrives.
if cmd == "Connected":
# What should be done in a connection package
- self.cache_items_received_names.clear()
- self.missing_locations_cache = args["missing_locations"]
- self.checked_locations_cache = args["checked_locations"]
self.hades_slot_data = args["slot_data"]
if not (self.hades_slot_data["version_check"] == self.polycosmos_version):
stringError = "WORLD GENERATED WITH POLYCOSMOS " + self.hades_slot_data["version_check"] \
+ " AND CLIENT USING POLYCOSMOS " + self.polycosmos_version + "\n"
- stringError += "THIS ARE NOT COMPATIBLE"
+ stringError += "THESE ARE NOT COMPATIBLE"
raise Exception(stringError)
+
self.location_name_to_id = self.get_location_name_to_id()
+
if "death_link" in self.hades_slot_data and self.hades_slot_data["death_link"]:
- asyncio.create_task(self.update_death_link(True))
+ Utils.async_start(self.update_death_link(True))
self.deathlink_enabled = True
self.is_connected = True
self.is_receiving_items_from_connect_package = True
@@ -127,20 +128,12 @@ def on_package(self, cmd: str, args: dict):
self.seed_name = args["seed_name"]
if cmd == "RoomUpdate":
- if "checked_lodations" in args:
- collect_locations_cache = ""
- for location in args["checked_locations"]:
- collect_locations_cache += self.location_names.lookup_in_slot(location) + "-"
- if len(collect_locations_cache) > 0:
- collect_locations_cache = collect_locations_cache[:-1]
- subsume.Send(styx_scribe_send_prefix + "Locations collected:" + collect_locations_cache)
+ if "checked_lodations" in args and len(args["checked_locations"]) > 0:
+ subsume.Send(styx_scribe_send_prefix + "Locations collected:" + "-".join(
+ (location) for location in args["checked_locations"]
+ ))
if cmd == "ReceivedItems":
- # What should be done when an Item is recieved.
- # NOTE THIS GETS ALL ITEMS THAT HAVE BEEN RECIEVED! WE USE THIS FOR RESYNCS!
- for item in args["items"]:
- self.cache_items_received_names += [self.item_names.lookup_in_slot(item.item)]
-
# We ignore sending the package to hades if just connected,
# since the game is not ready for it (and will request it itself later)
if self.is_receiving_items_from_connect_package:
@@ -150,46 +143,30 @@ def on_package(self, cmd: str, args: dict):
if cmd == "LocationInfo":
if self.creating_location_to_item_mapping:
self.creating_location_to_item_mapping = False
- asyncio.create_task(self.create_location_to_item_dictionary(args["locations"]))
+ self.create_location_to_item_dictionary(args["locations"])
return
super().on_package(cmd, args)
-
+
if cmd == "Bounced":
if "tags" in args:
if "DeathLink" in args["tags"]:
self.on_deathlink(args["data"])
- def send_items(self):
- payload_message = self.parse_array_to_string(self.cache_items_received_names)
+ def send_items(self) -> None:
+ payload_message = ",".join(self.item_names.lookup_in_game(item.item) for item in self.items_received)
subsume.Send(styx_scribe_send_prefix + "Items Updated:" + payload_message)
- def parse_array_to_string(self, array_of_items):
- message = ""
- for itemname in array_of_items:
- message += itemname
- message += ","
- return message
-
- async def send_location_check_to_server(self, message):
- sendingLocationsId = []
- sendingLocationsName = message
- payload_message = []
- sendingLocationsId += [self.location_name_to_id[sendingLocationsName]]
- payload_message += [{"cmd": "LocationChecks", "locations": sendingLocationsId}]
- asyncio.create_task(self.send_msgs(payload_message))
-
- async def check_connection_and_send_items_and_request_starting_info(self, message):
+ async def send_location_check_to_server(self, message : str) -> None:
+ await self.check_locations([self.location_name_to_id[message]])
+
+ async def check_connection_and_send_items_and_request_starting_info(self, message : str) -> None:
if self.check_for_connection():
self.is_receiving_items_from_connect_package = False
- await self.send_items_and_request_starting_info(message)
-
- async def send_items_and_request_starting_info(self, message):
- self.store_settings_data()
- # send items that were already cached in connect
- self.send_items()
- self.request_location_to_item_dictionary()
+ # send items that were already cached in connect
+ self.send_items()
+ self.request_location_to_item_dictionary()
- def store_settings_data(self):
+ def store_settings_data(self) -> None:
hades_settings_string = ""
#codify in the string all heat settings
hades_settings_string += str(self.hades_slot_data["heat_system"]) + "-"
@@ -238,26 +215,31 @@ def store_settings_data(self):
hades_settings_string += str(self.hades_slot_data["keepsakes_needed"]) + "-"
hades_settings_string += str(self.hades_slot_data["fates_needed"]) + "-"
- #Store the compact setting string
- self.compact_setting_string = hades_settings_string
+ return hades_settings_string
- def request_location_to_item_dictionary(self):
+ def request_location_to_item_dictionary(self) -> None:
self.creating_location_to_item_mapping = True
- request = self.missing_locations_cache + self.checked_locations_cache
- asyncio.create_task(self.send_msgs([{"cmd": "LocationScouts", "locations": request, "create_as_hint": 0}]))
+ request = self.server_locations
+ Utils.async_start(self.send_msgs([{"cmd": "LocationScouts", "locations": request, "create_as_hint": 0}]))
- async def create_location_to_item_dictionary(self, itemsdict):
+ def create_location_to_item_dictionary(self, itemsdict : Optional[dict]) -> None:
locationItemMapping = ""
for networkitem in itemsdict:
- locationItemMapping += self.clear_invalid_char(self.location_names.lookup_in_slot(networkitem.location)) \
- + "--" + self.clear_invalid_char(self.player_names[networkitem.player]) + "--" \
- + self.clear_invalid_char(self.item_names.lookup_in_slot(networkitem.item, networkitem.player)) + "||"
+
+ location = self.parse_to_len_encode(self.location_names.lookup_in_slot(networkitem.location))
+ player_name = self.parse_to_len_encode(self.player_names[networkitem.player])
+ item_name = self.parse_to_len_encode(self.item_names.lookup_in_slot(networkitem.item, networkitem.player))
+
+ locationItemMapping += self.parse_to_len_encode(location+player_name+item_name)
subsume.Send(styx_scribe_send_prefix + "Location to Item Map:" + locationItemMapping)
- self.creating_location_to_item_dictionary = False
- subsume.Send(styx_scribe_send_prefix + "Data finished" + self.compact_setting_string)
+ subsume.Send(styx_scribe_send_prefix + "Data finished" + self.store_settings_data())
- def clear_invalid_char(self, inputstring: str):
+ def parse_to_len_encode(self, inputstring: str) -> str:
+ output = self.clear_invalid_char(inputstring)
+ return str(len(output)) + "|" + output
+
+ def clear_invalid_char(self, inputstring: str) -> str:
newstr = inputstring.replace("{", "")
newstr = newstr.replace("}", "")
return newstr
@@ -266,7 +248,7 @@ def clear_invalid_char(self, inputstring: str):
# ----------------- Hints from game section starts --------------------------------
- async def send_location_hint_to_server(self, message):
+ async def send_location_hint_to_server(self, message : str) -> None:
if self.hades_slot_data["store_give_hints"] == 0:
return
split_array = message.split("-")
@@ -274,30 +256,30 @@ async def send_location_hint_to_server(self, message):
for location in split_array:
if len(location) > 0:
request.append(self.location_name_to_id[location])
- asyncio.create_task(self.send_msgs([{"cmd": "LocationScouts", "locations": request, "create_as_hint": 2}]))
+ Utils.async_start(self.send_msgs([{"cmd": "LocationScouts", "locations": request, "create_as_hint": 2}]))
# ----------------- Hints from game section ends ------------------------
# -------------deathlink section started --------------------------------
- def on_deathlink(self, data: dict):
+ def on_deathlink(self, data: dict) -> None:
# What should be done when a deathlink message is recieved
if self.deathlink_pending:
return
self.deathlink_pending = True
subsume.Send(styx_scribe_send_prefix + "Deathlink received")
super().on_deathlink(data)
- asyncio.create_task(self.wait_and_lower_deathlink_flag())
+ Utils.async_start(self.wait_and_lower_deathlink_flag())
- def send_death(self, death_text: str = ""):
+ def send_death(self, death_text: str = "") -> None:
# What should be done to send a death link
# Avoid sending death if we died from a deathlink
if self.deathlink_pending or not self.deathlink_enabled:
return
self.deathlink_pending = True
- asyncio.create_task(super().send_death(death_text))
- asyncio.create_task(self.wait_and_lower_deathlink_flag())
+ Utils.async_start(super().send_death(death_text))
+ Utils.async_start(self.wait_and_lower_deathlink_flag())
- async def wait_and_lower_deathlink_flag(self):
+ async def wait_and_lower_deathlink_flag(self) -> None:
await asyncio.sleep(3)
self.deathlink_pending = False
@@ -306,7 +288,7 @@ async def wait_and_lower_deathlink_flag(self):
# -------------game completion section starts
# this is to detect game completion. Note that on futher updates this will need --------------------------------
# to be changed to adapt to new game completion conditions
- def on_run_completion(self, message):
+ def on_run_completion(self, message : str) -> None:
#parse message
counters = message.split("-")
#counters[0] is number of clears, counters[1] is number of different weapons with runs clears.
@@ -316,19 +298,19 @@ def on_run_completion(self, message):
hasEnoughKeepsakes = self.hades_slot_data["keepsakes_needed"] <= int(counters[2])
hasEnoughFates = self.hades_slot_data["fates_needed"] <= int(counters[3])
if hasEnoughRuns and hasEnoughWeapons and hasEnoughKeepsakes and hasEnoughFates:
- asyncio.create_task(self.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]))
+ Utils.async_start(self.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]))
self.finished_game = True
# -------------game completion section ended --------------------------------
# ------------ game connection QoL handling
- def check_for_connection(self):
+ def check_for_connection(self) -> bool:
if not self.is_connected:
subsume.Send(styx_scribe_send_prefix + "Connection Error")
return False
return True
- # ------------ Helper method for 0.5.0
+ # ------------ Helper method to invert lookup table. Can erase if AP has its own internal one.
def get_location_name_to_id(self):
table = {}
@@ -336,9 +318,10 @@ def get_location_name_to_id(self):
table[self.location_names.lookup_in_slot(locationid)] = locationid
return table
+
# ------------ gui section ------------------------------------------------
- def run_gui(self):
+ def run_gui(self) -> None:
from kvui import GameManager
class HadesManager(GameManager):
@@ -346,14 +329,7 @@ class HadesManager(GameManager):
base_title = "Archipelago Hades Client"
self.ui = HadesManager(self)
- self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
-
-
-def print_error_and_close(msg):
- logger.error("Error: " + msg)
- Utils.messagebox("Error", msg, error=True)
- sys.exit(1)
-
+ self.ui_task = Utils.async_start(self.ui.async_run(), name="UI")
# ------------ Methods to start the client + Hades + StyxScribe ------------
@@ -364,7 +340,7 @@ def launch_hades():
def launch():
async def main(args):
ctx = HadesContext(args.connect, args.password)
- ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
+ ctx.server_task = Utils.async_start(server_loop(ctx), name="server loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
@@ -377,8 +353,23 @@ async def main(args):
import colorama
# --------------------- Styx Scribe initialization -----------------
styx_scribe_path = settings.get_settings()["hades_options"]["styx_scribe_path"]
+
+ # Parsing of styxscribe path. This will try to find it on the same folder if it fails.
+ last_slash = styx_scribe_path.rfind("/")
+ if (last_slash == -1):
+ print("Invalid path given to hades client for styxscribe. Cant parse")
+ return
+
+ filesbstr = styx_scribe_path[last_slash + 1 :]
+ if (filesbstr != "StyxScribe.py"):
+ print("Path given does not correspond to StyxScribe. Attempting to parse")
+ styx_scribe_path = styx_scribe_path[:last_slash + 1] + "StyxScribe.py"
+
hadespath = os.path.dirname(styx_scribe_path)
+ if (not os.path.exists(hadespath)):
+ print("Styx scribe not found at path.")
+
spec = importlib.util.spec_from_file_location("StyxScribe", str(styx_scribe_path))
styx_scribe = importlib.util.module_from_spec(spec)
sys.modules["StyxScribe"] = styx_scribe
diff --git a/worlds/hades/Items.py b/worlds/hades/Items.py
index ce05c8d92279..80d3b708b007 100644
--- a/worlds/hades/Items.py
+++ b/worlds/hades/Items.py
@@ -10,7 +10,7 @@ class ItemData(NamedTuple):
trap: bool = False
-hades_base_item_id = 666100
+hades_base_item_id = 1
item_table_pacts: Dict[str, ItemData] = {
"Hard Labor Pact Level": ItemData(hades_base_item_id, True),
@@ -99,6 +99,22 @@ class ItemData(NamedTuple):
"Close At Heart Event Item": ItemData(None, True, True),
"Denizens Of The Deep Event Item": ItemData(None, True, True),
"The Useless Trinket Event Item": ItemData(None, True, True),
+ "Musician and Muse Event Item" : ItemData(None, True, True),
+ "End to Torment Event Item" : ItemData(None, True, True),
+ "Divided by Death Event Item" : ItemData(None, True, True),
+ "Eternal Rest Event Item" : ItemData(None, True, True),
+ "A Place of Revelry Event Item" : ItemData(None, True, True),
+ "Amusing Chaos Event Item" : ItemData(None, True, True),
+ "Sea-God's Spite Event Item" : ItemData(None, True, True),
+ "War-God's Bloodlust Event Item" : ItemData(None, True, True),
+ "A Friendly Wager Event Item" : ItemData(None, True, True),
+ "The Holy Liege Event Item" : ItemData(None, True, True),
+ "The Fated Saint of War Event Item" : ItemData(None, True, True),
+ "The Preserver's Avatar Event Item" : ItemData(None, True, True),
+ "The Dragon's Rival Event Item" : ItemData(None, True, True),
+ "The God-like King Event Item" : ItemData(None, True, True),
+ "The Dawn Bringer Event Item" : ItemData(None, True, True),
+ "The Gift of Song Event Item" : ItemData(None, True, True),
}
item_table_filler: Dict[str, ItemData] = {
@@ -191,28 +207,6 @@ class ItemData(NamedTuple):
"Initial Money Helper": ItemData(hades_base_item_id + 84, False, False, False),
}
-
-def create_filler_pool_options(options):
- item_filler_options = []
- if options.darkness_pack_value.value:
- item_filler_options.append("Darkness")
- if options.keys_pack_value.value:
- item_filler_options.append("Keys")
- if options.gemstones_pack_value.value:
- item_filler_options.append("Gemstones")
- if options.diamonds_pack_value.value:
- item_filler_options.append("Diamonds")
- if options.titan_blood_pack_value.value:
- item_filler_options.append("TitanBlood")
- if options.nectar_pack_value.value:
- item_filler_options.append("Nectar")
- if options.ambrosia_pack_value.value:
- item_filler_options.append("Ambrosia")
- if not item_filler_options:
- item_filler_options.append("Darkness")
- return item_filler_options
-
-
def create_trap_pool():
return [trap for trap in item_table_traps.keys()]
@@ -280,6 +274,22 @@ def create_pact_pool_amount(options) -> Dict[str, int]:
"Close At Heart Event": "Close At Heart Event Item",
"Denizens Of The Deep Event": "Denizens Of The Deep Event Item",
"The Useless Trinket Event": "The Useless Trinket Event Item",
+ "Musician and Muse Event" : "Musician and Muse Event Item",
+ "End to Torment Event" : "End to Torment Event Item",
+ "Divided by Death Event" : "Divided by Death Event Item",
+ "Eternal Rest Event" : "Eternal Rest Event Item",
+ "A Place of Revelry Event" : "A Place of Revelry Event Item",
+ "Amusing Chaos Event" : "Amusing Chaos Event Item",
+ "Sea-God's Spite Event" : "Sea-God's Spite Event Item",
+ "War-God's Bloodlust Event" : "War-God's Bloodlust Event Item",
+ "A Friendly Wager Event" : "A Friendly Wager Event Item",
+ "The Holy Liege Event" : "The Holy Liege Event Item",
+ "The Fated Saint of War Event" : "The Fated Saint of War Event Item",
+ "The Preserver's Avatar Event" : "The Preserver's Avatar Event Item",
+ "The Dragon's Rival Event" : "The Dragon's Rival Event Item",
+ "The God-like King Event" : "The God-like King Event Item",
+ "The Dawn Bringer Event" : "The Dawn Bringer Event Item",
+ "The Gift of Song Event" : "The Gift of Song Event Item",
}
event_item_pairs_weapon_mode: Dict[str, str] = {
@@ -319,6 +329,22 @@ def create_pact_pool_amount(options) -> Dict[str, int]:
"Close At Heart Event": "Close At Heart Event Item",
"Denizens Of The Deep Event": "Denizens Of The Deep Event Item",
"The Useless Trinket Event": "The Useless Trinket Event Item",
+ "Musician and Muse Event" : "Musician and Muse Event Item",
+ "End to Torment Event" : "End to Torment Event Item",
+ "Divided by Death Event" : "Divided by Death Event Item",
+ "Eternal Rest Event" : "Eternal Rest Event Item",
+ "A Place of Revelry Event" : "A Place of Revelry Event Item",
+ "Amusing Chaos Event" : "Amusing Chaos Event Item",
+ "Sea-God's Spite Event" : "Sea-God's Spite Event Item",
+ "War-God's Bloodlust Event" : "War-God's Bloodlust Event Item",
+ "A Friendly Wager Event" : "A Friendly Wager Event Item",
+ "The Holy Liege Event" : "The Holy Liege Event Item",
+ "The Fated Saint of War Event" : "The Fated Saint of War Event Item",
+ "The Preserver's Avatar Event" : "The Preserver's Avatar Event Item",
+ "The Dragon's Rival Event" : "The Dragon's Rival Event Item",
+ "The God-like King Event" : "The God-like King Event Item",
+ "The Dawn Bringer Event" : "The Dawn Bringer Event Item",
+ "The Gift of Song Event" : "The Gift of Song Event Item",
"Beat Hades Sword Weapon": "Hades Victory Sword Weapon",
"Beat Meg Sword Weapon": "Meg Victory Sword Weapon",
"Beat Lernie Sword Weapon": "Lernie Victory Sword Weapon",
@@ -394,5 +420,5 @@ def __init__(self, name, player: int = None):
item_data.code, player
)
- def is_progression(self):
+ def is_progression(self) -> bool:
return self.classification == ItemClassification.progression
diff --git a/worlds/hades/Locations.py b/worlds/hades/Locations.py
index b2429b970707..e13bd0b69506 100644
--- a/worlds/hades/Locations.py
+++ b/worlds/hades/Locations.py
@@ -1,9 +1,8 @@
from BaseClasses import Location
-hades_base_location_id = 5093427000
+hades_base_location_id = 1
-# This is basically location + score checks. Keeping this as a variable to have easier time keeping
max_number_room_checks = 1700 + hades_base_location_id
# Making global tables that can be used for unit testing.
@@ -133,6 +132,22 @@
"Close At Heart": max_number_room_checks + 87,
"Denizens Of The Deep": max_number_room_checks + 88,
"The Useless Trinket": max_number_room_checks + 89,
+ "Musician and Muse" : max_number_room_checks + 90,
+ "End to Torment" : max_number_room_checks + 91,
+ "Divided by Death" : max_number_room_checks + 92,
+ "Eternal Rest" : max_number_room_checks + 93,
+ "A Place of Revelry" : max_number_room_checks + 94,
+ "Amusing Chaos" : max_number_room_checks + 95,
+ "Sea-God's Spite" : max_number_room_checks + 96,
+ "War-God's Bloodlust" : max_number_room_checks + 97,
+ "A Friendly Wager" : max_number_room_checks + 98,
+ "The Holy Liege" : max_number_room_checks + 99,
+ "The Fated Saint of War" : max_number_room_checks + 100,
+ "The Preserver's Avatar" : max_number_room_checks + 101,
+ "The Dragon's Rival" : max_number_room_checks + 102,
+ "The God-like King" : max_number_room_checks + 103,
+ "The Dawn Bringer" : max_number_room_checks + 104,
+ "The Gift of Song" : max_number_room_checks + 105,
}
@@ -173,6 +188,22 @@
"Close At Heart Event": None,
"Denizens Of The Deep Event": None,
"The Useless Trinket Event": None,
+ "Musician and Muse Event" : None,
+ "End to Torment Event" : None,
+ "Divided by Death Event" : None,
+ "Eternal Rest Event" : None,
+ "A Place of Revelry Event" : None,
+ "Amusing Chaos Event" : None,
+ "Sea-God's Spite Event" : None,
+ "War-God's Bloodlust Event" : None,
+ "A Friendly Wager Event" : None,
+ "The Holy Liege Event" : None,
+ "The Fated Saint of War Event" : None,
+ "The Preserver's Avatar Event" : None,
+ "The Dragon's Rival Event" : None,
+ "The God-like King Event" : None,
+ "The Dawn Bringer Event" : None,
+ "The Gift of Song Event" : None,
}
# ----------------------
@@ -189,10 +220,10 @@
# ---------------------
-def give_all_locations_table():
- table_rooms = give_default_location_table()
+def give_all_locations_table() -> dict:
+ table_rooms = give_default_location_table(False)
table_score = give_score_location_table(1000)
- table_weaponlocation = give_weapon_based_locations()
+ table_weaponlocation = give_weapon_based_locations(False)
return {
**table_rooms,
@@ -207,7 +238,7 @@ def give_all_locations_table():
}
-def clear_tables():
+def clear_tables() -> None:
global location_table_tartarus
location_table_tartarus = {
"Beat Meg": None,
@@ -236,7 +267,7 @@ def clear_tables():
# Change parameters so they include the settings of the player
# Chose between old and new system. And for the new system we want to be able
# to choose how many "locations" we have.
-def setup_location_table_with_settings(options):
+def setup_location_table_with_settings(options) -> None:
clear_tables()
total_table = {}
@@ -255,13 +286,13 @@ def setup_location_table_with_settings(options):
total_table.update(location_store_diamonds)
if options.location_system.value == 1:
- result = give_default_location_table()
+ result = give_default_location_table(options.disable_late_styx_scribe)
total_table.update(result)
elif options.location_system.value == 2:
levels = options.score_rewards_amount.value
total_table.update(give_score_location_table(levels))
elif options.location_system.value == 3:
- total_table.update(give_weapon_based_locations())
+ total_table.update(give_weapon_based_locations(options.disable_late_styx_scribe))
if options.fatesanity == 1:
total_table.update(location_table_fates)
@@ -271,7 +302,7 @@ def setup_location_table_with_settings(options):
# -----------------------------------------------
-def should_ignore_weapon_location(weaponLocation, options):
+def should_ignore_weapon_location(weaponLocation : str, options) -> None:
if options.initial_weapon.value == 0 and weaponLocation == "Sword Weapon Unlock Location":
return True
if options.initial_weapon.value == 1 and weaponLocation == "Bow Weapon Unlock Location":
@@ -289,7 +320,7 @@ def should_ignore_weapon_location(weaponLocation, options):
# -----------------------------------------------
-def give_default_location_table():
+def give_default_location_table(disable_late_styx : bool) -> dict:
#Repopulate tartarus table; rooms from 1 to 13.
global location_table_tartarus
for i in range(13):
@@ -310,12 +341,13 @@ def give_default_location_table():
# Repopulate styx table, rooms from 35 to 72. Split into early and late
global location_table_styx
- for i in range(35, 60):
+ for i in range(35, 55):
location_table_styx["Clear Room "+str(i + 1)] = hades_base_location_id + i
- global location_table_styx_late
- for i in range(60, 72):
- location_table_styx_late["Clear Room "+str(i + 1)] = hades_base_location_id + i
+ if (not disable_late_styx):
+ global location_table_styx_late
+ for i in range(55, 72):
+ location_table_styx_late["Clear Room "+str(i + 1)] = hades_base_location_id + i
location_table = {
**location_table_tartarus,
@@ -327,7 +359,7 @@ def give_default_location_table():
return location_table
-def give_score_location_table(locations):
+def give_score_location_table(locations : int) -> dict:
fraction_location = int(locations/8)
locations_first_region = locations - 7 * fraction_location
@@ -361,6 +393,7 @@ def give_score_location_table(locations):
location_table_styx["Clear Score "+stringInt] = hades_base_location_id + i + 72
global location_table_styx_late
+ location_table_styx_late = {}
for i in range(locations_first_region+6*fraction_location, locations):
stringInt = str(i + 1)
while len(stringInt) < 4:
@@ -378,7 +411,7 @@ def give_score_location_table(locations):
return location_table
-def give_weapon_based_locations():
+def give_weapon_based_locations(disable_late_styx : bool) -> dict:
subfixCounter = 0
weapon_locations = {}
@@ -403,15 +436,16 @@ def give_weapon_based_locations():
+ 73 * subfixCounter
weapon_locations["Beat Bros "+weaponSubfix] = None
- for i in range(35, 60):
+ for i in range(35, 55):
weapon_locations["Clear Room " + str(i + 1) + " " + weaponSubfix] = hades_base_location_id + 1073 + i \
+ 73 * subfixCounter
weapon_locations["Beat Hades " + weaponSubfix] = None
- for i in range(60, 72):
- weapon_locations["Clear Room " + str(i + 1) + " " + weaponSubfix] = hades_base_location_id + 1073 + i \
- + 73 * subfixCounter
+ if (not disable_late_styx):
+ for i in range(55, 72):
+ weapon_locations["Clear Room " + str(i + 1) + " " + weaponSubfix] = hades_base_location_id + 1073 + i \
+ + 73 * subfixCounter
subfixCounter += 1
diff --git a/worlds/hades/Options.py b/worlds/hades/Options.py
index 8945eec30adc..4db9eee6ce7c 100644
--- a/worlds/hades/Options.py
+++ b/worlds/hades/Options.py
@@ -23,8 +23,10 @@ class InitialWeapon(Choice):
class LocationSystem(Choice):
"""
Chooses how the game gives you items. (1) RoomBased gives items on every new room completed. (2) ScoreBased
- gives items according to score obtained by clearing rooms (even repeated ones). (3) RoomWeaponBased gives
- items on every new room completed with a new weapon (so more locations than the original room based system).
+ gives items according to score obtained by clearing rooms (even repeated ones). For example; each time the player
+ completes room 4 it will award 4 points. Getting a new high-score gives an item and resets the score.
+ (3) RoomWeaponBased gives items on every new room completed with a new weapon (so more locations than the
+ original room based system).
"""
display_name = "Location System"
option_room_based = 1
@@ -143,7 +145,7 @@ class HeatSystem(Choice):
display_name = "Heat System"
option_reverse_heat = 1
option_minimal_heat = 2
- option_vanillla_heat = 3
+ option_vanilla_heat = 3
default = 1
@@ -320,6 +322,17 @@ class DarknessPackValue(Range):
default = 1000
+class DarknessPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be darkness packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "Darkness Pack Percentage"
+ range_start = 0
+ range_end = 100
+ default = 10
+
+
class KeysPackValue(Range):
"""
Choose the value(amount of Keys) of each Keys pack in the pool.
@@ -331,6 +344,17 @@ class KeysPackValue(Range):
default = 20
+class KeysPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be Key packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "Keys Pack Percentage"
+ range_start = 0
+ range_end = 100
+ default = 10
+
+
class GemstonesPackValue(Range):
"""
Choose the value(amount of Gemstones) of each Gemstone pack in the pool.
@@ -342,6 +366,17 @@ class GemstonesPackValue(Range):
default = 100
+class GemstonesPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be Gemstones packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "Gemstones Pack Percentage"
+ range_start = 0
+ range_end = 1000
+ default = 10
+
+
class DiamondsPackValue(Range):
"""
Choose the value(amount of diamonds) of each diamond pack in the pool.
@@ -352,6 +387,16 @@ class DiamondsPackValue(Range):
range_end = 100
default = 15
+class DiamondsPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be Gemstones packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "Diamonds Pack Percentage"
+ range_start = 0
+ range_end = 100
+ default = 10
+
class TitanBloodPackValue(Range):
"""
@@ -364,6 +409,17 @@ class TitanBloodPackValue(Range):
default = 3
+class TitanBloodPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be Titan Blood packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "TitanBlood Pack Percentage"
+ range_start = 0
+ range_end = 100
+ default = 10
+
+
class NectarPackValue(Range):
"""
Choose the value(amount of Nectar) of each Nectar pack in the pool.
@@ -375,6 +431,16 @@ class NectarPackValue(Range):
default = 3
+class NectarPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be Nectar packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "Nectar Pack Percentage"
+ range_start = 0
+ range_end = 100
+ default = 10
+
class AmbrosiaPackValue(Range):
"""
Choose the value(amount of Ambrosia) of each Ambrosia pack in the pool.
@@ -385,13 +451,25 @@ class AmbrosiaPackValue(Range):
range_end = 50
default = 3
+
+class AmbrosiaPackPercentage(Range):
+ """
+ Choose the percentage of filler items in the pool that will be Ambrosia packs.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
+ """
+ display_name = "Ambrosia Pack Percentage"
+ range_start = 0
+ range_end = 100
+ default = 10
+
# -----------------------Settings for Helpers -------------------------
class FillerHelperPercentage(Range):
"""
- Choose the percentage of filler items in the pool that will be to helpers instead.
+ Choose the percentage of filler items in the pool that will be helpers instead.
Helpers give a boost to your max Health or boost the chance of obtaining rare Boons.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
"""
display_name = "Filler Helper Percentage"
range_start = 0
@@ -428,6 +506,7 @@ class FillerTrapPercentage(Range):
"""
Choose the percentage of filler items in the pool that will be traps instead.
Traps diminish your money or health during a run.
+ Note if filler porcentage don't sum up to 100 the system will treat them as proportions.
"""
display_name = "Filler Trap Percentage"
range_start = 0
@@ -461,6 +540,13 @@ class StoreGiveHints(DefaultOnToggle):
display_name = "Store Give Hints"
+class DisableLateStyxScribe(Toggle):
+ """
+ If true rooms pass room 55 will not have checks on room based settings.
+ """
+ display_name = "Automatic Room Finish On Hades Defeat"
+ default = 0
+
class AutomaticRoomsFinishOnHadesDefeat(Toggle):
"""
If defeating Hades should give all room clears on Room based location mode
@@ -505,12 +591,19 @@ class HadesOptions(PerGameCommonOptions):
tight_deadline_pact_amount: TightDeadlinePactAmount
personal_liability_pact_amount: PersonalLiabilityPactAmount
darkness_pack_value: DarknessPackValue
+ darkness_pack_percentage: DarknessPackPercentage
keys_pack_value: KeysPackValue
+ keys_pack_percentage: KeysPackPercentage
gemstones_pack_value: GemstonesPackValue
+ gemstones_pack_percentage: GemstonesPackPercentage
diamonds_pack_value: DiamondsPackValue
+ diamonds_pack_percentage: DiamondsPackPercentage
titan_blood_pack_value: TitanBloodPackValue
+ titan_blood_pack_percentage: TitanBloodPackPercentage
nectar_pack_value: NectarPackValue
+ nectar_pack_percentage: NectarPackPercentage
ambrosia_pack_value: AmbrosiaPackValue
+ ambrosia_pack_percentage: AmbrosiaPackPercentage
filler_helper_percentage: FillerHelperPercentage
max_health_helper_percentage: MaxHealthHelperPercentage
initial_money_helper_percentage: InitialMoneyHelperPercentage
@@ -518,6 +611,7 @@ class HadesOptions(PerGameCommonOptions):
reverse_order_em: ReverseOrderExtremeMeasure
ignore_greece_deaths: IgnoreGreeceDeaths
store_give_hints: StoreGiveHints
+ disable_late_styx_scribe : DisableLateStyxScribe
automatic_rooms_finish_on_hades_defeat: AutomaticRoomsFinishOnHadesDefeat
death_link: DeathLink
@@ -563,12 +657,19 @@ class HadesOptions(PerGameCommonOptions):
]),
OptionGroup("Filler Options", [
DarknessPackValue,
+ DarknessPackPercentage,
KeysPackValue,
+ KeysPackPercentage,
GemstonesPackValue,
+ GemstonesPackPercentage,
DiamondsPackValue,
+ DiamondsPackPercentage,
TitanBloodPackValue,
+ TitanBloodPackPercentage,
NectarPackValue,
- AmbrosiaPackValue
+ NectarPackPercentage,
+ AmbrosiaPackValue,
+ AmbrosiaPackPercentage,
]),
OptionGroup("Helpers and Trap Options", [
FillerHelperPercentage,
@@ -580,6 +681,7 @@ class HadesOptions(PerGameCommonOptions):
ReverseOrderExtremeMeasure,
IgnoreGreeceDeaths,
StoreGiveHints,
+ DisableLateStyxScribe,
AutomaticRoomsFinishOnHadesDefeat
]),
]
@@ -620,6 +722,7 @@ class HadesOptions(PerGameCommonOptions):
"max_health_helper_percentage": 40,
"initial_money_helper_percentage": 30,
"filler_trap_percentage": 0,
+ "disable_late_styx_scribe" : True,
"automatic_rooms_finish_on_hades_defeat": True,
},
"Normal": {
@@ -652,6 +755,7 @@ class HadesOptions(PerGameCommonOptions):
"ambrosia_pack_value": 2,
"filler_helper_percentage": 0,
"filler_trap_percentage": 5,
+ "disable_late_styx_scribe" : True,
"automatic_rooms_finish_on_hades_defeat": True,
},
"Hard": {
@@ -684,6 +788,7 @@ class HadesOptions(PerGameCommonOptions):
"ambrosia_pack_value": 1,
"filler_helper_percentage": 0,
"filler_trap_percentage": 10,
+ "disable_late_styx_scribe" : False,
"automatic_rooms_finish_on_hades_defeat": False,
},
}
\ No newline at end of file
diff --git a/worlds/hades/Regions.py b/worlds/hades/Regions.py
index 7cc5dd48020d..5bf8ef64e478 100644
--- a/worlds/hades/Regions.py
+++ b/worlds/hades/Regions.py
@@ -1,4 +1,4 @@
-def create_main_weapon_regions(ctx, weaponSubfix, subfixCounter, location_database):
+def create_main_weapon_regions(ctx, weaponSubfix : str, subfixCounter : int, location_database : dict) -> None:
from . import create_region
hades_base_location_id = 5093427000
@@ -28,14 +28,15 @@ def create_main_weapon_regions(ctx, weaponSubfix, subfixCounter, location_databa
+ 73 * subfixCounter
elysium["Beat Bros " + weaponSubfix] = None
- for i in range(35, 60):
+ for i in range(35, 55):
styx["Clear Room " + str(i + 1) + " " + weaponSubfix] = hades_base_location_id + 1073 + i + 73 * subfixCounter
styx["Beat Hades " + weaponSubfix] = None
- for i in range(60, 72):
- styx_late["Clear Room " + str(i + 1) + " " + weaponSubfix] = hades_base_location_id + 1073 + i \
- + 73 * subfixCounter
+ if (not ctx.options.disable_late_styx_scribe):
+ for i in range(55, 72):
+ styx_late["Clear Room " + str(i + 1) + " " + weaponSubfix] = hades_base_location_id + 1073 + i \
+ + 73 * subfixCounter
ctx.multiworld.regions += [
create_region(ctx.multiworld, ctx.player, location_database, "Tartarus " + weaponSubfix,
@@ -62,7 +63,7 @@ def create_main_weapon_regions(ctx, weaponSubfix, subfixCounter, location_databa
styx_late = {}
-def create_regions(ctx, location_database):
+def create_regions(ctx, location_database : dict) -> None:
from . import create_region
from .Locations import location_table_tartarus, location_table_asphodel, location_table_elysium, \
location_table_styx, location_table_styx_late, location_keepsakes, location_weapons, \
diff --git a/worlds/hades/Rules.py b/worlds/hades/Rules.py
index 19bae9f3bc83..90a24e61d1d4 100644
--- a/worlds/hades/Rules.py
+++ b/worlds/hades/Rules.py
@@ -26,6 +26,12 @@ def _has_enough_routine_inspection(self, player: int, amount: int, options) -> b
return True
return self._has_enough_of_item(player, amount, "Routine Inspection Pact Level")
+ def _has_all_approval_process(self, player: int, options) -> bool:
+ if not options.heat_system == "reverse_heat":
+ return True
+ total_approval_process = int(options.approval_process_pact_amount.value)
+ return self._has_enough_of_item(player, total_approval_process, "Approval Process Pact Level")
+
def _has_enough_urns(self, player: int, amount: int) -> bool:
return self.count("Urns Of Wealth1 Item", player) + self.count("Urns Of Wealth2 Item", player) \
+ self.count("Urns Of Wealth3 Item", player) >= amount
@@ -64,7 +70,7 @@ def _has_enough_fates_done(self, player: int, amount: int, options) -> bool:
amount_fates += self.count(fates_names, player)
return amount_fates >= amount
- def _has_weapon(self, weaponSubfix, player: int, option) -> bool:
+ def _has_weapon(self, weaponSubfix : str, player: int, option) -> bool:
if not option.weaponsanity:
return True
if weaponSubfix == "Sword Weapon":
@@ -92,7 +98,7 @@ def _can_get_victory(self, player: int, options) -> bool:
can_win = (can_win) and (self._has_enough_fates_done(player, fates, options))
return can_win
- def _has_defeated_boss(self, bossVictory, player: int, options) -> bool:
+ def _has_defeated_boss(self, bossVictory : str, player: int, options) -> bool:
if options.location_system == "room_weapon_based":
counter = 0
counter += self.count(bossVictory + " Sword Weapon", player)
@@ -121,7 +127,7 @@ def _enough_weapons_victories(self, player: int, options, amount: int) -> bool:
# -----------
-def set_rules(world: "HadesWorld", player: int, number_items: int, location_table, options):
+def set_rules(world: "HadesWorld", player: int, number_items: int, location_table: dict, options) -> None:
# Set up some logic in areas to avoid having all heats "stack up" as batch in other games.
total_routine_inspection = int(options.routine_inspection_pact_amount.value)
@@ -139,20 +145,20 @@ def set_rules(world: "HadesWorld", player: int, number_items: int, location_tabl
set_weapon_region_rules(world, player, number_items, location_table, options,
"Gun Weapon", total_routine_inspection)
else:
- set_rule(world.get_entrance("Exit Tartarus", player),
+ add_rule(world.get_entrance("Exit Tartarus", player),
lambda state: state.has("Meg Victory", player) and \
state._total_heat_level(player, min(number_items / 4, 10), options) and \
state._has_enough_routine_inspection(player, total_routine_inspection-2, options) and \
state._has_enough_weapons(player, options, 2))
- set_rule(world.get_entrance("Exit Asphodel", player), lambda state: state.has("Lernie Victory", player) and \
+ add_rule(world.get_entrance("Exit Asphodel", player), lambda state: state.has("Lernie Victory", player) and \
state._total_heat_level(player, min(number_items / 2, 20), options) and \
state._has_enough_routine_inspection(player, total_routine_inspection - 1, options) and \
state._has_enough_weapons(player, options, 3))
- set_rule(world.get_entrance("Exit Elysium", player), lambda state: state.has("Bros Victory", player) and \
+ add_rule(world.get_entrance("Exit Elysium", player), lambda state: state.has("Bros Victory", player) and \
state._total_heat_level(player, min(number_items * 3 / 4, 30), options) and \
state._has_enough_routine_inspection(player, total_routine_inspection, options) and \
state._has_enough_weapons(player, options, 5))
- set_rule(world.get_location("Beat Hades", player), lambda state: \
+ add_rule(world.get_location("Beat Hades", player), lambda state: \
state._total_heat_level(player, min(number_items, 35), options) and \
state._has_enough_weapons(player, options, 6))
@@ -165,7 +171,7 @@ def set_rules(world: "HadesWorld", player: int, number_items: int, location_tabl
add_rule(world.get_location("Eurydice Keepsake", player), lambda state: \
state._has_defeated_boss("Lernie Victory", player, options))
add_rule(world.get_location("Thanatos Keepsake", player), lambda state: \
- state._has_defeated_boss("Hades Victory", player, options))
+ state._has_defeated_boss("Bros Victory", player, options))
add_rule(world.get_location("Patroclus Keepsake", player), lambda state: \
state._has_defeated_boss("Bros Victory", player, options))
set_keepsake_balance(world, player, location_table, options)
@@ -183,72 +189,74 @@ def set_rules(world: "HadesWorld", player: int, number_items: int, location_tabl
state.has("Court Musician Sentence Item",player))
-def set_keepsake_balance(world: "HadesWorld", player: int, location_table, options):
- set_rule(world.get_location("Zeus Keepsake", player), lambda state: \
+def set_keepsake_balance(world: "HadesWorld", player: int, location_table : dict, options) -> None:
+ add_rule(world.get_location("Zeus Keepsake", player), lambda state: \
state.has("Zeus Keepsake", player))
- set_rule(world.get_location("Poseidon Keepsake", player), lambda state: \
+ add_rule(world.get_location("Poseidon Keepsake", player), lambda state: \
state.has("Poseidon Keepsake", player))
- set_rule(world.get_location("Athena Keepsake", player), lambda state: \
+ add_rule(world.get_location("Athena Keepsake", player), lambda state: \
state.has("Athena Keepsake", player))
- set_rule(world.get_location("Aphrodite Keepsake", player), lambda state: \
+ add_rule(world.get_location("Aphrodite Keepsake", player), lambda state: \
state.has("Aphrodite Keepsake", player))
- set_rule(world.get_location("Ares Keepsake", player), lambda state: \
+ add_rule(world.get_location("Ares Keepsake", player), lambda state: \
state.has("Ares Keepsake", player))
- set_rule(world.get_location("Artemis Keepsake", player), lambda state: \
+ add_rule(world.get_location("Artemis Keepsake", player), lambda state: \
state.has("Artemis Keepsake", player))
- set_rule(world.get_location("Dionysus Keepsake", player), lambda state: \
+ add_rule(world.get_location("Dionysus Keepsake", player), lambda state: \
state.has("Dionysus Keepsake", player))
- set_rule(world.get_location("Demeter Keepsake", player), lambda state: \
+ add_rule(world.get_location("Demeter Keepsake", player), lambda state: \
state.has("Demeter Keepsake", player))
- set_rule(world.get_location("Hermes Keepsake", player), lambda state: \
+ add_rule(world.get_location("Hermes Keepsake", player), lambda state: \
+ state._has_defeated_boss("Meg Victory", player, options))
+ add_rule(world.get_location("Megaera Keepsake", player), lambda state: \
state._has_defeated_boss("Meg Victory", player, options))
-def set_store_rules(world: "HadesWorld", player: int, location_table, options):
+def set_store_rules(world: "HadesWorld", player: int, location_table : dict, options) -> None:
#Fountains
- set_rule(world.get_location("Fountain Upgrade1 Location", player), lambda state: \
+ add_rule(world.get_location("Fountain Upgrade1 Location", player), lambda state: \
state.has("Fountain Tartarus Item", player))
- set_rule(world.get_location("Fountain Upgrade2 Location", player), lambda state: \
+ add_rule(world.get_location("Fountain Upgrade2 Location", player), lambda state: \
state.has("Fountain Upgrade1 Item", player) and state.has("Fountain Tartarus Item", player))
- set_rule(world.get_location("Fountain Asphodel Location", player), lambda state: \
+ add_rule(world.get_location("Fountain Asphodel Location", player), lambda state: \
state.has("Fountain Tartarus Item", player) and state.has("Keepsake Collection Item", player) and \
state._has_defeated_boss("Meg Victory", player, options))
- set_rule(world.get_location("Fountain Elysium Location", player), lambda state: \
+ add_rule(world.get_location("Fountain Elysium Location", player), lambda state: \
state.has("Fountain Tartarus Item", player) and state.has("Keepsake Collection Item", player) and \
state._has_defeated_boss("Lernie Victory", player, options))
#Urns
- set_rule(world.get_location("Urns Of Wealth1 Location", player), lambda state: \
+ add_rule(world.get_location("Urns Of Wealth1 Location", player), lambda state: \
state.has("Fountain Tartarus Item", player))
- set_rule(world.get_location("Urns Of Wealth2 Location", player), lambda state: state._has_enough_urns(player,1))
- set_rule(world.get_location("Urns Of Wealth3 Location", player), lambda state: state._has_enough_urns(player,2))
+ add_rule(world.get_location("Urns Of Wealth2 Location", player), lambda state: state._has_enough_urns(player,1))
+ add_rule(world.get_location("Urns Of Wealth3 Location", player), lambda state: state._has_enough_urns(player,2))
#Infernal Trove
- set_rule(world.get_location("Infernal Trove2 Location", player), lambda state: state._has_enough_throve(player,1) \
+ add_rule(world.get_location("Infernal Trove2 Location", player), lambda state: state._has_enough_throve(player,1) \
and state.has("Fountain Tartarus Item",player) and state.has("Fountain Asphodel Item",player) \
and state.has("Fountain Elysium Item",player) and state.has("Keepsake Collection Item", player))
- set_rule(world.get_location("Infernal Trove3 Location", player), lambda state: state._has_enough_throve(player,2) \
+ add_rule(world.get_location("Infernal Trove3 Location", player), lambda state: state._has_enough_throve(player,2) \
and state.has("Fountain Tartarus Item",player) and state.has("Fountain Asphodel Item",player) \
and state.has("Fountain Elysium Item",player) and state.has("Keepsake Collection Item", player) \
and state.has("Deluxe Contractor Desk Item", player))
#Keepsake storage
- set_rule(world.get_location("Keepsake Collection Location", player), lambda state: \
+ add_rule(world.get_location("Keepsake Collection Location", player), lambda state: \
state._has_defeated_boss("Meg Victory", player, options) and state.has("Fountain Tartarus Item", player))
#Deluxe contractor desk
- set_rule(world.get_location("Deluxe Contractor Desk Location", player), lambda state: \
+ add_rule(world.get_location("Deluxe Contractor Desk Location", player), lambda state: \
state.has("Fountain Elysium Item", player) and state.has("Court Musician Sentence Item", player) and \
state.has("Urns Of Wealth1 Item", player) and state.has("Keepsake Collection Item", player) and \
state.has("Infernal Trove1 Item", player))
#Other random stuff
- set_rule(world.get_location("Vanquishers Keep Location", player), lambda state:\
+ add_rule(world.get_location("Vanquishers Keep Location", player), lambda state:\
state.has("Deluxe Contractor Desk Item", player))
- set_rule(world.get_location("Fishing Rod Location", player), lambda state: \
+ add_rule(world.get_location("Fishing Rod Location", player), lambda state: \
state._has_defeated_boss("Bros Victory", player, options) and state.has("Fountain Tartarus Item", player))
- set_rule(world.get_location("Court Musician Stand Location", player), lambda state: \
+ add_rule(world.get_location("Court Musician Stand Location", player), lambda state: \
state.has("Court Musician Sentence Item", player))
#Orpheus might have other rules depending on settings
@@ -256,97 +264,127 @@ def set_store_rules(world: "HadesWorld", player: int, location_table, options):
state._has_defeated_boss("Meg Victory", player, options) and state.has("Fountain Tartarus Item", player))
#Upgrades of runs with gems locations
- set_rule(world.get_location("Pitch Black Darkness Location", player), lambda state: \
+ add_rule(world.get_location("Pitch Black Darkness Location", player), lambda state: \
state.has("Deluxe Contractor Desk Item", player))
- set_rule(world.get_location("Fated Keys Location", player), lambda state: \
+ add_rule(world.get_location("Fated Keys Location", player), lambda state: \
state.has("Deluxe Contractor Desk Item", player))
- set_rule(world.get_location("Brilliant Gemstones Location", player), lambda state: \
+ add_rule(world.get_location("Brilliant Gemstones Location", player), lambda state: \
state.has("Deluxe Contractor Desk Item", player))
- set_rule(world.get_location("Vintage Nectar Location", player), lambda state: \
+ add_rule(world.get_location("Vintage Nectar Location", player), lambda state: \
state.has("Deluxe Contractor Desk Item", player))
- set_rule(world.get_location("Darker Thirst Location", player), lambda state: \
+ add_rule(world.get_location("Darker Thirst Location", player), lambda state: \
state.has("Deluxe Contractor Desk Item", player))
-def set_fates_rules(world: "HadesWorld", player: int, location_table, options, subfix: str):
+def set_fates_rules(world: "HadesWorld", player: int, location_table : dict, options, subfix: str) -> None:
#Rules that dont depend on other settings
- set_rule(world.get_location("Is There No Escape?" + subfix, player), lambda state: \
+ add_rule(world.get_location("Is There No Escape?" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
- set_rule(world.get_location("Harsh Conditions" + subfix, player), lambda state: \
+ add_rule(world.get_location("Harsh Conditions" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
- set_rule(world.get_location("Slashed Benefits" + subfix, player), lambda state: \
+ add_rule(world.get_location("Slashed Benefits" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
- set_rule(world.get_location("The Useless Trinket" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Useless Trinket" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
- set_rule(world.get_location("Wanton Ransacking" + subfix, player), lambda state: \
+ add_rule(world.get_location("Wanton Ransacking" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
- set_rule(world.get_location("Dark Reflections" + subfix, player), lambda state: \
+ add_rule(world.get_location("Dark Reflections" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
#Rules that depend on storesanity
if options.storesanity:
- set_rule(world.get_location("The Reluctant Musician" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Reluctant Musician" + subfix, player), lambda state: \
state.has("Court Musician Sentence Item", player))
- set_rule(world.get_location("Denizens Of The Deep" + subfix, player), lambda state: \
+ add_rule(world.get_location("Musician and Muse" + subfix, player), lambda state: \
+ state.has("Court Musician Sentence Item", player) \
+ and state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("The Gift of Song" + subfix, player), lambda state: \
+ state.has("Court Musician Sentence Item", player))
+ add_rule(world.get_location("Denizens Of The Deep" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options) and state.has("Fishing Rod Item",player))
+ add_rule(world.get_location("A Place of Revelry" + subfix, player), lambda state: \
+ state.has("Deluxe Contractor Desk Item", player))
else:
- set_rule(world.get_location("The Reluctant Musician" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Reluctant Musician" + subfix, player), lambda state: \
state._has_defeated_boss("Meg Victory", player, options))
- set_rule(world.get_location("Denizens Of The Deep" + subfix, player), lambda state: \
+ add_rule(world.get_location("Musician and Muse" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("Denizens Of The Deep" + subfix, player), lambda state: \
state._has_defeated_boss("Hades Victory", player, options))
#This part depends on weaponsanity, but the false option is handled on has_enough_weapons
- set_rule(world.get_location("Infernal Arms" + subfix, player), lambda state: \
+ add_rule(world.get_location("Infernal Arms" + subfix, player), lambda state: \
state._has_enough_weapons(player, options, 6))
- set_rule(world.get_location("A Violent Past" + subfix, player), lambda state: \
+ add_rule(world.get_location("A Violent Past" + subfix, player), lambda state: \
state._has_enough_weapons(player, options, 6))
- set_rule(world.get_location("Master Of Arms" + subfix, player), lambda state: \
+ add_rule(world.get_location("Master Of Arms" + subfix, player), lambda state: \
state._has_enough_weapons(player, options, 6) and \
state._has_defeated_boss("Hades Victory", player, options))
#rules that depend on weaponsanity:
if options.weaponsanity:
- set_rule(world.get_location("The Stygian Blade" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Stygian Blade" + subfix, player), lambda state: \
state.has("Sword Weapon Unlock Item", player) or options.initial_weapon == 0)
- set_rule(world.get_location("The Heart Seeking Bow" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Heart Seeking Bow" + subfix, player), lambda state: \
state.has("Bow Weapon Unlock Item", player) or options.initial_weapon == 1)
- set_rule(world.get_location("The Eternal Spear" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Eternal Spear" + subfix, player), lambda state: \
state.has("Spear Weapon Unlock Item", player) or options.initial_weapon == 2)
- set_rule(world.get_location("The Shield Of Chaos" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Shield Of Chaos" + subfix, player), lambda state: \
state.has("Shield Weapon Unlock Item", player) or options.initial_weapon == 3)
- set_rule(world.get_location("The Twin Fists" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Twin Fists" + subfix, player), lambda state: \
state.has("Fist Weapon Unlock Item", player) or options.initial_weapon == 4)
- set_rule(world.get_location("The Adamant Rail" + subfix, player), lambda state: \
+ add_rule(world.get_location("The Adamant Rail" + subfix, player), lambda state: \
state.has("Gun Weapon Unlock Item", player) or options.initial_weapon == 5)
+ add_rule(world.get_location("Eternal Rest" + subfix, player), lambda state: \
+ state.has("Sword Weapon Unlock Item", player) or options.initial_weapon == 0)
+
+ #balancing for aspects related fates
+ add_rule(world.get_location("The Holy Liege" + subfix, player), lambda state: \
+ state.has("Sword Weapon Unlock Item", player) or options.initial_weapon == 0)
+ add_rule(world.get_location("The Preserver's Avatar" + subfix, player), lambda state: \
+ state.has("Bow Weapon Unlock Item", player) or options.initial_weapon == 1)
+ add_rule(world.get_location("The Fated Saint of War" + subfix, player), lambda state: \
+ state.has("Spear Weapon Unlock Item", player) or options.initial_weapon == 2)
+ add_rule(world.get_location("The Dragon's Rival" + subfix, player), lambda state: \
+ state.has("Shield Weapon Unlock Item", player) or options.initial_weapon == 3)
+ add_rule(world.get_location("The God-like King" + subfix, player), lambda state: \
+ state.has("Fist Weapon Unlock Item", player) or options.initial_weapon == 4)
+ add_rule(world.get_location("The Dawn Bringer" + subfix, player), lambda state: \
+ state.has("Gun Weapon Unlock Item", player) or options.initial_weapon == 5)
+
+
if options.keepsakesanity:
- set_rule(world.get_location("Close At Heart" + subfix, player), lambda state: \
+ add_rule(world.get_location("Close At Heart" + subfix, player), lambda state: \
state._has_enough_keepsakes(player, 23, options))
#This is balancing rules, so grinding gods is less painful
- set_rule(world.get_location("Goddess Of Wisdom" + subfix, player), lambda state: \
+ add_rule(world.get_location("Goddess Of Wisdom" + subfix, player), lambda state: \
state.has("Athena Keepsake", player))
- set_rule(world.get_location("God Of The Heavens" + subfix, player), lambda state: \
+ add_rule(world.get_location("God Of The Heavens" + subfix, player), lambda state: \
state.has("Zeus Keepsake", player))
- set_rule(world.get_location("God Of The Sea" + subfix, player), lambda state: \
+ add_rule(world.get_location("God Of The Sea" + subfix, player), lambda state: \
state.has("Poseidon Keepsake", player))
- set_rule(world.get_location("Goddess Of Love" + subfix, player), lambda state: \
+ add_rule(world.get_location("Goddess Of Love" + subfix, player), lambda state: \
state.has("Aphrodite Keepsake", player))
- set_rule(world.get_location("God Of War" + subfix, player), lambda state: \
+ add_rule(world.get_location("God Of War" + subfix, player), lambda state: \
state.has("Ares Keepsake", player))
- set_rule(world.get_location("Goddess Of The Hunt" + subfix, player), lambda state: \
+ add_rule(world.get_location("Goddess Of The Hunt" + subfix, player), lambda state: \
state.has("Artemis Keepsake", player))
- set_rule(world.get_location("God Of Wine" + subfix, player), lambda state: \
+ add_rule(world.get_location("God Of Wine" + subfix, player), lambda state: \
state.has("Dionysus Keepsake", player))
- set_rule(world.get_location("God Of Swiftness" + subfix, player), lambda state: \
- state.has("Hermes Keepsake", player))
- set_rule(world.get_location("Goddess Of Seasons" + subfix, player), lambda state: \
+ add_rule(world.get_location("Goddess Of Seasons" + subfix, player), lambda state: \
state.has("Demeter Keepsake", player))
#Balancing rules
- #Put here a rule that you need to have all God keepsakes for the DivinePairis?
- set_rule(world.get_location("Primordial Boons" + subfix, player), lambda state: \
+ add_rule(world.get_location("Primordial Boons" + subfix, player), lambda state: \
state.has("Chaos Keepsake", player))
- set_rule(world.get_location("Primordial Banes" + subfix, player), lambda state: \
+ add_rule(world.get_location("Primordial Banes" + subfix, player), lambda state: \
state.has("Chaos Keepsake", player))
+ add_rule(world.get_location("Amusing Chaos" + subfix, player), lambda state: \
+ state.has("Chaos Keepsake", player))
+ add_rule(world.get_location("Sea-God's Spite" + subfix, player), lambda state: \
+ state.has("Poseidon Keepsake", player))
+ add_rule(world.get_location("War-God's Bloodlust" + subfix, player), lambda state: \
+ state.has("Ares Keepsake", player))
#Balacing rules for DivinePairings
add_rule(world.get_location("Divine Pairings" + subfix, player), lambda state: \
@@ -363,48 +401,139 @@ def set_fates_rules(world: "HadesWorld", player: int, location_table, options, s
state.has("Artemis Keepsake", player))
add_rule(world.get_location("Divine Pairings" + subfix, player), lambda state: \
state.has("Dionysus Keepsake", player))
- add_rule(world.get_location("Divine Pairings" + subfix, player), lambda state: \
- state.has("Hermes Keepsake", player))
add_rule(world.get_location("Divine Pairings" + subfix, player), lambda state: \
state.has("Demeter Keepsake", player))
+
+ if options.hidden_aspectsanity:
+ #balancing for aspects related fates
+ add_rule(world.get_location("The Holy Liege" + subfix, player), lambda state: \
+ state.has("Sword Hidden Aspect", player) or options.initial_weapon == 0)
+ add_rule(world.get_location("The Preserver's Avatar" + subfix, player), lambda state: \
+ state.has("Bow Hidden Aspect", player) or options.initial_weapon == 1)
+ add_rule(world.get_location("The Fated Saint of War" + subfix, player), lambda state: \
+ state.has("Spear Hidden Aspect", player) or options.initial_weapon == 2)
+ add_rule(world.get_location("The Dragon's Rival" + subfix, player), lambda state: \
+ state.has("Shield Hidden Aspect", player) or options.initial_weapon == 3)
+ add_rule(world.get_location("The God-like King" + subfix, player), lambda state: \
+ state.has("Fist Hidden Aspect", player) or options.initial_weapon == 4)
+ add_rule(world.get_location("The Dawn Bringer" + subfix, player), lambda state: \
+ state.has("Gun Hidden Aspect", player) or options.initial_weapon == 5)
# This is extra balancing rules to avoid fates being a pain in the butt
add_rule(world.get_location("Primordial Boons" + subfix, player), lambda state: \
- state._has_defeated_boss("Lernie Victory", player, options))
+ state._has_defeated_boss("Hades Victory", player, options))
add_rule(world.get_location("Primordial Banes" + subfix, player), lambda state: \
state._has_defeated_boss("Lernie Victory", player, options))
+ add_rule(world.get_location("Primordial Boons" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("Primordial Banes" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
- set_rule(world.get_location("Power Without Equal" + subfix, player), lambda state: \
- state._has_defeated_boss("Bros Victory", player, options))
add_rule(world.get_location("Divine Pairings" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+
+ add_rule(world.get_location("Power Without Equal" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("Divine Pairings" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+
+ add_rule(world.get_location("Chthonic Colleagues" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
+ add_rule(world.get_location("Customer Loyalty" + subfix, player), lambda state: \
state._has_defeated_boss("Lernie Victory", player, options))
-
+
+ add_rule(world.get_location("Goddess Of Wisdom" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("God Of The Heavens" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("God Of The Sea" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("Goddess Of Love" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("God Of War" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("Goddess Of The Hunt" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("God Of Wine" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("God Of Swiftness" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("Goddess Of Seasons" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+
+ add_rule(world.get_location("The Stygian Blade" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("The Heart Seeking Bow" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("The Eternal Spear" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("The Shield Of Chaos" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("The Twin Fists" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+ add_rule(world.get_location("The Adamant Rail" + subfix, player), lambda state: \
+ state._has_all_approval_process(player, options))
+
+ add_rule(world.get_location("End to Torment" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("Divided by Death" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options) \
+ and state.has("Court Musician Sentence Item", player))
+ add_rule(world.get_location("Eternal Rest" + subfix, player), lambda state: \
+ state._has_defeated_boss("Lernie Victory", player, options))
+ add_rule(world.get_location("A Place of Revelry" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("Amusing Chaos" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("Sea-God's Spite" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("War-God's Bloodlust" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("A Friendly Wager" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+ add_rule(world.get_location("The Gift of Song" + subfix, player), lambda state: \
+ state._has_defeated_boss("Hades Victory", player, options))
+
+ #balancing for aspects related fates
+ add_rule(world.get_location("The Holy Liege" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
+ add_rule(world.get_location("The Fated Saint of War" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
+ add_rule(world.get_location("The Preserver's Avatar" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
+ add_rule(world.get_location("The Dragon's Rival" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
+ add_rule(world.get_location("The God-like King" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
+ add_rule(world.get_location("The Dawn Bringer" + subfix, player), lambda state: \
+ state._has_defeated_boss("Bros Victory", player, options))
def set_weapon_region_rules(world: "HadesWorld", player: int, number_items: int,
- location_table, options, weaponSubfix: str, total_routine_inspection: int):
- set_rule(world.get_entrance("Zags room " + weaponSubfix, player), lambda state: \
+ location_table : list, options, weaponSubfix: str,
+ total_routine_inspection: int) -> None:
+ add_rule(world.get_entrance("Zags room " + weaponSubfix, player), lambda state: \
state._has_weapon(weaponSubfix, player, options))
- set_rule(world.get_entrance("Exit Tartarus " + weaponSubfix, player), lambda state: \
+ add_rule(world.get_entrance("Exit Tartarus " + weaponSubfix, player), lambda state: \
state.has("Meg Victory " + weaponSubfix, player) and \
state._total_heat_level(player, min(number_items / 4, 10), options) and \
state._has_enough_routine_inspection(player, total_routine_inspection - 2, options) and \
state._has_enough_weapons(player, options, 2))
- set_rule(world.get_entrance("Exit Asphodel " + weaponSubfix, player), lambda state: \
+ add_rule(world.get_entrance("Exit Asphodel " + weaponSubfix, player), lambda state: \
state.has("Lernie Victory " + weaponSubfix, player) and \
state._total_heat_level(player, min(number_items / 2, 20), options) and \
state._has_enough_routine_inspection(player, total_routine_inspection - 1, options) and \
state._has_enough_weapons(player, options, 3))
- set_rule(world.get_entrance("Exit Elysium " + weaponSubfix, player), lambda state: \
+ add_rule(world.get_entrance("Exit Elysium " + weaponSubfix, player), lambda state: \
state.has("Bros Victory " + weaponSubfix, player) and \
state._total_heat_level(player, min(number_items * 3 / 4, 30), options) and \
state._has_enough_routine_inspection(player, total_routine_inspection, options) and \
state._has_enough_weapons(player, options, 5))
- set_rule(world.get_location("Beat Hades " + weaponSubfix, player), lambda state: \
+ add_rule(world.get_location("Beat Hades " + weaponSubfix, player), lambda state: \
state._total_heat_level(player, min(number_items, 35), options) and \
state._has_enough_weapons(player, options, 6))
-def forbid_important_items_on_late_styx(world: "HadesWorld", player: int, options):
+def forbid_important_items_on_late_styx(world: "HadesWorld", player: int, options) -> None:
if options.location_system == "room_weapon_based":
for weaponString in location_weapons_subfixes:
late_styx_region = world.get_region("Styx Late " + weaponString, player)
diff --git a/worlds/hades/__init__.py b/worlds/hades/__init__.py
index 5e430f87f218..6ea9b8435aa0 100644
--- a/worlds/hades/__init__.py
+++ b/worlds/hades/__init__.py
@@ -4,7 +4,7 @@
from BaseClasses import Entrance, Item, MultiWorld, Region, Tutorial
from .Items import event_item_pairs_weapon_mode, item_table, item_table_pacts, HadesItem, event_item_pairs, \
- create_pact_pool_amount, create_filler_pool_options, item_table_keepsake, item_table_weapons, \
+ create_pact_pool_amount, item_table_keepsake, item_table_weapons, \
item_table_store, item_table_hidden_aspects, create_trap_pool, item_name_groups
from .Locations import setup_location_table_with_settings, give_all_locations_table, HadesLocation, \
location_table_fates_events, location_name_groups
@@ -61,9 +61,9 @@ class HadesWorld(World):
topology_present = False
settings: typing.ClassVar[HadesSettings]
web = HadesWeb()
- required_client_version = (0, 5, 0)
+ required_client_version = (0, 6, 4)
- polycosmos_version = "0.13"
+ polycosmos_version = "0.14"
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = give_all_locations_table()
@@ -71,7 +71,7 @@ class HadesWorld(World):
item_name_groups = item_name_groups
location_name_groups = location_name_groups
- def create_items(self):
+ def create_items(self) -> None:
local_location_table = setup_location_table_with_settings(self.options).copy()
pool = []
@@ -123,8 +123,6 @@ def create_items(self):
self.multiworld.get_location(
event, self.player).place_locked_item(event_item)
- # create the pack of filler options
- filler_options = create_filler_pool_options(self.options)
# Fill filler items uniformly. Maybe later we can tweak this.
index = 0
@@ -136,30 +134,80 @@ def create_items(self):
# Substract the 4 bosses
total_fillers_needed = total_fillers_needed - 4
+ #Get the percentagesa and compute then compute the quantity of each one
+ darkness_percentage = self.options.darkness_pack_percentage
+ keys_pack_percentage = self.options.keys_pack_percentage
+ gemstones_pack_percentage = self.options.gemstones_pack_percentage
+ diamonds_pack_percentage = self.options.diamonds_pack_percentage
+ titan_blood_pack_percentage = self.options.titan_blood_pack_percentage
+ nectar_pack_percentage = self.options.nectar_pack_percentage
+ ambrosia_pack_percentage = self.options.ambrosia_pack_percentage
helper_percentage = self.options.filler_helper_percentage
- helper_fillers_needed = int(total_fillers_needed * helper_percentage / 100)
+ trap_percentage = self.options.filler_trap_percentage
+
+ total_percentage = darkness_percentage + keys_pack_percentage + gemstones_pack_percentage \
+ + diamonds_pack_percentage + titan_blood_pack_percentage + nectar_pack_percentage \
+ + ambrosia_pack_percentage + helper_percentage + trap_percentage
+
+ if total_percentage == 0:
+ darkness_percentage = 100
+
+ correction = 100/total_percentage
+
+ traps_needed = int(total_fillers_needed * trap_percentage * correction / 100)
+ helpers_needed = int(total_fillers_needed * helper_percentage * correction / 100)
+ ambrosia_needed = int(total_fillers_needed * ambrosia_pack_percentage * correction / 100)
+ nectar_needed = int(total_fillers_needed * nectar_pack_percentage * correction / 100)
+ titan_blood_needed = int(total_fillers_needed * titan_blood_pack_percentage * correction / 100)
+ diamonds_needed = int(total_fillers_needed * diamonds_pack_percentage * correction / 100)
+ gemstones_needed = int(total_fillers_needed * gemstones_pack_percentage * correction / 100)
+ keys_needed = int(total_fillers_needed * keys_pack_percentage * correction / 100)
+ darkness_needed = total_fillers_needed - keys_needed - gemstones_needed - diamonds_needed \
+ - titan_blood_needed - nectar_needed - ambrosia_needed - helpers_needed - traps_needed
+
+
- trap_percentage = min(self.options.filler_trap_percentage, 100 - helper_percentage)
- trap_fillers_needed = int(total_fillers_needed * trap_percentage / 100)
trap_pool = create_trap_pool()
- fillers_needed = total_fillers_needed-trap_fillers_needed - helper_fillers_needed
- for amount in range(0, fillers_needed):
- item_name = filler_options[index]
- item = HadesItem(item_name, self.player)
+ #Fill the standard fillers
+ for amount in range(0, darkness_needed):
+ item = HadesItem("Darkness", self.player)
+ pool.append(item)
+
+ for amount in range(0, keys_needed):
+ item = HadesItem("Keys", self.player)
+ pool.append(item)
+
+ for amount in range(0, gemstones_needed):
+ item = HadesItem("Gemstones", self.player)
+ pool.append(item)
+
+ for amount in range(0, diamonds_needed):
+ item = HadesItem("Diamonds", self.player)
+ pool.append(item)
+
+ for amount in range(0, titan_blood_needed):
+ item = HadesItem("TitanBlood", self.player)
+ pool.append(item)
+
+ for amount in range(0, nectar_needed):
+ item = HadesItem("Nectar", self.player)
+ pool.append(item)
+
+ for amount in range(0, ambrosia_needed):
+ item = HadesItem("Ambrosia", self.player)
pool.append(item)
- index = (index + 1) % len(filler_options)
# Fill helpers
- health_helpers_needed = int(helper_fillers_needed * self.options.max_health_helper_percentage / 100)
- money_helpers_needed = int(helper_fillers_needed * self.options.initial_money_helper_percentage / 100)
- boon_helpers_needed = helper_fillers_needed-health_helpers_needed - money_helpers_needed
+ health_helpers_needed = int(helpers_needed * self.options.max_health_helper_percentage / 100)
+ money_helpers_needed = int(helpers_needed * self.options.initial_money_helper_percentage / 100)
+ boon_helpers_needed = helpers_needed-health_helpers_needed - money_helpers_needed
for amount in range(0, health_helpers_needed):
item = HadesItem("Max Health Helper", self.player)
pool.append(item)
- for amount in range(0, min(money_helpers_needed, helper_fillers_needed - health_helpers_needed)):
+ for amount in range(0, min(money_helpers_needed, helpers_needed - health_helpers_needed)):
item = HadesItem("Initial Money Helper", self.player)
pool.append(item)
@@ -170,7 +218,7 @@ def create_items(self):
index = 0
# Fill traps
- for amount in range(0, trap_fillers_needed):
+ for amount in range(0, traps_needed):
item_name = trap_pool[index]
item = HadesItem(item_name, self.player)
pool.append(item)
@@ -178,7 +226,7 @@ def create_items(self):
self.multiworld.itempool += pool
- def should_ignore_weapon(self, name):
+ def should_ignore_weapon(self, name : str) -> bool:
if self.options.initial_weapon.value == 0 and name == "Sword Weapon Unlock Item":
return True
if self.options.initial_weapon.value == 1 and name == "Bow Weapon Unlock Item":
@@ -193,12 +241,12 @@ def should_ignore_weapon(self, name):
return True
return False
- def set_rules(self):
+ def set_rules(self) -> None:
local_location_table = setup_location_table_with_settings(self.options).copy()
set_rules(self.multiworld, self.player, self.calculate_number_of_pact_items(
), local_location_table, self.options)
- def calculate_number_of_pact_items(self):
+ def calculate_number_of_pact_items(self) -> int:
# Go thorugh every option and count what is the chosen level
total = int(self.options.hard_labor_pact_amount)
total += int(self.options.lasting_consequences_pact_amount)
@@ -221,7 +269,7 @@ def calculate_number_of_pact_items(self):
def create_item(self, name: str) -> Item:
return HadesItem(name, self.player)
- def create_regions(self):
+ def create_regions(self) -> None:
local_location_table = setup_location_table_with_settings(self.options).copy()
create_regions(self, local_location_table)
@@ -251,7 +299,7 @@ def get_filler_item_name(self) -> str:
return "Darkness"
-def create_region(multiworld: MultiWorld, player: int, location_database, name: str, locations=None, exits=None):
+def create_region(multiworld: MultiWorld, player: int, location_database, name: str, locations=None, exits=None) -> Region:
ret = Region(name, player, multiworld)
if locations:
for location in locations:
diff --git a/worlds/hades/archipelago.json b/worlds/hades/archipelago.json
new file mode 100644
index 000000000000..668f54b4378a
--- /dev/null
+++ b/worlds/hades/archipelago.json
@@ -0,0 +1,6 @@
+{
+ "game": "Hades",
+ "authors": ["Naix"],
+ "world_version": "0.14.0",
+ "minimum_ap_version": "0.6.4"
+}
diff --git a/worlds/jakanddaxter/client.py b/worlds/jakanddaxter/client.py
index fa2aea5495ef..7cb8cb8ea95f 100644
--- a/worlds/jakanddaxter/client.py
+++ b/worlds/jakanddaxter/client.py
@@ -601,7 +601,7 @@ async def run_game(ctx: JakAndDaxterContext):
f"Please check your host.yaml file.\n"
f"If the value of 'jakanddaxter_options > auto_detect_root_directory' is true, verify that OpenGOAL "
f"is installed properly.\n"
- f"If it is false, check the value of 'jakanddaxter_options > root_directory'."
+ f"If it is false, check the value of 'jakanddaxter_options > root_directory'. "
f"Verify it is a valid existing path, and all backslashes have been replaced with forward slashes.")
ctx.on_log_error(logger, msg)
return
diff --git a/worlds/jakanddaxter/docs/setup_en.md b/worlds/jakanddaxter/docs/setup_en.md
index 9cd892a9b2c7..379b4eecf214 100644
--- a/worlds/jakanddaxter/docs/setup_en.md
+++ b/worlds/jakanddaxter/docs/setup_en.md
@@ -9,7 +9,7 @@ At this time, this method of setup works on Windows only, but Linux support is a
## Installation via OpenGOAL Launcher
-**You must set up a vanilla installation of Jak and Daxter before you can install mods for it.**
+**You must set up a vanilla installation of Jak and Daxter before you can install mods for it.** These instructions will work on both NTSC and PAL versions of the game.
- Follow the installation process for the official OpenGOAL Launcher. See [here](https://opengoal.dev/docs/usage/installation).
- Follow the setup process for adding mods to the OpenGOAL Launcher. See [here](https://jakmods.dev/).
@@ -18,45 +18,7 @@ At this time, this method of setup works on Windows only, but Linux support is a
- Click `Features` in the bottom right corner, then click `Mods`.
- Under `Available Mods`, click `ArchipelaGOAL`. The mod should begin installing. When it is done, click `Continue` in the bottom right corner.
- **DO NOT PLAY AN ARCHIPELAGO GAME THROUGH THE OPENGOAL LAUNCHER.** The Archipelago Client should handle everything for you.
-
-### For NTSC versions of the game, follow these steps.
-
-- Run the OpenGOAL Launcher (if you had it open before, close it and reopen it).
-- Click the Jak and Daxter logo on the left sidebar.
-- Click `Features` in the bottom right corner, then click `Mods`, then under `Installed Mods`, click `ArchipelaGOAL`.
-- In the bottom right corner, click `Advanced`, then click `Compile`.
-
-### For PAL versions of the game, follow these steps.
-
-PAL versions of the game seem to require additional troubleshooting/setup in order to work properly.
-Below are some instructions that may help.
-If you see `-- Compilation Error! --` after pressing `Compile` or Launching the ArchipelaGOAL mod, try these steps.
-
-- Remove these folders if you have them:
- - `/iso_data`
- - `/iso_data`
- - `/data/iso_data`
-- Place your Jak1 ISO in `` and rename it to `JakAndDaxter.iso`
-- Type `cmd` in Windows search, right click `Command Prompt`, and pick `Run as Administrator`
-- Run `cd `
-- Then run `.\extractor.exe --extract --extract-path .\data\iso_data "JakAndDaxter.iso"`
- - This command should end by saying `Uses Decompiler Config Version - ntsc_v1` or `... - pal`. Take note of this message.
-- If you saw `ntsc_v1`:
- - In cmd, run `.\decompiler.exe data\decompiler\config\jak1\jak1_config.jsonc --version "ntsc_v1" data\iso_data data\decompiler_out`
-- If you saw `pal`:
- - Rename `\data\iso_data\jak1` to `jak1_pal`
- - Back in cmd, run `.\decompiler.exe data\decompiler\config\jak1\jak1_config.jsonc --version "pal" data\iso_data data\decompiler_out`
- - Rename `\data\iso_data\jak1_pal` back to `jak1`
- - Rename `\data\decompiler_out\jak1_pal` back to `jak1`
-- Open a **brand new** console window and launch the compiler:
- - `cd `
- - `.\goalc.exe --user-auto --game jak1`
- - From the compiler (in the same window): `(mi)`. This should compile the game. **Note that the parentheses are important.**
- - **Don't close this first terminal, you will need it at the end.**
-- Then, open **another brand new** console window and execute the game:
- - `cd `
- - `.\gk.exe -v --game jak1 -- -boot -fakeiso -debug`
-- Finally, **from the first console still in the GOALC compiler**, connect to the game: `(lt)`.
+- Once the mod is installed, in the bottom right corner, click `Advanced`, then click `Compile`.
## Updates and New Releases via OpenGOAL Launcher
diff --git a/worlds/jakanddaxter/test/test_trades.py b/worlds/jakanddaxter/test/test_trades.py
index 92552f9d0eb6..97b7c2a220cf 100644
--- a/worlds/jakanddaxter/test/test_trades.py
+++ b/worlds/jakanddaxter/test/test_trades.py
@@ -7,7 +7,9 @@ class TradesCostNothingTest(JakAndDaxterTestBase):
"global_orbsanity_bundle_size": 10,
"citizen_orb_trade_amount": 0,
"oracle_orb_trade_amount": 0,
- "start_inventory": {"Power Cell": 100},
+ "fire_canyon_cell_count": 0,
+ "mountain_pass_cell_count": 0,
+ "lava_tube_cell_count": 0,
}
def test_orb_items_are_filler(self):
@@ -26,7 +28,9 @@ class TradesCostEverythingTest(JakAndDaxterTestBase):
"global_orbsanity_bundle_size": 10,
"citizen_orb_trade_amount": 120,
"oracle_orb_trade_amount": 150,
- "start_inventory": {"Power Cell": 100},
+ "fire_canyon_cell_count": 0,
+ "mountain_pass_cell_count": 0,
+ "lava_tube_cell_count": 0,
}
def test_orb_items_are_progression(self):
diff --git a/worlds/kdl3/presets.py b/worlds/kdl3/presets.py
index 491ad9dca993..f07973e6b0d1 100644
--- a/worlds/kdl3/presets.py
+++ b/worlds/kdl3/presets.py
@@ -7,7 +7,7 @@
"game_language": "random",
"goal": "random",
"goal_speed": "random",
- "total_heart_stars": "random",
+ "max_heart_stars": "random",
"heart_stars_required": "random",
"filler_percentage": "random",
"trap_percentage": "random",
@@ -34,7 +34,7 @@
beginner = {
"goal": "zero",
"goal_speed": "normal",
- "total_heart_stars": 50,
+ "max_heart_stars": 50,
"heart_stars_required": 30,
"filler_percentage": 25,
"trap_percentage": 0,
diff --git a/worlds/kh1/Options.py b/worlds/kh1/Options.py
index 879ea4c331f3..221a1818c67d 100644
--- a/worlds/kh1/Options.py
+++ b/worlds/kh1/Options.py
@@ -589,7 +589,7 @@ class OrichalcumInPool(Range):
You need 17 to synth every recipe that requires it.
"""
- display_name = "Mythril In Pool"
+ display_name = "Orichalcum In Pool"
default = 20
range_start = 17
range_end = 30
diff --git a/worlds/kh1/__init__.py b/worlds/kh1/__init__.py
index fbdc99206a2f..593a612bbdbb 100644
--- a/worlds/kh1/__init__.py
+++ b/worlds/kh1/__init__.py
@@ -30,7 +30,7 @@ class KH1Web(WebWorld):
theme = "ocean"
tutorials = [Tutorial(
"Multiworld Setup Guide",
- "A guide to setting up the Kingdom Hearts Randomizer software on your computer."
+ "A guide to setting up the Kingdom Hearts Randomizer software on your computer. "
"This guide covers single-player, multiworld, and related software.",
"English",
"kh1_en.md",
diff --git a/worlds/kh1/docs/kh1_en.md b/worlds/kh1/docs/kh1_en.md
index f6b6a640673a..ad534b6f6dae 100644
--- a/worlds/kh1/docs/kh1_en.md
+++ b/worlds/kh1/docs/kh1_en.md
@@ -1,6 +1,6 @@
# Kingdom Hearts Archipelago Randomizer Setup Guide
-
Required software
+## Required software
- KINGDOM HEARTS -HD 1.5+2.5 ReMIX- 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/)
@@ -10,9 +10,9 @@
- The latest release of [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) for the ArchipelagoKH1Client.exe
-
Setting up the required software
+## Setting up the required software
-
OpenKH
+### OpenKH
- Extract the OpenKH files to a directory of your choosing.
- When prompted for game edition, choose PC Release, select which platform you're using (EGS or Steam), navigate to your `Kingdom Hearts I.5 + II.5` installation folder in the path box and click `Next`.
@@ -21,11 +21,11 @@
- Extract the data for KH1.
- Click `Finish`
-
Kingdom Hearts 1FM Randomizer Software
+### Kingdom Hearts 1FM Randomizer Software
- Extract the Kingdom Hearts 1FM Randomizer Software files in a directory of your choosing.
-
Obtaining and using the seed zip
+## Obtaining and using the seed zip
- When you generate a game you will see a download link for a KH1 .zip seed on the room page.
- After downloading this zip, open `mod_generator.exe` in your Kingdom Hearts 1FM Randomizer Software folder.
@@ -38,7 +38,7 @@
- Ensure this mod is checked, then, if you want to play right away, click `Mod Loader` at the top.
- Click `Build and Run`. Your modded game should now open.
-
Connecting to your multiworld via the KH1 Client
+## Connecting to your multiworld via the KH1 Client
- Once your game is being hosted, open `ArchipelagoLauncher.exe`.
- Find `KH1 Client` and open it.
@@ -48,52 +48,68 @@
- After typing your slot name, press enter.
- If all is well, you are now connected.
-
FAQ
+## FAQ
-
The client did not confirm connection to the game, is that normal?
+### The client did not confirm connection to the game, is that normal?
Yes, the game and client communicate via a game communication path set up in your in your `%AppData%` folder, and therefore don't need to establish a socket connection.
-
I am not sending or receiving items.
+### I am not sending or receiving items.
Check out this [troubleshooting guide](https://docs.google.com/document/d/1oAXxJWrNeqSL-tkB_01bLR0eT0urxz2FBo4URpq3VbM/edit?usp=sharing)
-
Why aren't the evidence boxes spawning in Wonderland?
+### Why aren't the evidence boxes spawning in Wonderland?
You'll need to find `Footprints` in your multiworld.
-
Why won't Phil let me start the Prelims?
+### Why won't Phil let me start the Prelims?
You'll need to find `Entry Pass` in the multiworld.
-
Why aren't the slides spawning in Deep Jungle?
+### Why aren't the slides spawning in Deep Jungle?
You'll need to find `Slides` in the multiworld.
-
Why can't I make progress in Atlantica?
+### Why can't I make progress in Atlantica?
You'll need to find `Crystal Trident` in the multiworld.
-
Why won't the doctor let me progress in Halloween Town?
+### Why won't the doctor let me progress in Halloween Town?
You'll need to find either `Forget-Me-Not` or `Jack-in-the-Box` in the multiworld.
-
Why is there a book missing in the Hollow Bastion library?
+### Why is there a book missing in the Hollow Bastion library?
You'll need to find `Theon Vol. 6` in the multiworld.
-
How do I unlock End of the World?
+### How do I unlock End of the World?
Depending on your settings, your options are either finding a specified amount of `Lucky Emblems` or finding the item `End of the World`.
-
How do I enter Destiny Islands?
+### How do I enter Destiny Islands?
After obtaining the item `Destiny Islands`, you can land there as an additional option in Traverse Town.
-
How do I progress to Destiny Islands Day 2 and 3?
+### How do I progress to Destiny Islands Day 2 and 3?
In order to access Day 2 and 3, you need to collect an amount of `Raft Materials` specified in your settings. When you start Day 3, you'll be immediately warped to Homecoming.
-
Why can't I use the summon I obtained?
+### Why can't I use the summon I obtained?
You need at least one magic spell before you can use summons.
+
+## Troubleshooting
+
+### Why am I not sending or receiving any items despite being connected to the server?
+
+Make sure you are using the KH1 Client and not the Text Client. You will need to open the client manually via the Archipelago Launcher.
+
+### Why don't I have any worlds on the world map?
+
+If you have any of these symptoms: you find that the title screen does not have the Archipelago logo, that you had to do the entirety of Dive to the Heart, that you do not warp to the world map after choosing your Dream Weapons, or that when you get to the world map there are no worlds there;